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(邊界)的角度來看,整個團隊一起開發已經是這個團隊能力範圍內最大的「邊界」,也就是他們已經同時間盡其所能的一起合作解決問題。如果這個「鬼」那麼厲害能夠遮住全部人的眼睛,你派一個人或是兩個人去對付這個鬼,更加無法打敗它。

 

***

 

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