l

2019年7月2日 星期二

領域驅動設計學習筆記(6):Aggregate (中)

July 02 21:45~22:48


在上一集〈領域驅動設計學習筆記(5):Aggregate (上)〉提到為了避免破壞aggregate invariant(聚合不變量或聚合規則),aggregate root在回傳資料的時候,需要考慮:

  1. 完全禁止回傳Aggregate內部的Entity。
  2. 可以回傳Entity,但只回傳immutable Entity或是Entity的deep copy。
  3. 設計immutable interface,讓Entity實作immutable interface並透過immutable interface回傳給客戶端。

今天討論這幾種做法的優缺點,並展示一個透過自動產生immutable proxy的工具來解決這問題的範例。

***

禁止回傳Aggregate內部的Entity

  • 優點:這個方法因為禁止aggregate root回傳內部entity,所以客戶端不可能透過aggregate內部entity來破壞aggregate invariant。
  • 缺點:為了提供資料給客戶端,aggregate root可能需要將內部entity轉成DTO(data transfer object)再往外傳。這種做法有點類似clean architecture中,use case層定義雙向介面將entity往外傳遞。從架構的角度來看可以切得很乾淨,但是需要花費額外的功夫包一層DTO。

***

回傳immutable entity或是entity的deep copy

  • 優點:用相同的domain model(直接回傳aggregate內部的entity)來處理程式邏輯,而且因為回傳的entity是不可修改的物件,或是原本物件的複製版本,所以也不可能透過這個entity破壞aggregate invariant。
  • 缺點:無論是實作immutable entity或是deep copy(GoF的Prototype設計模式),都需要花費額外的設計、實作與測試的功夫。特別是deep copy,不小心沒有實作好變成shallow copy就GG了。

***

設計immutable interface

Immutable interface是一種設計模式,請參考c2。簡單講,immutable interface是一個只有getter沒有setter的介面。設計好immutable interface之後,讓你的entity實作此介面。如果aggregate root要回傳entity,只能傳回entity所實作的immutable interface,如此一來客戶端就不可能修改到aggregate的資料。

Immutable interface的缺點和回傳DTO類似,domain model裡面除了原本的entity,又多了另一種代表「不可修改」的immutable interface。

***

折衷作法

如果程式語言可以支援自動回傳immutable object,這個問題就不是問題。Teddy不知道有沒有哪個程式語言有這種功能,目前只能從上述三種做法中挑一種來實做。

Teddy想用方法二。方法二有兩種實作,考慮到回傳entity的deep copy比較不好實作,所以決定優先選擇回傳immutable entity的方式。

要實作immutable entity,可以套用Proxy設計模式,概念如下:

Ministage是一個mutable介面,MinistageImpl是這個介面的實作,而ImmutableMinistage則是將MinistageImpl包裝一層,只要客戶端呼叫到setter的函數,就丟出例外。

這種方法的優點是,從客戶端的角度,他看到的就是Ministage這個domain model的概念。至於它是mutable還是immutable,則只是實作細節。

這種做法當然也有缺點,首先設計上當然比沒套用Proxy設計模式要來的複雜。其次,因為在編譯期間(compile time)客戶端不知道拿到的Ministage是mutable還是immutable,如果不小心拿到immutable但卻呼叫到它的setter函數,則會出現runtime exception。

程式設計師都有偷懶的天性,Teddy想回傳immutable entity但又懶得手動實作proxy,於是google了一下,找到一個Java語言的reflection-util工具,可以自動產生immutable proxy。


▼使用方法很簡單,以maven建構工具為例,首先加入reflection-util的參考。



▼接著在程式中透過ImmutableProxy.create()方法,可以直接產生immutable entity,非常容易使用。

***

從Clean Architecture的角度來看…

使用reflection-util這種工具,可以幾乎無痛產生immutable entity。但是從clean architecture的角度來看,在最核心的entity層引用外部工具,違反了相依性原則,你的架構就變得不再那麼乾淨了。

Teddy覺得這個「小違規」目前是可以接受的折衷方案。因為你可以透過將reflection-util再包一層,讓aggregate root透過間接的方式呼叫reflection-util來獲得immutable entity。有朝一日如果真的不想使用reflection-util,也可以在影響最小的情況下,以其他實作方式將它取而代之。

所以,雖不完美,但還可接受。

***

友藏內心獨白:設計就是取捨後的結果。

2019年6月17日 星期一

領域驅動設計學習筆記(5):Aggregate (上)

June 17 15:36~16:42


問題

在物件導向的domain model裡面,物件和物件之間經常有著複雜的關聯(associations)。如下圖所示,一個看板系統的domain model,Board擁有若干的Stage,一個Stage擁有若干個MiniStage,一個MiniStage擁有若干個SwimLane,一個SwimLane擁有若干個WorkItem。

當修改這些物件狀態時,如果不小心很可能會違反系統的不變量(invariant)或固定規則而導致系統狀態不正確(產生bug)。例如,看板系統的一個不變量是:「SwimLane的WorkItem數量不可以超過WipLimit數值。」

試想在一個多人使用的狀況下,為了滿足上述不變量,當修改SwimLane的狀態時,你可能要鎖住SwimLane,甚至是鎖住整個系統,導致系統無法使用或效率很差。

***

Aggregate Pattern

Aggregate是領域驅動設計(Domain-Driven Design;DDD)裡面很重要的pattern。依據《Domain-Driven Design: Tackling Complexity in the Heart of Software》書中的定義,Aggregate想要解決的問題與解決方案分別為:

Problem

It is difficult to guarantee the consistency of changes to objects in a model with complex associations. Invariants need to be maintained that apply to closely related groups of objects, not just discrete objects. Yet cautious locking schemes cause multiple users to interfere pointlessly with each other and make a system unusable.

很難保證具有複雜關聯的模型中物件更改的一致性。 需要維護不變量,這些不變量適用於密切相關的物件組,而不僅僅是離散的物件。 然而,謹慎的鎖定方案會導致多個用戶無意義地相互干擾並使系統無法使用。(中文修改自google翻譯)


Solution

Cluster the ENTITIES and VALUE OBJECTS into AGGREGATES and define boundaries around each. Choose one ENTITY to be the root of each AGGREGATE, and control all access to the objects inside the boundary through the root. Allow external objects to hold references to the root only. Transient references to internal members can be passed out for use within a single operation only. Because the root controls access, it cannot be blindsided by changes to the internals. This arrangement makes it practical to enforce all invariants for objects in the AGGREGATE and for the AGGREGATE as a whole in any state change.

將ENTITIES和VALUE OBJECTS聚集在一起形成AGGREGATES並定義AGGREGATES的邊界。 選擇一個ENTITY作為每個AGGREGATE的根,並透過根控制對邊界內物件的所有存取。 允許外部物件僅保留對根的引用。可以傳遞對內部成員的瞬時引用,以便僅在單個操作中使用。 因為根控制存取權限,所以內部的更改不能繞過它。 這種安排可以確保任何針對AGGREGATE裡面的物件以及AGGREGATE本身的狀態修改都不會違反不變量。(中文修改自google翻譯)

***

實作細節

以上述看板系統為例子,Stage、MiniStage與SwimLane形成一個Aggregate,Stage是Aggregate Root。依據DDD的定義,為了確保不變量:

  1. 所有對於Aggregate內部物件的操作都要透過Aggregate Root。也就是存取MiniStage與SwimLane都要透過Stage。
  2. 外部物件只可以擁有Stage物件的reference(因為它是Aggregate Root),不可以擁有MiniStage與SwimLane的reference。
  3. 但是,Stage可以將MiniStage與SwimLane往外傳,只不過客戶端程式只能在單一method中操作這些entity,不可以把它們的reference保存起來(例如,不能存在data member裡面)。

前兩點很容易理解與實作,但第三點就需要進一步討論。因為如果把MiniStage與SwimLane往外傳,既使在單一method裡面使用,也可能繞過Stage直接呼叫MiniStage與SwimLane,因此改變Stage這個Aggregat的狀態而破壞不變量。

所以,在實作Aggregate的時候,就有幾種可能的選擇:

  • 完全禁止回傳Aggregate內部的Entity。
  • 可以回傳Entity,但只回傳immutable Entity或是Entity的deep copy。
  • 設計immutable interface,讓Entity實作immutable interface並透過immutable interface回傳給客戶端。

無論採取哪種方式,在實作時都需要特別留意,以免Aggregate的不變量一不小心就被違反了。

***

廣告

對於DDD的Aggregate與Event Storming(事件風暴)有興趣的鄉民,可參考泰迪軟體的【Clean Architecture這樣學就會了實作班】,2019年7月份課程已確定開課。

***


友藏內心獨白:還是要看定義。

2019年5月31日 星期五

如何用看板管理卡住的工作項目?

May 31 10:30~11:53


狀況分析

使用看板的團隊經常會遇到工作做到一半卡住的問題,卡住的原因不外乎:

  • 遇到技術困難不知道該怎麼做下去。
  • 遇到相依性問題,需要等待其他資源(工作、人、設備、材料)完成才可以繼續。

