l

2022年3月21日 星期一

物件聚合與類別繼承的取捨

March 21 15:16~16:14

截圖 2022-03-21 下午3.23.02

 

不是說好要少用繼承嗎?

昨天上「Design Patterns這樣學就會了–入門實作班」,講完Template Method設計模式之後Teddy問學員:「GoF不是說Favor object composition over class inheritance,但Template Method卻使用class inheritance,為什麼?你們能不能用object composition 達到Template Method的效果?

 

圖1是Teddy上課時設計的Template Method範例,鄉民們一起想想看,如何用object composition來取代Template Method。

截圖 2022-03-21 下午3.24.11

▲圖1:Template Method類別圖

***

不使用繼承的設計

圖2是使用object composition取代class inheritance的設計,原本Template Method所呼叫的primitive operations或是hook operations改呼叫實作Operation的具體類別,請參考圖3程式碼。

 

截圖 2022-03-21 下午3.53.02

▲圖2:用object composition取代class inheritance (套用Command設計模式取代Template Method)

 

截圖 2022-03-21 下午3.51.19

▲圖3:圖2中的ConfigParser程式示意範例

 

***

哪種設計比較好?

如果只從Favor object composition over class inheritance的觀點來思考,圖2套用Command設計模式的設計比較好。但是,請數一下這兩個設計所需要的類別/介面數量:

  • Template Method:如圖1所示需要3個類別:
  • Command:參考圖3,ConfigParser與Operation這兩個是固定的,要分別支援從檔案與資料庫讀取設定資料,所以需要兩種DataSource實作,因此最少需要 1 + 1 + 2 + 4 = 8個類別/介面。

 

從Kent Beck的建單設計(Simple Design)原則來看:

  1. Passes the Tests
  2. Reveals Intention
  3. No Duplication
  4. Fewest Elements

第4條:最少元素,達到相同的功能,在這個例子裏面Template Method(class inheritance)用了3個「元素」,而Command(object composition)則用了至少8個「元素」,因此在這裡用Template Method的設計應該是比較簡單的設計。

***

友藏內心獨白:繼承不是不能用。

2022年3月18日 星期五

領域模型 VS 資料模型

March 18 15:36~17:02

 

截圖 2022-03-18 下午4.49.39

▲看板桌遊

 

問題

昨天在北科上軟體架構請學生練習Event Storming,有學生問Teddy:「域驅動設計強調要建立域模型,但我不太清楚領域模型 (domain model) 與資料模型 (data model) 有什麼不一樣?領域模型裡面物件的屬性,和資料模型的資料欄位不是很類似嗎?

領域驅動設計強調透過開發人員與領域專家共同合作建立領域模型,而不要採用傳統資料驅動或是使用者介面驅動的方式來開發軟體。道理很簡單,但領域模型與資料模型到底有何不同,許多人並無法分辨。

今天來談這個問題。

***

資料驅動

請看圖1和圖2畫面,你會覺得這是兩個不同的軟體,還是同一個軟體?

 

截圖 2022-03-18 下午4.00.07

▲圖1:ezKanban畫面

 

截圖 2022-03-18 下午4.00.17

▲圖2:看板桌遊畫面

 

如果從「資料驅動」的角度來看,看了這兩個圖,大部分的人會覺得這是兩個不同的系統。以圖2為例,畫面上有以下不同種類的卡片:

  • 事件卡片:Day 9那張卡片代表event card,它的背面也有資料。你可能在資料庫中設計一個event_card table來儲存事件卡片的資料。
  • 標準卡片:S開頭的卡片,如果設計一個standard_card table來儲存它的資料,則該table欄位可能包含analysis_total_work, analysis_done_work, dev_total_work, dev_done_work, test_total_work, test_done_work, day_deployed, day_ready, lead_time, subscribers等欄位。
  • 固定交期卡片:F1與F2卡片,這兩張卡片的內容與標準卡片很像,感覺可以把資料放在standard_card table,只要多增加一個card_type欄位來區分卡片種類是標準卡片還是固定交期卡片即可。
  • 技術卡片:和前兩種卡片也很像,可以把資料放在standard_card table

