March 06 14:06~15:20
昨天談到「開三聯式發票」功能如果只用一個Invoice類別可能會違反單一責任原則進而形成Divergent Change怪味道,今天單純從物件導向設計或是modeling的角度來探討只用一個Invoice類別可能會有什麼問題。
用單元測試紀錄行為
▼先從簡單的開始,如果要開一張空白發票。
▼執行結果失敗,這個問題和設計無關,只是忘了設定Invoice類別的初始值。
▼把以下四個屬性初始值都設為0就可以了。
▼測試案例通過。
▼寫到這裡想到一個問題:「如果不呼叫issue函數,直接產生一個Invoice物件是否也等於開一張空白發票 ?」寫個測試案例試看看:
▼測試通過,驗證了剛剛的假設,開始有點懷疑issue函數的必要性。但這也可能只是空白發票的行為剛好一樣,至少還需要再寫一個測試案例反應非空白發票的行為才能確定。
***
非空白發票的行為
▼首先回顧一下原本已經有的單元測試:「提供含稅價格來開發票。」
▼接著試著把一張含稅價格為17000的發票,重設含稅價格為105,看看newInvoice物件是否會重新計算發票內容。
▼測試結果通過,得知如果要計算稅金與未稅價格只要重設含稅價格即可。因此更加確定在目前的設計中,issue函數應該是多餘的。
▼最後再寫一個測試案例告訴我們:「不需要透過issue函數開發票,每次重設含稅價格的時候就會重新計算一次發票。」
▼測試案例通過,issue函數真的是多餘的。
***
回到原點
不知道鄉民們是否還記得當初為什麼會設計issue函數?因為Scenario告訴我們使用者會觸發「開三聯式發票(issue a company invoice)」這個動作。
到目前為止Teddy討論了兩種實作這個動作的可能:
- 另外建立InvoiceBuilder類別,由它來產生一個不可變(immutable)的Invoice物件。
- 把issue函數設計在Invoice類別身上,讓它具有開發票的能力。
討論至此推理的過程雖然有點囉嗦,但鄉民們應該可以看出來,無論是從需求面(問題領域)或設計面(單一責任原則)來看,第1種方法感覺起來比較合適。不但反應了問題領域的行為,讓開出的發票無法修改也比較不會造成程式的問題。
***
軟體的可讀性、可修改性、可維護性,都是來自於這些點點滴滴的細節。對於初學者來說,很容易選擇第2種設計方式(用一個Invoice類別打通關)。雖然程式可以動,但付出的代價卻是降低可讀性、可修改性、可維護性。這個問題就算是開發團隊「宣稱」使用BDD/TDD,還是非常可能寫出「不乾淨」的程式。為什麼?因為對於初學者來說只考慮到「工法(BDD/TDD)」,卻沒留意這個工法的「初心」,也就是協助開發人員在不同的尺度上規範系統行為。沒有花腦筋與時間去探索系統行為,工法可以幫上忙的地方就相對有限。
***
友藏內心獨白:一不小心軟體很快就凝固變成硬體了。
沒有留言:
張貼留言