依據卡住的時間長短,卡住的狀況又可分為:

  • 短期:團隊投入資源想辦法排除卡住的原因,每日站立會議追蹤卡住工作的進度,儘量讓工作可以順利往下游移動。
  • 遙遙無期:預期工作會卡住很久,或是在特定時間才有辦法處理該工作。不需要在每日站立會議追蹤卡住工作的進度,一周、兩周甚至一個月更新一次進度即可。

***

用看板管理卡住的工作項目

▼針對短期卡住的工作,在該工作卡片上面貼上一張粉紅色便利貼,提醒團隊該工作項目發生阻礙,需要特別關注排除阻礙讓工作順利流動。


▼每日站立會議時追蹤阻礙項目的狀況,如果尚未排除則在該阻礙項目上用筆點一點,類似progress bar的概念。如果阻礙卡片上的progress bar越長,表示該阻礙停留時間越久。

***

針對被卡住「遙遙無期」的工作項目,短時間團隊並不會也沒辦法去處理它。如果直接把它放在看板上不管,會佔據一個WIP,這樣也不好。

▼一種常見的作法是在看板上建立一個獨立的Ice Box(冰箱)工作階段,把卡住遙遙無期的工作項目移入Ice Box。如此一來原本的工作階段便空出一個WIP,而團隊只需要定期檢視Ice Box裡面的工作卡片有沒有「臭酸」或「超出保存期限」即可。

當Ice Box中的工作項目其卡住的原因排除之後,只要原本的工作階段未達WIP上限,便可移回原本的工作階段繼續施工。

***

如果怕整個看板只有一個Ice Box不容易分類不同工作階段卡住的工作項目,可以參考《Agile Project Management with Kanban》書中的做法,直接在工作階段增加一個追蹤(Track)欄位。

▼如下圖所示,追蹤欄位和Ice Box類似,(通常)沒有WIP限制,放在其中的工作項目數量不會佔據實作工作階段(WIP Limit  = 5)的WIP。

***

結論

看板透過小批量生產消除變異性讓工作流動得更順暢,縮短產品交期。卡住的工作代表產生阻礙,也就是工作流程產生變異,需要特別關注,才不會發生「人進不來,貨出不去,公司發不了財」的問題。

***

友藏內心獨白:貨暢其流。

2019年5月29日 星期三

設定看板的WIP

May 29 11:52~13:41


前言

很多剛開始跑看板的鄉民都有一個疑問:「要如何設定每個工作階段的WIP?」今天介紹《Agile Project Management with Kanban》書中提到的方法。

***

準備工作

要決定如何設定WIP,以下資料需要先準備好:

  • 視覺化工作流程,例如分析、開發、測試。
  • 知道每個工作項目(work item)完成的平均時間,例如分析工作一周完成六個,開發工作一周完成兩個,測試工作一周完成三個。
  • 每個工作階段有多少人負責,特別是瓶頸工作階段的人數(詳見下面說明)。

***

開始計算

有了以上資訊,就可以計算初始看板的WIP。參考下表,計算步驟如下:

分析

實作

測試

平均每人完成工作速度

6/week

2/week

3/week

工作階段人數


3


產能


6/week


WIP


3 * 1.5 = 5


  • 找出瓶頸,也就是速度最慢的工作階段,在這個例子中實作速度最慢。
  • 算出瓶頸的產能:平均每人完成工作速度 * 工作階段人數,2 * 3 = 6,代表實作階段每周可完成6個工作項目。
  • 將實作的WIP訂為:工作階段人數 * 1.5 (四捨五入),實作階段的開發人員有3人,3 * 1.5 = 5。乘上1.5的目的是讓每個人手邊有1.5件工作可以做,以免WIP太小任何一件工作卡住就必須等待。但也不會因為WIP太高導致context switch的浪費。這個技巧在《Kanban in Action》書中也有介紹。
  • 其他工作階段配合瓶頸速度:由於屬於瓶頸的實作階段每周只可完成6件工作,因此分析只需要1人即可搭配實作階段的消耗工作速度。同理,而測試階段只需要兩人。

分析

實作

測試

平均每人完成工作速度

6/week

2/week

3/week

工作階段人數

1

3

2

產能

6/week


6/week

6/week

WIP


3 * 1.5 = 5


  • 計算其他工作階段WIP:分析工作階段WIP = 1 * 1.5 = 2。測試階段WIP = 2 * 1.5 = 3。

分析

實作

測試

平均每人完成工作速度

6/week

2/week

3/week

工作階段人數

1

3

2

產能

6/week


6/week

6/week

WIP

2

3 * 1.5 = 5

3

***

人太多怎麼辦

剛剛的例子是由瓶頸階段的人數與平均每人完成工作的速度,回推其他工作階段需要多少人。但很多情況是團隊人數已經確定,例如分析、實作、測試各2人,請參考下表:

分析

實作

測試

平均每人完成工作速度

6/week

2/week

3/week

工作階段人數

2

2

2

產能


12/week


4/week

6/week

WIP

3

3

3


在這種情況下,因為一周完成12件分析工作但卻只能消化4件,因此分析階段的工作會累積在實作之前。而測試會沒有工作可做,導致人員閒置。

但是,幸好有WIP限制,分析工作不可能無限增加。如下圖所示,假設分析與實作階段已達WIP上限,此時分析人員便不可再從待辦事項中拉取工作來分析,只能停下來看看如何幫助工作流動得更順暢。

同理,測試階段可能沒有多餘的工作可以做,一樣需要思考如何幫助工作流動得更順暢。最直接的方式就是所謂的swarming,分析與測試階段的人到實作階段幫忙,提高實作階段,也就是瓶頸階段,的產能。

如果能夠從每個工作階段的產出可以互相匹配的角度來安排人力與設定WIP,會讓工作流動比較順暢。

當然,實務上平均每人完成工作速度變異性可能很高,受到品質、施工人員的技能與身心靈狀態、工作項目大小、需求不確定性等因素影響,導致需要控制的因素很多,因而需要時時檢討人力、WIP的設置是否恰當,以及透過流程改善來讓工作流動更加順暢,降低交期(lead time)。

***

友藏內心獨白:小批量生產與消除變異性。

2019年5月28日 星期二

不要只聽半套

May 28 23:11~23:54

▲這句怎麼都沒人聽進去XDD


故事一

有一個公司派了整個團隊來泰迪軟體上Scrum敏捷方法實作班,回公司之後團隊跑了一陣子Scrum。有一天因為產品出了包,非常緊急,Product Owner希望團隊加班趕緊把這個問題解決。沒想到,團隊中有成員居然回答PO:「我們去上Teddy的課,Teddy說跑Scrum不能加班。」

公司老闆得知後非常氣憤,有一次遇到Teddy,用開玩笑的語氣說:「我員工去上你的課,回來之後都不加班了」。

這,員工不想加班就大聲說「我不想加班」,幹嘛扯到Teddy頭上哩?!Teddy上課時是說:

敏捷開發講求「穩定的開發步驟」(sustainable pace),早期XP對於穩定開發步驟有一個簡單的說法,就是每週工作四十小時。我自己還在與Scrum團隊的時候,也是儘量利用上班專心把工作完成,以不加班為原則。

假設公司因為產品出問題都火燒屁股了,作為員工此時還在「我跑敏捷我不加班」,如果Teddy是老闆,也會發火啊。古語有云:「善戰者無赫赫之功」,這也是好的敏捷團隊期望達到的狀態。但是,天有不測風雲,總是會遇到需要「江湖救急」的時候。此時願不願意跳下來解決問題,就不是敏不敏捷的問題,而是心態的問題了。

***

故事二

某PO質問Teddy:「為什麼你跟團隊成員說retrospective沒用?」聽到這樣的質問,Teddy也是滿頭黑人問號???

細問之下,原來是Teddy和團隊成員聊天時提到:

如果retrospective會議沒有訂定具體的改善執行計畫(action plan),或是改善計畫都沒有落實,相同的問題便會一再出現,導致每次retrospective都討論一樣的議題。有迭代沒有增量,久了之後團隊就會覺得retrospective沒有用。

不知道這位團隊成員是不是buffer不夠,只擷取到「retrospective沒有用」這幾個字。也未免斷章取義的太厲害了一點吧。

***

問題是自己狀況的投射

相信很多人都有這樣的經驗,聽了一場演講,或是經過一次對話,腦袋中留下來最深刻的只有「與自己現況不滿相關的記憶」。因為這些不滿的力量非常強大,強大到足以「扭曲力場」,把別人的意思曲解成自己的內心獨白,然後採用「借刀殺人」的手段,將自己一直想說卻不敢說出口的話,假借「別人的名義」說出來。

這種斷章取義、扭曲本意、沒有脈絡(context)的引用,並非全無意義。它往往透露出講話者內心真正的感受,只不過平常不敢說出來罷了。