看板遊戲還有骰子、Ready、Analysis、Dev、Test、Ready to Deploy、Deployed等固定的工作階段,也要設計相對應的資料庫表格來儲存這些資料。

***

至於圖1的ezKanban,卡片內容記錄的資料和看板桌遊不一樣,所以可能需要一個card table來紀錄資料,裡有可能有cardId, workflowId, laneId, description, deadline, note, estimate, assignees 等欄位。很明顯地,兩個系統的資料模型並不一樣。

***

領域驅動

如果從領域驅動的角度來看,ezKanban與看板桌遊廣義來看都屬於「看板」這個問題領域,所以它們的商業邏輯與行為很可能絕大部分是相同的。例如,從看板系統的三個核心原則來看:

  • 視覺化:視覺化團隊的工作流程與工作項目
  • 限制WIP:限制工作階段的WIP (Work In Progress)
  • 管理工作流:可以測量Lead Time與Cycle Time,看到被阻礙的工作。

這三個特性,在ezKanban與看板桌遊都成立。從領域驅動的角度來看,兩者可以視為同一個系統,共享相同的領域模型,請參考圖3,只不過前端顯示方式不同。

 

   截圖 2022-03-18 下午6.42.55

▲圖3:ezKanban領域模型,看板桌遊也可以共用此領域模型

 

***

看到這裡鄉民們可能會想:「兩個系統的資料模型明明就不一樣啊,硬要說它們是同一個系統,那麼要如何儲存兩者的資料,資料庫要怎麼設計?」

很簡單,如果把看板桌遊視為ezKanban的一部分,那麼看板桌遊的事件卡片、標準卡片、固定交期卡片、技術卡片,就全部都是ezKanban裡面Card這個Aggregate的一種特例。只要將看板桌遊的卡片所需要的資料以JSON的格式存入Card的note欄位即可,如圖4所示。

 

截圖 2022-03-18 下午4.30.04

▲圖4:標準卡片S9所需的資料,用JSON格式表達

 

***

結論

ezKanban因為採用領域驅動設計的方式開發,因此將看板桌遊視為看板系統的一種特例,只有前端顯示桌遊的React程式不同,後端絕大部分的功能都是一樣,不需重複開發。

但如果從資料驅動設計使用者介面驅動設計的角度來看,後端就非常有可能變成兩個完全不同的系統,產生很多重複的工作。

***

友藏內心獨白:不管是單體還是微服務,有複雜邏輯的系統還是採用領域驅動設計比較好。

2022年2月11日 星期五

愛上Mob Programming之突破瓶頸篇

Jan. 11 03:22~04:55

截圖 2022-02-11 上午3.45.16

▲圖1:Pitest產生的涵蓋率報表

 

前言

今年初開始幫ezKanban的use case tests(使用案例的測試案例)改用Given-When-Then的格式讓它更接近Living Documentation,如圖2。改完之後發現use case tests與Aggregate Root測試案例(單元測試)有許多重疊的現象。因為ezKanban的所有Aggregate Root都包含了合約(Contracts),在程式執行期間會自動驗證程式正確性,因此Teddy就在想「是不是可以透過Specification by Example方式所撰寫的驗收案例,加上幫Aggregate撰寫合約,來省略entity layer的單元測試」。

截圖 2022-02-11 上午3.44.38

▲圖2:加上Given-When-Then的測試案例

 

如果這個想法成立,就可以在確保程式正確性的前提之下少寫很多單元測試 。問題是怎麼知道拿掉entity layer的單元測試只要有Aggregate Root的合約依然可以確保程式正確性呢?軟體測試中有一種叫做Mutation testing(變異測試)的方法可以回答這個問題。

 

***

驗證關卡1

有兩位非常優秀的ezKanban的成員負責驗證這個想法,這個題目並成為其中一位的碩士論文。他們找到Pitest這個Mutation testing工具,但是使用在ezKanban的時候遇到問題一直無法解決。因為這是個最新冒出來的題目,所以還沒有機會在ezKanban團隊的mobbing活動中一起處理,這兩位成員是利用mobbing以外的時間去嘗試解決。

