為什麼改一個東西要這麼久?#
在 legacy code 中,即使是最簡單的變更也可能花費很長時間。原因主要有兩個:
- 理解 (Understanding) – 搞清楚要改什麼就花了大半時間
- 延遲時間 (Lag Time) – 從做出變更到得到回饋之間的等待
Understanding(理解)#
隨著程式碼量的增長,理解的困難度會逐漸超越程式碼量本身。要花越來越多時間才能弄清楚應該改哪裡。
良好維護的系統 vs. Legacy 系統:
- 良好維護的系統:弄清楚要改哪裡可能需要一些時間,但一旦了解,修改本身通常很簡單,而且你會對系統更有信心
- Legacy 系統:不僅要花很長時間弄清楚該改什麼,修改本身也很困難,而且你可能覺得除了這次修改的狹窄範圍外,什麼也沒學到
將系統拆分成小的、命名良好、易於理解的部分,能讓工作更快。參考 Chapter 16 I Don’t Understand the Code Well Enough to Change It 和 Chapter 17 My Application Has No Structure。
Lag Time(延遲時間)#
Lag time 是指從做出變更到收到回饋之間的時間。
作者用火星探測車 Spirit 做比喻:從地球發信號到火星需要 7 分鐘,操作控制需要等 14 分鐘才能知道結果。這聽起來荒謬,但許多軟體開發團隊的工作方式本質上類似 – 做一堆修改、開始 build、然後等待結果。
在大多數主流語言中,你總是可以用打破依賴的方式,讓你正在修改的程式碼在 10 秒以內完成重新編譯和測試。如果團隊真的有動力,甚至可以壓縮到 5 秒以內。
快速回饋的影響#
當回饋循環壓縮到幾秒鐘:
- 心智狀態從「規劃後等待」變成「即時嘗試」
- 工作方式更像開車而非等公車
- 注意力更集中,因為不用不斷等待下一次機會
- 發現和修正錯誤所需的時間大幅縮短
Breaking Dependencies(打破依賴)#
依賴是阻礙快速開發的主要障礙。要讓 class 能獨立編譯和測試,必須打破不必要的依賴。
基本步驟#
- 嘗試在 test harness 中建立物件 – 遇到的問題通常就是需要打破的依賴
- 打破依賴後,檢查哪些東西仍然影響編譯時間
- 抽取 interface 來隔離 cluster 之間的依賴
Build Dependencies 範例#
假設有一組互相依賴的 class:
AddOpportunityFormHandler依賴ConsultantSchedulerDB和AddOpportunityXMLGeneratorConsultantSchedulerDB建立OpportunityItem
所有 class 都是 concrete(具體類別),互相緊密耦合。

Figure 7.1: Opportunity handling classes
步驟一:Extract Implementer
對 ConsultantSchedulerDB 使用 Extract Implementer,建立 interface:
<<interface>>
ConsultantSchedulerDB
^
|
ConsultantSchedulerDBImpl --> OpportunityItem現在 AddOpportunityFormHandler 依賴的是 interface。修改 ConsultantSchedulerDBImpl 時,AddOpportunityFormHandler 不需要重新編譯。

Figure 7.2: Extracting an implementer on ConsultantSchedulerDB
步驟二:進一步隔離
對 OpportunityItem 也使用 Extract Implementer:
<<interface>> <<interface>>
ConsultantSchedulerDB OpportunityItem
^ ^
| |
ConsultantSchedulerDBImpl --> OpportunityItemImpl現在 AddOpportunityFormHandler 完全不依賴任何具體實作。

Figure 7.3: Extracting an implementer on OpportunityItem
步驟三:拆分 Package
OpportunityProcessing DatabaseGateway
+ AddOpportunityFormHandler + ConsultantSchedulerDB (interface)
+ AddOpportunityXMLGenerator + OpportunityItem (interface)
^
DatabaseImplementation
+ ConsultantSchedulerDBImpl
+ OpportunityItemImpl
Figure 7.4: Refactored package structure
Dependency Inversion Principle(依賴反轉原則)
當你的程式碼依賴 interface 時,這個依賴通常非常輕微且不引人注意。你的程式碼不需要因為 interface 的實作改變而修改。依賴 interface 或 abstract class 比依賴具體 class 更好,因為你降低了特定變更觸發大規模重新編譯的機會。
測試也受益#
將測試放在對應的 package 中:
OpportunityProcessing
+ AddOpportunityFormHandler
+ AddOpportunityFormHandlerTest
- AddOpportunityXMLGenerator
- AddOpportunityXMLGeneratorTest
DatabaseImplementation
+ ConsultantSchedulerDBImpl
+ ConsultantSchedulerDBImplTest
+ OpportunityItemImpl
+ OpportunityItemImplTest對 AddOpportunityFormHandler 使用 Extract Interface 或 Extract Implementer,可以進一步讓其他 package 不受其修改影響。

Figure 7.5: Package structure
當你在設計中引入更多 interface 和 package 來打破依賴時,整體系統的 rebuild 時間會稍微增加(更多檔案需要編譯),但平均 build 時間會大幅下降,因為每次只需要重新編譯真正受影響的部分。
總結#
本章展示的技巧可以加速小群 class 的 build 時間,但這只是用 interface 和 package 管理依賴的冰山一角。打破依賴、將 class 拆分到不同 package 非常值得投資。一旦你有了可以獨立編譯和測試的小區域,你就能享受快速回饋帶來的所有好處。