為什麼敏捷開發的始祖XP要提「勇敢」?因為太多人不敢面對自己,不敢正視問題。真、善、美,只有真誠的表達自己的感受,改善才有可能發生。只有改善發生,才有可能逐步趨近完美。

***

友藏內心獨白:Teddy有說多讀書你怎麼都沒聽進去?!

2019年5月24日 星期五

一個Scrum各自表述

May 24 00:40~01:27

▲每個component team都在跑Scrum,啊不就好棒棒…Orz


沒事不要找Teddy聊天XD

去年在某個演講場合,演講結束後某位鄉民找Teddy聊天….

鄉民:Teddy你好,我是你部落格的忠實讀者。

Teddy:你好、你好,謝謝捧場。

Teddy:你看我的部落格,是因為你們有跑Scrum嗎?

鄉民:對,我在用戶研究團隊跑Scrum。

Teddy:喔……用戶研究團隊跑Scrum…..這麼神奇。這是什麼意思?

鄉民:就是我們團隊會在專案開始前期用迭代、增量的方式,跑幾個sprint產出用戶研究報告。

Teddy:然後把產出的報告交給開發團隊去實作?

鄉民:對。

Teddy:怎麼我聽起來好像是瀑布式開發?

鄉民:不是、不是,我們是跑Scrum。

Teddy:你們做出用戶研究報告交給開發團隊之後,需求都不會改變嗎?

鄉民:會啊。

Teddy:那之前花時間所做的用戶研究報告需要重做嗎?

鄉民:這……….

Teddy:你們就是waterfall啊。

鄉民:我們是Scrum啦……至少在公司內我們要說我們在跑Scrum。

Teddy:那你們當初為什麼想「跑Scrum」?

鄉民:因為總經理下令說要跑Scrum。

Teddy:喔,對、對。幾個月前我有看過你們公司發的新聞稿,說你們Scrum跑得很成功。


▲有功大家練,有敏大家捷。

***

高興就好

▲圖片來源在此


陳近南:反清復明 敏捷只不過是個口號,跟阿彌陀佛其實是一樣的。Waterfall一直欺壓我們公司,浪費我們的銀兩,所以我們要反Waterfall!

韋小寶:要反Waterfall搶回我們的錢,是不是?說來說去敏不敏捷根本就是脫褲子放屁!關人鳥事呀?行了,大家聰明人,了解!繼續!

陳近南:嗯。總之,如果成功的話,就有無數的銀兩,你願不願意去呀?

韋小寶:願意~!只不過你剛剛那句九死一生太嚇人了。

***

友藏內心獨白:敏捷就是專案出得去,錢進得來,公司發大財XD。


2019年5月20日 星期一

2019 iMac 27吋開箱

May 20 20:58~22:31


Teddy手邊2016年購買的MBP 15”用了兩年半,已略顯老態。平常上網、文書處理是沒什麼問題,但只要同時開啟Parallels與IntelliJ,加上數十個分頁的Chrome,整台電腦跑起來就會有點卡卡的。

今年決定不要再買筆電,改買桌機。本來考慮iMac或是Mac Pro,但Mac Pro遲遲未上市,最近寫程式的頻率比較高,等不及Mac Pro決定先買2019年iMac。


▼美國今年三月就上市,台灣等到五月七日才開放訂購。這次狠下心來攻頂,購買最高階i9版本。


▼記憶體選擇最基本的8GB,自己另外購買4條OWC 32GB RAM,擴充為128GB。


上禮拜六(5/18)11點多iMac送到家裡,但我人卻在台南。隔天下午回台北後,趕緊開箱。


▼打開後包裝很簡潔。


▼把螢幕撲倒準備加裝記憶體,後來發現其實螢幕立著也可以裝記憶體,不用撲倒。


▼連接電源線的位置有一個按鈕,用十字起子用尖尖的東西刺一下,記憶體被蓋就會彈出。


▼兩條內建的4GB記憶體。


▼鍵盤、滑鼠、觸控板。原本考慮是否要買附數字鍵的鍵盤,但顧及桌面空間,以及之前已經用了六年多的無數字鍵的鍵盤也還算習慣,最後還是選擇無數字鍵鍵盤。這一代的鍵盤、滑鼠、觸控板都內建鋰電池,可以直接充電,方便很多。


▼開機後開始設定。


▼2019 iMac 27” 可以安裝128GB記憶體,Apple Store最多只可客制到64GB,而且價格比自己到外面購買要貴一倍。如果需要擴充記憶體,還是自己動手比較省錢。


▼因為經常需要使用Windows,此次特別買了專業版的Parallels,可以設定比較大的記憶體給VM使用。

***

剛拿到iMac兩天,目前只有一個問題,就是iMac不能調高低,Teddy覺得iMac螢幕高度太高,和以前使用的螢幕高度比較來脖子要稍微抬高,有點不舒服。

桌機的速度還是比筆電快很多,價格也比較便宜。像Teddy這種使用習慣,應該老早買桌機才對。只要搭配一台輕巧、長效的Windows筆電外出使用即可。

***

友藏內心獨白:爽度破表XDD。

2019年5月14日 星期二

三小故事之這不是多型,什麼才是多型

April 14 09:55~10:33

▲貓也是多型的生物


多型的定義

一個訊息的含義,是由訊息接收者所決定,不是由訊息發出者所決定。

***

故事一

幾年前有一個外商網通公司的HR找Teddy去他們公司擔任半年的Scrum Master。當時因為有顧問案在身,只能婉拒。

HR退而求其次,希望Teddy幫他們介紹合適的人選。Teddy請對方提供徵人資格條件,看了之後Teddy委婉的說:「你們要求的標準很高啊!」。沒想到對方居然說:「對啊,我覺得好像在找聖人一樣。」

這個故事Teddy講過好幾次,主要是要強調「好的ScrumMaster難尋」。沒想到有一次跟客戶講到這個故事,對方好幾個人居然異口同聲的說:「Teddy你是說你是聖人嗎!」

冤枉啊,包大人。小的從來都沒這麼想。也許客戶是跟Teddy開玩笑,不過還真沒想到這個故事可以被這樣解讀啊。

***

故事二

好幾年前在一個介紹Scrum的演講中,休息時間有聽眾問Teddy…

聽眾:在跑Scrum的時候怎麼設計軟體架構?

Teddy:這個問題很多人都有疑惑,我以前去上Bas的CSM課程也問過她同樣的問題。我問他,跑RUP會建議在elaboration階段產生architecture baseline,跑Scrum要怎麼設計軟體架構?

聽眾:他怎麼說?

Teddy:他反問我…什麼是architecture baseline?

聽眾:你是說Bas不懂軟體架構?

Teddy:不是啦,我是說,Scrum根本不管這些,跑敏捷的人會告訴你,軟體架構是長出來的,不是事先設計出來的。該怎麼長就怎麼長,你自己慢慢長吧你XD。

怎麼會解讀成Bas不懂軟體架構哩…

***

故事三

有一次在 C C Agile介紹TDD/BDD/SBE,Teddy提到…

很多TDD的例子,都有兩個特色。第一,domain model很簡單,只有1~2個類別。其次,都有非常清楚的商業邏輯, 這樣子學習者才可以在很短的時間內體驗TDD。例如,保齡球計分、網球記分、電子商務系統結帳計價、郵寄系統。

但是,回家之後發現,自己手邊的案子,domain model都很複雜,商業邏輯不太清楚,所以都D不太出來。

結果回家後,有鄉民又私下問Teddy…

鄉民:Teddy你剛剛是在暗指XXX的TDD例子都太簡單對不對?

Teddy:XXX?你說Uncle Bob嗎?我又沒上過他的課怎麼會知道他的例子長什麼樣子。

Teddy:我只是想指出,快速學習TDD所用的例子,和實際工作上會遇到的專案,有一些距離。而這些距離需要被克服才可以落實TDD,你想到那裏去了。

鄉民:!#!@$%

***

結論

年輕的時候,很怕被「誤會」。只要覺得別人誤解自己,就好像受到天大的冤屈,恨不得找包大人來主持公道。從某種角度看,這是一種沒有自信、缺少歸屬感的表現。

幾年前讀了幾本哲學、禪的書,越發體會Quality Without A Name 的道理。反正不管是真話、假話都無法驗證,那就不用管那麼多,全心專注在 Quality 上面。用自己的生命去展現這個 Quality,然後只要加強被討厭的勇氣就好了 XD。

***

友藏內心獨白:你今天被討厭了嗎XD。

2019年5月7日 星期二

尋找第一個Pattern

May 07 14:11~15:07


很久以前有一次Teddy在某場合介紹Alexander的pattern languages,談到這個方法是一種「由上而下」的設計過程,透過一次套用一個pattern(one pattern at a time)的方式逐次展開,採用pattern為基礎的方式解決一個大問題。就好像人類使用「語言」解決問題一樣,Alexander把這種方法稱為pattern language。