昨天和ezKanban團隊mobbing,上午把這幾周以來一直在處裡的持續整合工作告一段落。盤點一下手邊的工作,團隊決定一起看看Pitest的問題。團隊手邊有一個測試用的小專案,可以正常執行Pitest,但是當團隊在ezKanban的專案中執行Pitest卻會出現如圖3的不明錯誤訊息。

 

截圖 2022-02-11 上午4.07.46

▲圖3:Pitest錯誤不明訊息

 

由於團隊是透過maven去執行Pitest,因此大家懷疑是不是maven專案的pom.xml檔案設定有問題。於是團隊試著修改pom.xml檔案但問題還是沒解決。後來Teddy想到,既然有一個可以正常執行的專案,那乾脆把ezKanban的程式碼複製到這個專案中看看能不能執行,這樣子就可以確定到底是ezKanban程式碼導致Pitest執行失敗,還是因為maven設定的問題。

將ezKanban程式碼複製過去之後可以正常執行Pitest,於是團隊試著比較兩個專案的pom.xml的差異。大家看來看去也看不出來到底是哪裡有問題,索性將兩個檔案文字比對,如圖四)。

 

截圖 2022-02-11 上午4.19.42

▲圖4:比對兩個專案的pom.xml差異

 

發現原來ezKanban使用JUnit 5的artifactId是junit-jupiter-api,如下:

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.2</version>
</dependency>

 

而Pitest只支援JUnit 4,但有鄉民幫它加工之後讓它可以在Junit 5執行,但是此時artifactId要改成junit-jupiter。

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>

 

改用正確的依賴之後,Pitest就可以產生如圖1的報表。

***

 

驗證關卡2

ezKanban系統由若干的maven專案所構成,一開始團隊使用DDDCore這個相依性最簡單,只包含Clean Architecture的Entities Layer與Use Cases Layer,沒有使用到SpringBoot與資料庫的專案來測試Pitest。測試成功之後改用Account專案,這個專案除了包含Clean Architecture中的完整四個Layers,除了使用到SpringBoot與資料庫,還用到Java 17最新的預覽功能,例如Pattern Matching for Switch。

執行Pitest之後出現圖5所示的錯誤訊息,。

截圖 2022-02-11 上午4.36.52

▲圖5:Pitest無法辨認byte code格式

 

很簡單啊,就是執行Pitest的時候加上—enable-preview參數就好了啊。問題是,要加在哪裡?試了幾種方式,後來有團隊成員找到正確的格式,如圖6所示。

 

截圖 2022-02-11 上午4.41.23

▲圖6:加上—enable-preview參數讓Pittest支援Java 17預覽功能

 

***

打破個人的瓶頸

瓶頸就是系統中生產力最弱的環節,每一個人都有自己擅長的地方,也有自己鬼遮眼的時候。軟體開發是一個動態的系統,採用傳統的單人開發模式(solo programming),每位開發人員同時各自開工,看似生產效率很高,但事實上很可能這些各自執行的thread經常處於block(卡住)狀態。

Pair programming可以稍微改善這種情況,但兩個人一組還是比不上全部的人一組。你可能會說:「全部的人一組還是會有盲點啊,可能這個鬼很厲害,把全部的人的眼睛都遮住了。」沒錯,可能會這樣。但從Boundary(邊界)的角度來看,整個團隊一起開發已經是這個團隊能力範圍內最大的「邊界」,也就是他們已經同時間盡其所能的一起合作解決問題。如果這個「鬼」那麼厲害能夠遮住全部人的眼睛,你派一個人或是兩個人去對付這個鬼,更加無法打敗它。

 

***

 

友藏內心獨白:團隊一起處裡有價值的工作。

2021年12月31日 星期五

2021年終心得:Coding & Learning

Dec. 31 20:34~22:08

截圖 2021-12-27 下午3.59.13

 

上次寫年終心得是〈2019年終心得:突破〉,這兩年疫情爆發,也發生了不少事,加上晚上有空,寫個2021年回顧好了。
今年的心得可以用Coding & Learning兩個字來概括。

2021年泰迪軟體的重大事件有:

***

業績

今年泰迪軟體的營業額是成立以來最高的一年,比2019年的高點還要多一點點。雖然因為疫情緣故很多傳統的課程像是Scrum、Design Patterns公開班今年都只開了一次,重構與測試的公開班課程則是完全停開,但受惠於【領域驅動設計與簡潔架構入門實作班】頗受鄉民歡迎,開了好幾梯次的公開班與企業內訓,反倒是增加不少收入。

但【領域驅動設計與簡潔架構入門實作班】之所以受歡迎,源自Teddy在2019年說過的一段話:「研究許久的Clean Architecture(簡潔架構)Domain-Driven Design(領域驅動設計),今年結合了這兩者,發現無論是在架構上、problem modeling以及coding,軟體開發與維護變得比較容易也更系統化。」

加上從2020年6月底開始,Teddy與北科大軟體系統實驗室的ezKanban團隊開始mobbing,至今花了超過1500小時的時間一起開發ezKanban。在這個過程中大量套用DDD、Event Storming、Clean Architecture、Event Sourcing、CQRS、TDD、Design by Contract(DBC)、Exception Handling、Specification by Example(SBE)、Living Documentation、Continuous Integration、Refactoring等方法,幾乎快把Teddy畢生所學都用在上面。

Teddy很喜歡Kent Beck說過的一句話:「If you stop coding, you stop learning」身為一位軟體開發從業人員,不管到了什麼階段,還是應該持續保持寫程式的習慣。寫程式的目的不一定是要拚production code的數量,而是要從中學習。這種學習的深度,會與所寫的程式數量成正比。寫得越多,領得越多。啊,不對,是寫得越多,學得越多。

今年七月ezKanban幾位研二學生畢業之後團隊只剩下3位學生,後來OIS團隊加入,增加了一搏、二碩、三大,一共六個人。感謝這幾位學生今年和Teddy一起mobbing,度過許多美好的coding時光。

***

畢業

2019年Teddy提到Erica已經能夠獨立作業,在敏捷教練、引導、敏捷需求管理方面也有不錯的成績。今年9月底Erica從泰迪軟體畢業,回到她的家鄉生活與工作。

泰迪軟體一開始就只有「Teddy」一個人,Erica加入之後,幫了很多忙,很多事情是Teddy能力範圍以外,剛好是Erica比較擅長,兩人互補。Erica剛到泰迪軟體的時候,Teddy告訴她:「我沒有把你當成員工,而是把你當成博士生來看待。我對你的責任就是希望能夠讓你有一技之長,別人不是因為你是泰迪軟體的Erica而付你錢,而是因為你是Erica而付你錢。這一天到來,就等於你博士班畢業。」

其實Erica的「點數」在2019年已經集滿,達到畢業的標準,算是在泰迪軟體多幫忙2年。當年Teddy博士班唸了N年,雖然捨不得但終究還是要離開實驗室。「畢業」,是另一個成長的開始,不是結束。


泰迪軟體又回到只有Teddy的公司,反映公司的名稱,可能是當初取名子就已經決定好的宿命XD。

***

感恩

2021-12-29 22.53.51

▲難得三喵同床

 

明年Teddy預計將ezKanban部署成微服務架構,在微服務架構上再多花點時間深入研究,同時準備這方面的新課程。另外,最近Teddy找到一個在DDD中簡單落實Living Documentation的方法,明年也會花點時間在這上面。

最後感謝2021年直接或間接金援泰迪軟體的所有鄉民與親朋好友。沒有金錢的幫助,泰迪軟體無法經營下去,當然也就沒有現在的Teddy。

對於一個不拉幫結派、不搞花俏行銷、不造神,相信把自己的專長持續做到最好就有一線生機的笨蛋,還可以在這片土地上生存下去,而且活得快樂、活的有尊嚴。除了感恩,還是感恩。

***

友藏內心獨白:想不到更好的,重複使用了2015、2019年的結語。

2021年12月23日 星期四

無痛將驗收測試文件寫在測試案例中

Dec. 23 20:18~21:45

截圖 2021-12-23 下午9.42.42

