本章待辦清單#
$5 + 10 CHF = $10(匯率 2:1)$5 + $5 = $10Return Money from $5 + $5Bank.reduce(Money)Reduce Money with conversionReduce(Bank, String)- Sum.plus
- Expression.times
實作 Sum.plus()#
測試用例:
public void testSumPlusMoney() {
Expression fiveBucks = Money.dollar(5);
Expression tenFrancs = Money.franc(10);
Bank bank = new Bank();
bank.addRate("CHF", "USD", 2);
Expression sum = new Sum(fiveBucks, tenFrancs).plus(fiveBucks);
Money result = bank.reduce(sum, "USD");
assertEquals(Money.dollar(15), result);
}補充: 這裡刻意用
new Sum(...)而非fiveBucks.plus(tenFrancs)來建立 Sum,是為了讓測試意圖更直接明確。測試不只是讓程式能跑,更是寫給未來讀者看的文件。
實作非常簡單,和 Money.plus() 的邏輯完全一樣:
public Expression plus(Expression addend) {
return new Sum(this, addend);
}技巧: Sum.plus() 和 Money.plus() 的程式碼完全相同,暗示未來可以抽取到共同的抽象類別中。
TDD 的經濟學#
作者指出:使用 TDD 時,測試程式碼和正式程式碼的行數大致相當。要讓 TDD 在經濟上合理,你需要:
- 每天寫出兩倍的程式碼量,或
- 用一半的程式碼量完成相同功能
在評估時,別忘了把除錯、整合、解釋的時間也算進去。
實作 Sum.times()#
一旦 Sum.times() 能運作,在 Expression 介面宣告 times() 就只是簡單一步。
測試用例:
public void testSumTimes() {
Expression fiveBucks = Money.dollar(5);
Expression tenFrancs = Money.franc(10);
Bank bank = new Bank();
bank.addRate("CHF", "USD", 2);
Expression sum = new Sum(fiveBucks, tenFrancs).times(2);
Money result = bank.reduce(sum, "USD");
assertEquals(Money.dollar(20), result);
}Sum.times() 的實作——對兩個加數分別做 times:
Expression times(int multiplier) {
return new Sum(augend.times(multiplier), addend.times(multiplier));
}因為上一章已把 augend 和 addend 抽象為 Expression,現在必須在 Expression 介面宣告 times():
Expression times(int multiplier);這迫使 Money.times() 和 Sum.times() 都改為 public:
// Sum
public Expression times(int multiplier) {
return new Sum(augend.times(multiplier), addend.times(multiplier));
}
// Money
public Expression times(int multiplier) {
return new Money(amount * multiplier, currency);
}測試通過。
更新後的待辦清單#
$5 + 10 CHF = $10(匯率 2:1)$5 + $5 = $10Return Money from $5 + $5Bank.reduce(Money)Reduce Money with conversionReduce(Bank, String)Sum.plusExpression.times
所有項目完成!
嘗試性實驗:$5 + $5 回傳 Money#
最後一個鬆散的想法:當兩個同幣種相加時,能否直接回傳 Money 而非 Sum?
public void testPlusSameCurrencyReturnsMoney() {
Expression sum = Money.dollar(1).plus(Money.dollar(1));
assertTrue(sum instanceof Money);
}注意: 這個測試不太好,因為它在測試實作細節(
instanceof)而非外部可見行為。
檢視需要修改的程式碼:
public Expression plus(Expression addend) {
return new Sum(this, addend);
}沒有明顯、乾淨的方法能在 addend 是 Money 時檢查其幣種。實驗失敗,刪除測試,繼續前進。
本章回顧#
- 為未來讀者寫測試:測試是活文件,要讓意圖清楚
- 建議嘗試 TDD 與你現有開發方式的對比實驗
- 再一次跟隨編譯器的連鎖反應:改了宣告,編譯器會告訴你下一步該改什麼
- 進行短暫實驗,失敗就果斷放棄:不要硬撐不好的方向