聽完演講之後有一個聽眾問我:「要如何選擇最上層的第一個pattern?」當下Teddy心裡覺得:「啊不就是你書讀得少,pattern認識沒幾個,所以才不知道要如何選擇第一個pattern。」

事實上Teddy的想法並不完全正確,對方也許真的不熟pattern,但這個問題首要的重點不在於對方懂多少個pattern,而是「最上層的第一個pattern,就是你要解決的那個大問題,也就是你想要達到的目標 (goal)」。


***

設計模式的例子

 

▲Mediator範例


上周末在上【Design Patterns這樣學就會了:進階實作班】,討論到Mediator模式的時候有學員問…

學員:如果Mediator的coordinating logic太複雜,我是不是可以把 Mediator + Strategy或是Mediator + Command混和一起使用?

Teddy:你先不要把問題複雜化,想著把A模式加B模式結合起來的可能性。「理論上」很多模式可以被「一起使用」,但如果只從解決方案來看設計模式,你會有太多排列組合都可以達到「相同功能」。這樣子學習者會無所適從,不知道怎麼套用pattern解決設計問題才合適,很容易變成為了套pattern而套pattern,變成過度設計(over design)。

Teddy:你要先問自己「目前你首要想解決的設計問題是什麼?」以Mediator的例子來看,因為你想拿掉Colleague與Colleague之間多對多的相依性,所以找一個中間人來負責協調與溝通。此時此刻,根本還沒有「Mediator的coordinating logic太複雜」的問題,所以不需要討論Mediator + Strategy或是Mediator + Command的可能性。

Teddy:當你套了第一個Mediator pattern,過了一段時間後,你發現coordinating logic太複雜。這個複雜性已經強大到讓你修改coordinating logic變得很困難,你的軟體慢慢變成了硬體。此時,你再來考慮要採取什麼方式來對付這個force。

一次一個pattern,第一個pattern解決你目前首要凸顯出來的問題。套完第一個pattern之後,如果沒有其他未被處理問題,那就沒事了。如果還有(例如Mediator的coordinating logic變得太複雜),你再進一步思考要如何解決。

***

道理很簡單但你不一定能活用

幾年前有一次到客戶端幫忙看Scrum團隊運作情況,客戶覺得他們跑的卡卡的,劈哩啪啦跟Teddy描述一大堆「可疑現象」。經過Teddy實地觀察之後,發現問題的確並不單純。

要從何開始著手?如果用pattern解題的方式來思考,要先套用哪一個pattern?

Teddy發現客戶的Scrum團隊最大的問題就是他們並不是真正的跨職能團隊(cross-functional team),團隊成員主要都是程式設計師,沒有測試專長的人,也沒有UI/UX的人。上游(UI/UX)的步調搭不上下游開發團隊的步調。經常發生UI/UX自己覺得效率很好,但他們完成的工作,開發團隊可能好幾個sprint之後才會用到,甚至也有完全沒用到的時候,最後直接丟到「垃圾桶」。

伴隨著這個根本原因,團隊產生了很多病症,必且試圖用各種有創意的方式來演緩這些病症,但都無法長期維持下去。

其實這個問題很簡單,先讓自己笨一點,既然是跑Scrum,為什麼不直接聽Scrum的話,組織一個真正的cross-functional team,先傻傻套用這個pattern「試看看」。套用之後,跑一陣子,如果有其他的問題浮現,再思考要如何解決。例如,之後可能發現需求管理很困難,團隊對於產品願景與sprint目標不明確,此時再討論嘗試套用impact mapping與user story mapping的可能性,才顯得有其意義。

***

友藏內心獨白:這麼簡單的道理居然這麼久才想通啊。

2019年5月1日 星期三

把你當作大學生

May 01 08:49~09:26

▲小屁孩時期的Ada


不打不成器

Teddy念國中的時代,體罰尚未被禁止。只要考試沒達到標準,吃板子是很常見的「改善執行計畫」。國二那年分班,新的國文老師是一位中年男士,身材略胖,學生幫他取個外號,叫做「大番薯」。

這位國文老師很「奇葩」,他幾乎不體罰。學生知道老師不打人,於是上課態度就比較隨便,考試成績也沒有表現很好。有一天,班導師語重心長地告訴我們…

班導師:國文老師把你們當成「大學生」對待,希望你們讀書要自動自發,不需要人家打你才讀書。大家不要欺負國文老師,不打人就不讀書。

班導師的一番話,並沒有感化太多學生。國中小屁孩,哪知道大學生是怎麼讀書的(以前的大學生應該比較認真,如果是現在的大學生就另當別論)。反正「沒有forces就沒有問題」,哪一科不打人,就輕鬆一點混過去就好。

***

為自己讀書

「把你們當成大學生對待」,一直到Teddy念碩士班之後,才慢慢體會這句話的真義(因為Teddy沒機會念大學XD)。學習、讀書,本當是個人的責任。學校、老師提供一個情境(context),讓學生有機會成型。至於成為哪一種形狀,只能由學生自己決定。

出社會之後,因為工作繁忙,有許多人的學習模式變成找名師。希望獲得名師加持,不須自己努力,就可以立刻頓悟。這種想法,很容易被利用。名師沒找到,詐神倒是遇到不少

有些學生,希望老師直接給他一個可以學習(抄襲)的例子,而不去管背後的理論基礎,也不去讀書。好像學會例子就可以快速變成該領域的專家,但這是不可能的,至少Teddy還沒發覺這種可能性。

真正把一門學問弄懂,除了找很多例子,也需要讀很多書學習背後的理論基礎。光看例子,很容易被例子所侷限,離開例子的脈絡就無法靈活運用。這種學習,不算真的學會。

***

結論

Specification and Example,Tell and Show,兩者相輔相成,缺一不可。

***

友藏內心獨白:我只有撿到貓。

2019年4月25日 星期四

Clean Architecture讓系統穩定演化

April 25 15:40~16:26


劇情

以下是許多開發團隊都經歷過的對話…

技術控員工:我們的系統架構太爛,我已經快受不了了。現在新技術那麼多,如果用了這些新技術所有問題都迎刃而解。我強烈建議,應該把系統砍掉重練

技術主管:我承認我們的系統已經改不太動,每次修改的成本越來越高,但我覺得我們應該重構系統而非重新撰寫系統。

技術控員工:理論上沒錯,但我們系統亂成一團,重構不一定比重寫要來得簡單啊,搞不好更花時間。現在新技術、新框架,解決我們的問題很快,我還是覺得應該要整個砍掉重練。

技術主管:根據我以前的經驗,整個砍掉重練風險很高。我們的系統至少目前可以動,這是無可取代的價值。我還是覺得重構比較保險,穩扎穩打。

技術控員工:可是我已經不想再看到這堆爛code了,有辦法你自己來重構看看。

技術主管:!@#!%^^#!E04…

***

不自覺的過程

技術控員工與技術主管各有道理,雖然一般來講重構比重寫要來得好,但有時候歷史包袱太大,很可能重寫的成本反而比重構要來得低。決策的困難點在於,兩種做法的成本很難量化之後加以比較,只能藉由經驗、資源、甚或是一時情緒衝動所決定。依據主管的經驗,重寫往往沒什麼好下場,只滿足技術控員工學習新技術的慾望,而忽略的重寫帶來的高風險。

如何讓系統在持續演進的過程中,又可以保持一定的穩定性,相信是很多軟體從業人員想要追求的目標。上禮拜四地震後Teddy跟指導教授聊天,聊到Alexander在《Notes on the Synthesis of Form》提到:「一個系統要能夠持續保持穩定,其自身的調適性一定要大於外在需求改變的頻率與強度,否則系統將會處於不穩定的狀態。」

在傳統社會中,因為文化的限制力道非常大,因此建築物的演化,往往只做小範圍的改變,除非發生文化劇烈改變(例如維新運動、被外族統治),否則設計者基本上只要遵循老祖宗的做法去設計建築即可。

此外,因為設計者(蓋房子的人)生活在其設計的建築物之內,所以只要建築物有任何不合適的地方,例如漏水、淹水、通風不良等,設計者立刻會著手修正。因此,系統會一直保持穩定的狀態。這種設計方法,叫做不自覺的過程(unselfconscious process),此過程會產生極其穩定的系統。

***

Clean Architecture所形成的文化

不自覺過程的原理,套用在軟體系統一樣成立。以一個套用Clean Architecture的系統為例,Clean Architecture好比傳統社會的文化,規定了系統階層(entity、use case、interface adapter、framework and driver、)、每個階層所負擔的責任、以及限制(相依性原則、跨層原則)。

在此文化之下,開發人員不再享有「我想怎麼胡搞瞎搞,就怎麼胡搞瞎搞的自由。」相依性原則嚴格限制了系統相依性方向,I/O層變成可隨意替換的一部分。框架不再是架構師關心的核心,domain model與use case才是主角。