▲新買的電腦還沒寫code先來寫部落格

 

前情提要

2017年Teddy寫過一系列關於行為驅動開發(Behavior-Driven Development,BDD)的文章,請參考以下列表:

  1. BDD(1):詳盡的文件就是可用的軟體
  2. BDD(2):大家來吃小黃瓜之Cucumber運作原理
  3. BDD(3):在Eclipse執行Cucumber-JVM
  4. BDD(4):第一個Cucumber-JVM範例,上集
  5. BDD(5):第一個Cucumber-JVM範例,下集
  6. 在IntelliJ IDEA使用Cucumber(上)
  7. 在IntelliJ IDEA使用Cucumber(下)
  8. BDD(6):讓Step找到Step Definition
  9. BDD(7):使用Transform讓稅金同時支援5%和0.05表達方式
  10. BDD(8):實作第一個開發票Scenario
  11. 在IntelliJ IDEA使用Cucumber(上)
  12. 在IntelliJ IDEA使用Cucumber(下)

 

當時學了一陣子Cucumber但後來一直沒有真正使用它。主要原因在於Teddy覺得「Cucumber將需求寫在feature file裡面,然後再轉成step definition程式碼,然後開發人員在這些step definition立面撰寫真正的測試邏輯」的這種流程不太流暢。完整開發一個功能,從寫feature file到寫完produciton code的過程會一直撞牆(卡住),尤其是feature file有參數要傳給step definition,真的不太直覺。

但當時Teddy也沒多想,就把這個問題放著。

***

活文件

前幾天讀了《Living Documentation: Continuous Knowledge Sharing by Design》,書中有一個例子如下圖所示:

 

截圖 2021-12-23 下午8.59.19

▲圖1:Living Documentation書中範例

 

看到這個例子Teddy突然心中有感:「對了,就是這樣。應該要把Cucumber的feature file內容寫在程式碼中而不是與程式碼分離。」於是Teddy也試著修改ezKanban的測試案例看看效果如何,請參考下圖:

 

截圖 2021-12-23 下午9.26.24

▲圖2:修改後的ezKanban驗收測試案例

 

Teddy覺得直接在測試案例程式碼加上Given-When-Than說明文字讓測試案例清楚很多,也免去了原本Cucumber在feature file與step definition之間做binding的麻煩。

至於圖2中Scenario(), When(), WhenFailure()是怎麼來的?其實很簡單,原本Teddy以為書中用了什麼工具,但找了一下沒找到。後來想到,自己寫一個不就好了。程式很簡單,長成下面這樣:

截圖 2021-12-23 下午9.34.27

▲圖3:用來在測試案例中撰寫Given-When-Than說明文字的工具,算是一的超級簡單的DSL(domain specific language)

 

這種做法,與程式語言和工具都無關。絕大部分的高階語言要寫出圖3中的程式應該是幾分鐘就搞定的事。

 

***

 

友藏內心獨白:本篇在新買的第12代i9電腦上撰寫XD。

2021年10月5日 星期二

頭痛醫腳,腳痛醫頭

Oct. 05 17:24~18:55

截圖 2022-02-11 上午5.13.47

 

幾年前有一陣子Teddy的左腳腳底板只要踩到地上就好痛,觀察了幾天都沒改善,查了網路資料,感覺很像足底筋膜炎。找了一天去看家裡附近的的復健科整所,把症狀告訴醫生之後,Teddy跟醫生說:「我覺得我好像是足底筋膜炎」。

醫生聽完之後面帶微笑,要Teddy擺出幾個姿勢,然後說:「你要拉腰。」

明明是腳底痛,為什麼要去拉腰?

醫生說:「你這是腰椎突出影響到腳,我看過很多類似的病人,他們也都以為是腳有問題。但這個現象一般來講只要拉腰就可以改善。」

Teddy半信半疑之下持續去復健拉腰,沒想到後來腳底板疼痛的問題就消失了。

***

最近有一位客戶打電話給Teddy…

Teddy:泰迪軟體您好,我是Teddy。

客戶:Teddy老師你好,我們是XX公司。

客戶:請問你們有單元測試的企業內訓課程嗎?