開發人員透過持續重構讓軟體系統隨時維持在clean code、clean architecture的狀態,系統的調適性便可基本大於需求改變的頻率與強度,如此才可讓「需求改變主要與scope相關,與改變發生的時間點無關」,軟體就真的軟了起來。

你不喜歡React,想換成Angular,沒問題,把UI層抽換掉即可,它原本就是可插入替換的一部分。不喜歡MySQL想換成NoSQL資料庫,沒問題,換個database gateway即可。

***

結論

只要你的軟體系統的調適性大於需求改變所造成的殺傷力,你就不怕需求改變所造成的衝擊。就算改變會造成混亂,也可以在短時間內讓系統恢復穩定。

這不是開發人員一味只強調技術至上的觀點,而是一種支持快速交付商業價值的靈活性。

***

廣告

對於Clean Architecture + 領域驅動設計(Domain-Driven Design;DDD) + 測試驅動開發(Test-Driven Development;TDD)有興趣的鄉民, 歡迎參考泰迪軟體的【Clean Architecture這樣學就會了實作班】課程。

***

友藏內心獨白:限制讓開發變得更容易。

2019年4月1日 星期一

你還在寫程式?

April 01 00:34~01:03


對話

前一陣子前某公司HR和Teddy聯絡,想找Teddy去企業內訓上【Clean Architecture這樣學就會了實作班】…

HR:經過調查之後,公司內部想上這門課的人太多,因為預算關係我們想優先安排最需要的同仁來上課。

Teddy:這很合理。

HR:上完課程會有甚麼成效?每個人都可以應用在工作上嗎?

Teddy:Clean Architecture簡單來說,是一種可以擴充的插件式架構。對同仁而言,如果是舊專案,沒什麼特別理由是不需要改成Clean Architecture。但如果是新專案,以我的經驗Clean Architecture有很大的機會可以派上用場。

Teddy:我最近套Clean Architecture寫了一個看板系統,我覺得很有幫助。

HR:你還在寫程式?

Teddy:對啊,不能光說不練XD。

***

程式可以寫一輩子嗎?

以上這段和HR主管的對話,讓Teddy想起2014年寫的這篇文章〈程式可以寫一輩子嗎?〉。這個問題,從Teddy年輕時第一次被「質問」到現在,說實話自己內心並沒有100%肯定的答案。

每個人的情況都不同,Teddy自己是希望能夠寫程式寫一輩子。雖然現在泰迪軟體的工作性質不需要像以前當開發人員寫那麼多程式碼,但學習軟體新技術、設計課程教材,還是需要寫程式,不可能光讀書不寫code。

Kent Beck說:「If you stop coding, you stop learning.」Teddy非常喜歡這句話,coding的目的很多,透過寫程式學習,絕對是開發人員最主要的學習方式。

***

結論

身為程式設計師,有機會、有能力寫code,是很幸福的一件事,不應輕易放棄。

***

友藏內心獨白:寫程式可以用手也可以用口。

2019年3月29日 星期五

Scrum讓你家庭更幸福

March 29 07:06~08:08

▲畫面節錄自維基百科


家庭也是一個團隊

昨天到信義區對一群非資訊業的美女、帥哥主管介紹敏捷精神。談到Scrum團隊cross-functional team(跨職能團隊)的時候,Teddy開玩笑說…

Teddy:很多人家庭不幸福,因為他們的家庭採取專業分工的component team。小孩只負責做小孩該做的事,只要乖乖讀書就好,什麼家事都不用幫忙。爸爸只負責賺錢,回家一屁股坐下來,不是看電視等吃飯,就是打電動。媽媽最可憐,下班之後還要煮飯、做家事、顧小孩。

Teddy:這是一種「區域最佳化」的做法,每個人都扮演好自己的「專業」,感覺好像很有「生產力」,但卻沒有顧及整個家庭價值鏈的最佳化。

***

魔戒遠征隊

很多人對於導入Scrum從專業分工的component team轉換到交付價值的cross-functional team,一開始在觀念上無法接受。「我是一位UI/UX設計師,現在Scrum團隊中明明就沒有符合我專長的工作,那我要幹嘛?」

還記得電影魔戒中的魔戒遠征隊嗎?魔戒遠征隊有兩個特性和Scrum團隊很像:

  • 跨職能團隊:團隊中包含了完成任務所需要的各種人才,包含巫師、人類、矮人、精靈、哈比人,每個角色都有特定的專長。
  • 自組織:這個特性是到團隊組成中期之後才出現,一開始團隊沒有共同目標,有的人是想完成消滅魔戒的任務,有的人是陪鄰居出門郊遊、有的人是想要搶魔戒、有的人是老闆派來的、有的人是憂國憂民的先知。到後來大家確定共同目標就是要消滅魔戒,各自盡其所能來達成這個目標。

如果你是魔戒遠征隊的哈比人「山姆」,你原本的「專長」是負責煮飯。遇到強獸人來攻擊,你可以說:「我只負責煮飯,不負責打仗。你們慢慢打,我要繼續煮飯了」嗎?當然不會,此時煮更多的飯已經沒有意義,你會拿起短刀,在旁邊偷偷地刺啊、刺啊。就算不能對敵人造成致命攻擊,但總也是想盡辦法幫助團隊其他成員達成退敵的共同目標。

***

當作自己的事

Teddy覺得,敏捷講到底,也沒什麼大學問。就是好好過生活,認真做事,活在當下,去感受各種 forces(作用力、限制條件)。提升自我能力,靈活地面對這些 forces 做出反應,讓身處的環境回復到一個穩定的狀態,繼續面對新的 forces

很多人在公司跑敏捷,覺得痛苦、遇到阻礙,講到底也都是人的問題。從個人的角度來看,如果把手中的工作看成是「別人的事情」、「公司的事情」、「我只是領薪水的」,做事的動機、態度、積極性自然和「這是我自己的事」差很多。

但這也不能怪員工,因為很多時候公司的大環境、文化,迫使員工採取保守的做事心態,少做少錯,防弊重於興利。但無論如何,認真做事、認真過生活,不管公司待你如何,這種做人處事的態度,會跟著自己一輩子,別人是搶不走的。

反正跑不跑敏捷都要痛苦,還不如嘗試點新鮮的玩意,苦中作樂、苦中學習,也是一種不錯的選擇。

***

廣告

對於敏捷開發、Scrum有興趣的鄉民,可參考泰迪軟體的【Scrum敏捷方法實作班】課程,上課日期為4月27、28日(六、日)。

***

友藏內心獨白:小刀刺久了也是會有殺傷力的。

2019年3月26日 星期二

落實TDD的三個難題(下):技術能力的精進

March 26 07:37~08:53


難題三

落實TDD/ATTD/BDD/SBE最後的難題是技術能力的精進。TDD所需的技術能力,包含如何透過與領域專家緊密合作,找出代表規格的關鍵例子(key examples),再以軟體工具(Cucumber、SpecFlow、Robot Framework、xUnit等)逐一自動化這些例子(驗收測試)。

有了失敗的測試案例界定規格邊界(範圍),接下來便可撰寫production code實作這個規格。等待測試案例通過,代表某項規格已被實現,此時回頭思考程式碼是否可透過重構(refactoring)改善設計品質,讓軟體持續維持在軟的狀態。一個可閱讀、可維護、可測試、易修改、易部屬的軟體,才能給予團隊擁抱改變的力量。

遵循這個步驟,採取穩扎穩打、迭代與增量、價值驅動的方式,完成系統開發工作。

***

三個圈圈的交集

這系列三篇文章提到的三個主題:

三者看起來好像是各自獨立的議題,但實質上彼此緊密相關,互有增強效果。領域模型與軟體架構可促進團隊建立穩健的通用語言,讓後續開發工作變得更具體,更容易落實ubiquitous language in code。更強的技術能力,讓團隊與領域專家溝通時,能更精確找出代表需求的關鍵例子,以更準確、精練的語言,描述規格並將所獲的的知識以程式碼的形式紀錄下來。

舉個例子,當你準備採用最簡單的Rename重構程式中的變數、函數、類別、package名稱時,是否有著不知道如何命名的困擾?對於非英語系國家的人,可能直覺認為是自己的英文能力不夠好,所以不知道如何取名字。英文程度當然會影響取名字的能力,但有另一個更深層且不容易被察覺的問題,就是「團隊對於問題領域的重要概念或商業邏輯還沒有達成一定的共識,不知道用什麼名字來稱呼某件事情」,

另外一個常見的例子就是在沒有做任何分析的情況下,期望透過 TDD,在撰寫失敗的測試案例過程中就能夠直接找出類別必且定義它們的介面。這其實是一件困難的工作,「物件村」空無一人,你的失敗案例要呼叫誰來幫忙?如果可以做一點點事前設計,有了初始的領域模型,進一步甚至直接套用像是clean architecture這種通用軟體架構,可以讓後續實作自動化驗收測試的過程更加容易且比較不會長歪掉。

***

吃技術

Teddy的一位朋友在討論技術問題時很喜歡用「吃技術」這三個字:「TDD很吃技術、重構很吃技術、持續整合很吃技術、DDD很吃技術」。

軟體開發本身就是一件技術活,有什麼活動是「不吃技術」的嗎?只要基本功夫紮實,做起事來就跟喝開水一樣簡單,不會覺得做什麼都「吃技術」。

有不少人因為沒學過OOAD(物件導向分析與設計),或是學過OOAD但覺得OOAD好難,TDD好簡單,都不用作什麼分析、設計的工作,只要驗收測試寫出來,物件就自動冒出來,介面也設計好了。傑克,這真是太神奇了。

在此Teddy要告訴傑克:「這是不可能的」。能量不滅,原本OOAD所要做的工作,不會因為改用TDD就消失不見,只是以其他的形式呈現罷了。

想學好一件事,先弄清楚困難的地方在哪裡。知道地形地物、context之後,才不會人云亦云,才會有自己的看法,才能夠透過迭代與增量的方式累積層次而不至於長歪掉。

***

友藏內心獨白:做軟體的不吃技術難道要吃大便XD。

2019年3月22日 星期五

往外看一層

March 22 22:27~23:45


「這個我用不到」效應

幾年前有一次到某公司教【Design Patterns這樣學就會了–入門實作班】,課程結束後邀請我去的主管覺得還滿有幫助的,但他發現部門內一位小組長上課時興趣缺缺,於是主管主動詢問小組長對於課程的看法。

小組長告訴主管:「我工作上所需要的東西Microsoft .Net Framework都有了,不需要自己設計新東西,Design Patterns對我工作上沒什麼幫助。」

這位小組長說的也有他的道理,如果目前工作上用不到,學習的動機也就減低。顧好目前工作最重要,以後的事以後再說。他會出席這個課程,是因為主管要求,並不是因為他自己覺得有需要。

***

Teddy的VB經驗

N年前Teddy五專畢業後,在當兵前有三個月空檔,經過朋友介紹接了一個用VB 3.0寫Windows應用程式的案子。當時Teddy沒學過VB,擔心無法勝任。但朋友一再保證,VB非常、非常、非常簡單,一定沒問題。基於打工賺點小錢外加人情因素,於是就大膽接下這個案子。

做專案之後才發現朋友沒有欺騙Teddy,VB真的非常、非常、非常簡單。突然覺得自己好棒棒,英明神武,一下子就學會視窗程式開發,可以用VB打天下。

寫了一陣子之後,遇到第一個瓶頸:「為什麼別人的系統畫面(UI)那麼好看?我的卻那麼普通?」

原來VB有很多OCX元件,可以用拖拉方式讓自己的系統畫面變得非常「有質感」。從專案預算中花了一點小錢,買了幾個OCX元件。突破畫面關卡之後,再次覺得自己好棒棒,可以做出這麼有質感的視窗應用程式。

又過了一陣子,發現第二個瓶頸:「為什麼別人的視窗程式可以做出半透明與最小化之後縮到工具列的效果,而我卻只能作出標準的視窗?」

到處找書、看雜誌,終於發現原來這些效果要呼叫Windows API才作得到。知道這點之後好像發現新大陸一樣,透過Windows API可以做的事情太多了,功力大增。再次覺得自己好棒棒,可以做出這麼酷炫的視窗應用程式。VB + Windows API萬歲!

後來案子完成沒多久Teddy就去當兵了。

***

因緣際會之下,在當兵的兩年期間,第二年一整年都在部隊中寫VB程式。這時候沒遇到什麼技術上的問題,累積了更多VB開發經驗。

退伍之後上班,原本Teddy應徵這份工作是要用Java開發e-learning(網路教學)系統,但因為神秘的原因公司接了一個連鎖洗衣店門市系統的案子,結果又變成Teddy用VB練功的機會。

從VB 3.0用到VB 6.0,寫得程式越多,心情越不美麗。因為VB不是真正的物件導向語言(object-oriented language),只能算是object-based language。VB有Class,但卻不支援實作繼承。此時遭遇第三個瓶頸:「為什麼別人用C++/Java都可以開發真正的物件導向系統,而我只能用VB這種不物件導向的語言來寫程式?整個氣勢都弱掉了,一點都不酷啊。」

有一天讀到GoF的《Design Patterns》,被書中這兩句話震撼到:

  • Programming to an interface, not an implementation
  • Favor object composition over class inheritance

啊!原來實作繼承要少用,那麼VB沒有實作繼承這件事,好像就沒有那麼嚴重了。寫了好幾年的VB程式,居然此時才發現,VB其實是有「介面繼承(與Java的Interface類似)」的功能。透過介面繼承,你可以「programming to an interface」,也可以做多型(polymorphism)。

知道這點再度發現新大陸,後來用VB開發的新系統,物件導向程度到了前所未有的境界,自詡為VB OO之神…精病XD。

又過了一陣子,寫程式寫到手好痠。不是因為打字太多、太快,而是因為手動測試真的太累人。此時遇到第四個瓶頸:「有沒有什麼方法可以取代人工方式驗證程式的正確性,讓我的手不要那麼累啊?」

剛好這時候JUnit問市,原來這世界上還有一種東西叫做「自動化單元測試」。當時沒有免費的VB Unit可以使用,上網花了一點小錢買了一個付費版本。Teddy的第一個工作上實際使用的自動化單元測試,就貢獻給VB了。

從此之後,又發現另一個新大陸,哇,原來自動化單元測試可以節省這麼多手動測試以及debug的時間啊。傑克,真的是太神奇了。

***

往外看風景更美

學習與能力增強,是一種累積層次的過程。學會一樣工具或技術,自然很高興、有成就感。但如果就此自滿而停滯,你所能解決問題的範圍(context或scope)就會受限於這個工具或技術所設定的邊界。一直到有一天,你發現這項工具已經不能解決你的問題,這時候才想要找新的方法,很可能會緩不濟急,或拉長解決問題的時間。

Teddy的VB學習過程就到自動化單元測試為止嗎?當然不是,隨著公司人員增加,管理團隊、溝通的問題逐漸浮現。如何讓一群人一起有效合作開發軟體?這已經跳脫程式語言的層次,要往 外太空 更外層探詢答案。於是來到軟體開發流程、專案管理、需求探索、軟體架構這個世界繼續拓荒。

還有沒有更外層,當然有,繼續往外拓展,遇到公司經營、客戶滿意、產品獲利等問題。

很多時候,問題的答案不在你以為的「這一層」。往外看,視野更好,答案也呼之欲出。

***

友藏內心獨白:就連地獄都有18層了。

2019年3月18日 星期一

落實TDD的三個難題(中):通用語言的建立

March 18 17:02~18:11

▲廣義的說,TDD、ATDD、BDD、SBE都是同義字。


難題二

落實TDD/ATTD/BDD/SBE的第二個難題就是通用語言的建立。傳統TDD(先寫失敗的單元測試)好像只是開發人員自己的事情,它被降級為一種開發方式的選擇(Test First VS. Code First),一種技術議題,與商業無直接關係。

但後來的TDD,也就是冠上ATTD/BDD/SBE之後的TDD,強調透過開發團隊與領域專家的合作,以「舉例」的方式一起釐清需求與商業價值

這裡說的領域專家,可能是Scrum團隊的Product Owner,或是任何具備問題領域知識的stakeholders。這是一種頻繁、高互動性且非常燒腦的活動,而不是單方面由Product Owner寫好user story然後在sprint planning meeting「宣達聖旨」給團隊的那種溝通模式,更不是瀑布式開發那種「文件丟過牆」的溝通模式。

***

通用語言

通用語言(Ubiquitous Language)是領域驅動設計(Domain-Driven Design;DDD)所提出的觀念,意指「在一個特定領域中,所有人所固定使用的術語」。例如,在貓奴這個特定領域(bounded context),大家對於乾乾、濕濕、罐罐、浪浪、小橘、賓士、虎斑、三花、玳瑁、麒麟尾、鏟屎官、聖上、皇后、離胺酸、貓砂、逗貓棒、結紮、貓毛、掃地機器人等名詞有著高度共識。一群貓奴聚在一起,用他們的「通用語言」可以非常有效率溝通而且比較不會造成誤會

這個概念,應用在軟體開發上面,當團隊建立了良好的的通用語言,問題領域的知識可以直接出現在解決方案領域,商業人士可以直接和技術人員用相同的語言溝通,表達商業邏輯。更進一步,在實作軟體系統時,這個通用語言將直接反應在程式碼裡面,也就是ubiquitous language in code。如此一來,程式將變得更加容易理解與維護,也更容易擴充。這是一種讓軟體變軟,擁抱改變的做法

***

通用語言和TDD有什麼關係?

在〈落實TDD的三個難題(上):領域模型與軟體架構〉中Teddy提到建立領域模型對於TDD的重要性,而建立通用語言的過程,同時間幫助團隊建立領域模型,兩者相輔相成。