Teddy:有啊,泰迪軟體所有公開班課程都提供企業內訓。

客戶:是這樣,最近我們的系統出了點包,公司想要提升軟體品質,長期來講要做持續整合與持續佈署,短期想先從測試開始。

Teddy:這很合理,一般人想到提升品質、減少bug,直接就想到測試。

客戶:所以我們從自動化單元測試著手是OK的?

Teddy:從測試著手有兩個方向,第一個是人工測試,雖然看起來很笨,但只要有錢,這是最簡單又立即可以看到效果的方法,但長遠來講會不符成本,畢竟用人工方式做回歸測試不只成本高,速度也慢。

Teddy:如果從自動化單元測試著手,不是說員工學會測試技巧就可以快快樂樂寫出有效的單元測試。依據我的經驗,很多人不寫單元測試,或是說寫不出單元測試,不是因為測試太難,而是因為設計太爛

Teddy:因為設計太爛,所以耦合太高,系統很難獨立測試。這時候,可能需要先教同仁基本的物件導向設計觀念與SOLID等原則,更進階要學習Design Patterns與Clean Architecture。軟體設計品質提升,測試自然變得比較容易。

***

軟體品質不好,不一定直接從測試著手。有沒有可能是需求一開始就錯了?或是規格沒弄清楚?如果是這種狀況,也許要先學Event Storming、Specification By Example與Design By Contract。有沒有可能是設計過於複雜導致錯誤?如果是,應該先改善設計品質,讓設計簡單到不會出現明顯的錯誤。

設計與測試是一體兩面,設計能力好的人,通常測試也做得好,反之亦然。

***

友藏內心獨白:兩點之間最短距離通常不是直線。

2021年9月15日 星期三

成本與價值

September 15 18:36~19:02

▲ezKanban 團隊mobbing實況


學員:如何讓整個團隊,包含剛進來的新人,都可以學會用DDD(領域驅動設計)開發軟體?

Teddy:我有一個很簡單且有效的方法,但這個方法就算你知道了你們公司應該也不會採用。

學員:什麼方法?

Teddy:Mobbing,又稱為Mob Programming,整個團隊,包含測試、UI/UX與Product Owner,一起開發軟體。

學員:類似Pair Programming嗎?

Teddy:對,不過是Pair Programming的加強版,不只是兩個人一起開發,而是整個團隊。

學員:這樣的確是很難在公司推行,Pair Programming老闆都覺得成本太高

Teddy:那你們都怎麼帶新人?

學員:我們會指派一位導師帶著新人做一陣子。

Teddy:然後呢?是不是一、兩個禮拜之後就讓新人單飛,然後每天問他進度?

學員:(苦笑)沒有這麼慘啦,有問題還是會回答。

Teddy:軟體開發的成本,其實很難量化。老闆通常只看到「開發人員」的成本,因為這是最直接的成本。但除了直接寫code以外,其他的成本呢?

Teddy:你要不要Code Review?有bug要不要改?要改多久?做出來的東西客戶不要怎麼辦?舊員工離職新員工交接怎麼辦?軟體搞到變成硬體,改不動也沒人敢改怎麼辦?後端、前端、內人、外人互相等對方工作完成怎麼辦?需求不清楚開發人員亂寫怎麼辦?測試團隊自創需求,胡亂回報issues怎麼辦?

Teddy:從精實開發的角度來看,這些都是浪費。很多的等待(延遲)、重工、半成品、過度生產、交接、缺陷、工作切換、重複學習。我不敢說Mobbing可以完全消除這七種浪費,但以我的經驗,這種「看起來成本太高」的開發模式反而能夠消除浪費而降低成本並提高產品的品質與價值。

Teddy:老闆覺得開發成本太高,會不會是因為自身的產品在市場上的價值太低,或是想要犧牲品質來拉低成本,所以任何提高品質的活動都會被認為是增加成本。不管如何,這有可能是對於自己產品的定位不同,產生的價值觀落差。

***

沒有銀子彈,但是有高科技武器跟二戰時的武器之分。

***

友藏內心獨白:知道了也做不到。