只要Teddy聽到有朋友「宣稱」採用TDD開發軟體,Teddy一定會問對方:「你們的需求如何產生(如何撰寫失敗的驗收測試)?」如果答案是「Product Owner寫好給我們照做」,那麼落實TDD的程度就還有不少改善的空間。

如果答案是「透過頻繁地與Product Owner溝通,討論列出需求中的重要例子(key examples)」,那就比單向的接受user story要好很多。如果答案是「透過與Product Owner以及stakeholder的討論,以舉例的方式,建立共同討論的語言,並以此為基礎來撰寫程式」,那就可以獲得一張「好棒棒」貼紙。

***

結論

TDD是透過先撰寫測試案例來釐清需求或規格,之後再考慮如何開發程式的一種設計方法。套用建築師Alexander的模式框架,這是一種「先決定Context,再決定Form」的設計方法。

為什麼網路上的TDD範例與TDD Kata你都做得嚇嚇叫,公司的專案做起來卻讓人很想睡覺?很簡單,因為前者具有定義清楚的Context,而後者的Context非常模糊。

一般來講,Product Owner與stakeholders擁有比較多的領域知識,換句話說他們比較了解Context。缺少這些角色的幫忙,開發人員很難獨自透過TDD的方式自行決定Context,這也是許多人在真實專案中落實TDD所遭遇到的困難。

在許多公司,Product Owner與stakeholders覺得「撰寫失敗的測試案例是開發人員的事」,人家不願意理你啊。

難怪你的TDD又變成XD了。

***

友藏內心獨白:不能放任開發人員自由發揮,結果會很恐怖。

2019年3月15日 星期五

落實TDD的三個難題(上):領域模型與軟體架構

March 15 07:11~09:11


緣起

幾年前有一位泰迪軟體忠實學員問Teddy:「我上過TDD的課,但回公司後卻不知道該怎麼落實,為什麼?」當時Teddy無法回答這個問題,因為:(1)他上的TDD課程不是Teddy教的,不知道他學了什麼;(2)Teddy以前工作上開發的軟體,只有不到10%是採用TDD,其他大部分都是用傳統OOAD,code first(先寫production code再寫test code)的方式,所以沒在工作上遇到全面落實TDD的問題。

雖然工作上TDD用的不多,但Teddy寫了很多測試案例,也做了持續整合,算一算也有16年的時間。這幾年因為教學需要,投資大量時間在TDD/BDD/SBE以及DDD/Clean Architecture上面,直到最近才慢慢有種可以清楚回答N年前這個問題的感覺。

***

例子太小

不少人都是透過網路上的例子或是TDD Kata來學習TDD,這些例子大多具備以下特點:

  • 所需物件很少:只有單一或少量物件、例如著名的Bowling Game Kata,只需要一個Game物件就搞定。
  • 商業邏輯明確:上述提到的Bowling Game Kata,或是計算不同方式的郵寄費用(平信、掛號、國內快捷、國際郵件)、商品費用(一般客戶、VIP、大量採購、特價商品)等例子,它們要解決的問題「商業邏輯」都非常明確,很容易透過TDD,採用逐次完成每一個例子的方式來實作完整商業邏輯。
  • 不須考慮架構:因為例子小,邏輯清楚,所以也不需要考慮軟體架構的問題。

以上特性,對於一個「以學習TDD為目的」的例子來說,原本都不是問題,反而是優點。因為例子很專注在特定的小問題上面,所以學習者可以在短時間內把握TDD的精神:

  • 寫一個失敗的測試案例
  • 用「最笨」的方式撰寫程式碼讓測試案例通過
  • 重構程式

***

問題在哪裡?

當使用者學了TDD要實際應用在工作上的專案,此時卻發現,實際要解決的問題放大了N倍。不只物件變多、商業邏輯複雜,連帶著軟體架構也需要一起考慮。這些都是在學習TDD階段沒有冒出來的因素(forces)。

奇怪,為什麼看別人TDD,只要透過撰寫測試案例,物件與介面好像信手拈來就有。換成我來TDD,就D不出來,最後只能XD。


▼如下圖所示,這個問題隨著驗收測試開發(ATTD)、行為驅動開發(BDD)、實例化規格(SBE)等方法,將原本傳統TDD「先撰寫失敗單元測試」提升為「先撰寫失敗驗收測試」之後稍有緩解。驗收測試提供TDD一個更大的Context (背景、脈絡),讓開發人員擁有更多的資訊來「隔空抓藥」,透過測試案例描述物件以及物件之間的互動關係。


▼Specification By Example範例


但就算有了驗收測試,整個系統的全貌還是無法清楚呈現,透過每次撰寫失敗測試案例來完成系統依舊屬於由下而上的開發方式。

由下而上的開發方式最大的問題就是:「最後兜出來的系統很容易長歪掉。」看到這裡鄉民們可能會想:「敏捷開發是一種迭代與增量的方法,這不也是一種由下而上的方式?難道敏捷開發也很容易長歪掉嗎?

沒錯,完全正確。這也是為什麼現在敏捷開發流行採用「影響力對照(impact mapping)」與「用戶故事對照(user story mapping)」協助團隊關照系統全貌。撰寫程式之前,不管這個程式是test code還是production code,如果對於所開發的系統缺少一種「整體的感覺」,最後的系統設計就很容易長歪掉。

***

怎麼辦?

其實答案很簡單,就是準備「剛剛好的事前設計」(just enough up-front design)。問題是怎麼拿捏這個「剛剛好」?

▼如下圖所示,對照OOAD與TDD/BDD/SBE的做法,後者少了強調「建立領域模型」(domain model)這個步驟,讓鄉民以為domain model裡面的物件,不需要特別分析與設計就會自然而然隨著撰寫失敗的測試案例而冒出來。就算是剛開始找到的物件不洽當,反正最後總是可以透過重構來改善設計品質。


能力強者如Kent Beck或Uncle Bob等級的人物,在腦海中已有某種 皇輿全覽圖「軟體全貌地圖」,因此可以信手拈來得到合適的領域物件。就算設計不小心歪掉,後續採用重構來改善系統設計品質對他們而言也不是難題。大師們只需極小化的事前設計便可順利透過TDD完成系統,但一般大眾畢竟敏捷性沒有那麼高,所以適量的事前設計有助於TDD。

扯了這麼久還是沒講到具體解法。以下是Teddy建議的方向:

  • OOAD:如果鄉民們學過OOAD,可以參考80-20原則。找出系統中20%最優先的use case或user story,花一點點時間地建立domain model。有了這個domain model,對於後續將「失敗測試案例」轉成test code會很有幫助。


▼cleanKanban系統的領域模型


  • DDD:參考領域驅動設計方法(Domain-Driven Design;DDD),建立domain model與通用語言(Ubiquitous Language)。有這兩項「致命武器」,後續不管你想「怎麼D」都可以得心應手。


▼透過事件風暴(event storming)找出領域事件與建立領域模型


  • Clean Architecture:軟體架構百百種,屬於「插件式架構」的Clean Architecture,因為具備高度擴充性與可測試性,很適合作為各種軟體的「預設架構」。搭配Clean Architecture,撰寫TDD的失敗驗收測試直接對應到呼叫Use Case,而Clean Architecture的Use Case有著固定的結構,需要定義清楚的Input與Output介面。也就是說,Clean Architecture限縮了開發者的「選擇性」,而讓TDD的開發的工作變得更簡單。

▼搭配Clean Architecture採用TDD開發所撰寫的失敗驗收測試。

***

結論

敏捷開發與TDD都不鼓勵大量事前設計(Big Up-Front Design;BUFD),但並不是說不需要任何事前設計直接「帶著鋼盔往前衝」就可以攻克敵軍山頭。合適的事前設計,可以幫助開發人員釐清目標,支撐後續迭代與增量式開發活動,讓整體設計慢慢湧現。

***

友藏內心獨白:緣分到了,問題就想通了。


廣告

對於Clean Architecture搭配TDD與Event Storming(事件風暴)有興趣的鄉民,可參考泰迪軟體的【Clean Architecture這樣學就會了實作班】,2019年4月份課程已確定開課。

2019年3月14日 星期四

無用之用

March 14 16:37~18:06


The Timeless Way of Building》這本書從2003年5月30日入手起算到現在已將近16年。剛到手時幾乎每天都在傻傻地讀,因為看不懂作者想要表達的真正意思,只能硬著頭皮念下去。這本書的英文用字與文法不算太難,而且有中文版可以對照著看,所以讀不懂的主要原因倒不是因為語言隔閡,而是無法理解作者背後的整體思想。作者的腦袋裡不知道裝什麼,真是太奇葩了XD。

有人可能會問:「念資工的幹嘛去讀建築的書?當然看不懂啊。」當初因為博士論文想研究設計模式,指導教授說:「要研究pattern,不能只看GoF 的《Design Patterns》這本書,要從源頭去研究pattern發明人Alexander的作品。

就因為這句話,Teddy立刻上Amazon買了《The Timeless Way of Building》、《A Pattern Language》以及《Notes on the Synthesis of Form》各兩本。一本自己讀,另一本給指導教授,「暗示」指導教授也要一起讀。總不能只有Teddy一人受苦啊 XD。

***

印象中,過了一段瞎子摸象的日子,後來慢慢地有點感覺,可以將書中的一些講法「硬套」在軟體開發上面,有種重見光明的感覺。

數年後,一直到博士班畢業,Teddy都還沒把這本書整本看懂。又過了幾年,泰迪軟體成立後因為要賺錢生存下來,在設計【Design Patterns這樣學就會了–入門實作班】教材時,Teddy特別在第一天的課程介紹Alexander的方法。幾年下來,「Design Patterns這樣學就會了–入門實作班」教了20幾次,不斷地修改教材內容,對這本書的體會又更深了一些。

***

五年多前Teddy開始讀《Domain-Driven Design》(DDD),一開始也搞不清楚DDD到底在搞什麼。表面上看起來,就是另一種物件導向分析設計(OOAD)的方法啊,但仔細一看,和傳統的OOAD味道又不一樣。那是個有點陌生但又熟悉的味道,耶,原來DDD是一種pattern language!作者Eric Evans很顯然也受到Alexander的影響。有了這層認識,再回頭看DDD的眼光就不一樣。不但看得更深入,也可以重複使用以前讀Alexander書本的那些知識。

這就是Teddy之前說的:「有九陽神功護體,學什麼功夫都快。

***

兩個多禮拜前Teddy因為要設計【Clean Architecture實作班】課程教材,用TDD/Specification By Example(SBE)加Clean Architecture加DDD的Event Storming、Ubiquitous Language與Aggregate等技術,幾乎「無痛地」很快就把範例做好。之前對於TDD/SEB有一些沒想清楚的地方,在無形之中居然豁然開朗。這並不是因為這兩個禮拜Teddy吃了天山雪蓮突然功力大增,而是之前下的功夫點點滴滴累積,剛好在這個時間點「因緣成熟」而豐收

如果沒讀過Alexander的書,也許這一切「好事」都不會發生。

感恩seafood、讚嘆seafood。

***

友藏內心獨白:還好在台灣這本書沒什麼人讀,少了很多競爭對手。

2019年3月12日 星期二

關於時間的測試

March 12 07:51~09:07


時光一逝永不回

程式中如果有使用到「時間」,對測試來說是一個很傷腦筋的問題。例如,在看板系統中,想測試一個工作項目(work item)的lead time(從開工到交貨的時間)或cycle time(在某個或某些工作階段停留的時間),如果採用手動測試,測試人員需要不斷修改電腦時間,以便模擬出工作項目停留在不同工作階段的狀況

在〈對付時好時壞的測試案例(5):Time〉Teddy曾經談過這個問題,今天要從程式碼的角度再談一次。

***

程式範例

為了製作「Clean Architecture實作班」課程範例,Teddy最近忙著開發一個看板系統軟體稱為cleanKanban。Teddy參考領域驅動設計(Domain-Driven Design;DDD)的作法,套用Aggregate設計模式。一個aggregate將一小群物件包裝在一起,最上層的物件稱為aggregate root,負責維持不變量(invariant)與交易一致性。

不同Aggregate之間的狀態透過領域事件(domain event)保持同步,下圖AbstractDomainEvent類別代表領域事件的抽象類別,其中occurredOn屬性紀錄事件發生的日期與時間。

上圖中AbstractDomainEvent類別直接透過 new Date()獲得日期物件,這種寫法要撰寫自動測試就很傷腦筋。

學過測試替身Test Double(1):什麼是測試替身?〉的鄉民應該會想到以下兩種解法:

  • 相依性注入技巧,AbstractDomainEvent不要自己產生Date物件,而是讓呼叫它的物件傳入。這種做法有兩個潛在問題:
    • 需要改程式,破壞了既有程式的介面。如果AbstractDomainEvent已經有很多子類別實作,異動到的程式就比較多。
    • 客戶端比較難用。很多領域物件(domain object)都會產生領域事件,代表事件產生時間的occurredOn屬性如果只是因為測試的原因需要外部注入,平添客戶端的麻煩。
  • 採用mock object,透過mock object在測試時直接攔截 new Date()呼叫,注入特定日期。這種做法不需要改程式,在測試案例上動手腳即可。詳細討論可參考stackoverflow的這篇文章

***

透過第三者

除了上述兩種解法,還有一種方式就是透過第三者得到時間物件。撰寫DateProvider類別,透過它間接獲得日期物件。DateProvider 允許使用者注入一個日期物件,在測試模式下可注入特定日期達到模擬不同日期的自動化測試目的。

▼修改AbstractDominEvent的建構函數,透過DateProvide.new() 傳回日期。


▼在測試案例中,將特定日期傳給DateProvider,可以模擬領域事件發生在特定日期的情況。

***

結論

今天談到關於日期測試的三種方法,各有優缺點,視不同情況可能都會派上用場。DateProvider的缺點是,萬一有人要搞破壞在production code裡面呼叫到DateProvider.setDate(),注入一個特定日期,系統狀態可能就整個錯掉。這個問題可以透過把DateProvider寫得更「精緻」一點,只允許在測試環境被注入日期來改善。

關於更多測試技巧,歡迎參考泰迪軟體的【單元測試這樣學就會了實作班】。

***


友藏內心獨白:有月光寶盒才可以穿梭時空。

2019年3月6日 星期三

最新課程:【Clean Architecture實作班】

March 06 14:00~16:20


今年泰迪軟體新規畫一門兩天課程:【Clean Architecture實作班】(簡潔架構實作班),這是去年「Clean Architecture嘴砲班」的進化版。今年的新課程,打嘴砲練練嘴上功夫還是必要的,此外增加了一個完整的實作練習,讓大家動動腦與練練手上功夫。

關於這個練習題目Teddy規劃了超過一整年,去年設計好的題目經過實驗發現整體來說有點太難。於是今年Teddy從新設計一個練習範例,除了學習Clean Architecture以外,還同時兼顧TDD/BDD/SBE,並借用DDD(領域驅動設計)的Ubiquitous Language(通用語言)Event Storming(事件風暴)AggregateEntity來幫助學員分析domain model(領域模型)的物件邊界。

今天簡短介紹一下課程範例。

***

看板系統需求

課程範例要開發一個看板系統(Kanban System),稱為cleanKanban。看板範例如下所示,由不同工作階段所組成,以下圖為例,有待辦事項、分析、實作、測試、可佈署等五個工作階段。

▲看板範例


每個工作階段還可以分為子工作階段,以上圖為例,待辦事項細分成「想法」與「Top 6」,分析階段細分成「進行中」與「完成」。

使用者將工作項目移入不同的工作階段,以視覺化方式追蹤工作進度。每個工作階段可以設定WIP上限(Work In Progress Limit),當工作項目已達WIP上限時,就不可以再拉入工作到該工作階段。

最後,系統要計算工作項目在每個工作階段所停留的時間,稱為Cycle Time,以及Lead Time

結論就是,cleanKanban系統要支援看板系統三原則:

  • 視覺化(工作流程)
  • 限制WIP
  • 管理工作流

***

測試驅動開發/行為驅動開發

Clean Architecture的重點之一就是將系統分層,如下圖所示,由內而外分別是:Entity(Domain Model)Use CaseInterface AdapterFramework and Driver

▲Clean Architecture,圖片來源在此


課程採取測試驅動開發(TDD/BDD/SBE)方式,首先寫出規則(Specification)例子(Example)


看到這裡鄉民們可能會想:「要用BDD或Specification by Example方法,需要使用Cucumber或SpecFlow工具嗎?

答案是:可以使用但先不要用工具之前,先練習用腦,以免在學習過程中被工具綁住。本練習只需要最簡單的xUnit單元測試工具即可 。


▲用JUnit撰寫第一個失敗的驗收測試


▲因為要套用Clean Architecture,最後的驗收測試長成這樣,直接呼叫use case。

***

事件風暴(Event Storming)

Clean Architecture非常重視領域模型的建立,如果有學過物件導向分析與設計(OOAD)的朋友,可以沿用原本OOAD的技巧來建立領域模型。在這門課中,Teddy將介紹在領域驅動設計很流行的事件風暴,來協助建立領域模型與通用語言。

***

專案結構

最後開發出來的專案結構,反應Clean Architecture的三大原則:

  • 分層原則
  • 相依性原則
  • 跨層原則

***

課程費用

  • 原價$22,000元。
  • 早鳥優惠: NT$18,900/人(2019年3月19日前報名並完成繳費。)
  • 2人團報:$17,900 元/人。
  • 嘴砲班舊生優惠:$9,900 元/人(僅限五名)。

課程網址:http://teddysoft.tw/courses/clean-architecture/

上課地點:台北市(近台北車站)。2019年4月19、20日(五、六),09:30-16:30,共12小時

image

***

友藏內心獨白:一個課程三種享受—Clean Architecture、TDD與DDD。