本章從本書的設計原則檢視幾個近幾十年流行的軟體開發趨勢與模式。

對每個趨勢,問:它真的能對抗大型系統的複雜性嗎?

物件導向程式設計與繼承#

OOP 是過去 30–40 年最重要的新概念之一,引入:

  • 類別、繼承
  • private 方法、實例變數

謹慎使用時這些機制能幫助設計(例如 private 確保資訊隱藏,外部無法依賴內部成員)。

介面繼承(Interface inheritance)#

  • 父類別只定義方法簽章,不實作
  • 各子類別自行實作
  • 例:I/O 介面,一個子類處理磁碟、一個子類處理 socket

介面繼承對抗複雜性:同一介面被重用於多種用途。

知識(例如怎麼用 I/O 介面讀寫磁碟)能轉移到其他問題(透過 socket 通訊)。

從深度來看:實作越多 → 介面越深——因為它必須抽出所有實作的共同本質、避開差異。

實作繼承(Implementation inheritance)#

  • 父類別不只定義簽章,也提供預設實作
  • 子類別可繼承或覆寫

實作繼承的初衷:降低變更放大——避免相同方法在多個子類重複。

但代價:

  • 父類別與每個子類別之間建立依賴
  • 父類別的 instance 變數常被父子兩端存取 → 階層內出現資訊洩漏
  • 改父類前可能要看所有子類;子類覆寫父類方法時又得看父類實作
  • 最壞情況:要改任一類,得對整個階層瞭如指掌
  • 大量使用實作繼承的階層往往複雜性極高

建議#

實作繼承應謹慎使用

  • 先考慮**組合(composition)**能不能達到同樣效果(例如用小 helper 類別實作共用功能,原類別建立在 helper 之上而非繼承父類)
  • 真的得用實作繼承,讓父類管理的狀態與子類管理的狀態分開:例如某些 instance 變數完全由父類方法管理,子類僅用唯讀方式(或透過父類方法)訪問——把資訊隱藏概念套用到階層內部

OOP 的機制幫助乾淨設計,但不保證乾淨。淺類別、複雜介面、暴露內部狀態,仍然會讓 OOP 系統極度複雜。

敏捷開發(Agile development)#

1990 年代末興起,2001 年正式定義;多半關於開發流程(組隊、排程、單元測試、客戶互動),不直接談設計。但其增量、迭代精神與本書相通。

第 1 章已說過:複雜系統初期難以完整看清,最佳設計來自逐步增量發展——這與敏捷一致。

風險:滑進戰術型#

敏捷讓開發者把焦點放在「功能」而非「抽象」,並鼓勵延後設計以盡快交付可運作的軟體。

部分敏捷支持者主張「先做最小特化的版本,需要時再 refactor 成通用」。這個論點有道理,但與投資心態相反,鼓勵更戰術型的風格 → 複雜性快速累積。

修正方向#

增量的單位應該是抽象,不是功能。

  • 在功能未需要某抽象前,可以暫時不想它
  • 一旦需要 → 投入時間乾淨地設計
  • 採第 6 章建議:把它做成稍微通用

單元測試(Unit tests)#

過去測試多半由獨立 QA 寫,現在已普及由開發者自己寫。

類型規模通常由誰寫環境
Unit tests小、聚焦在單一方法的某段開發者隔離環境,無需 production setup
System tests整個應用通常由 QA / 測試團隊production-like

對設計的幫助:促進重構#

單元測試在軟體設計中扮演重要角色——它們讓重構變得安全

沒測試 → 大規模結構變更危險 → 開發者保守,最小化每次變更 → 複雜性累積、設計錯誤無法被修。

有好測試 → 開發者敢做結構性改進 → 系統設計持續變好。

單元測試比系統測試覆蓋率更高,更可能抓出 bug。

範例:Tcl 換 byte-code 編譯器#

用 byte-code 編譯器取代直譯器是一次大改:

  • Tcl 有極佳的單元測試套件
  • alpha 釋出後只有一個 bug漏掉

測試驅動開發(TDD)#

TDD:寫程式前先寫測試,測試失敗 → 寫剛好讓它通過的程式碼 → 全部通過 → 類別完成。

作者支持單元測試,但不喜歡 TDD

問題:

  • 注意力放在「讓特定功能通過」,不是找最佳設計
  • 純戰術型,且太細粒度——隨時都被誘惑「再 hack 一下下一個測試」
  • 沒有明顯的設計時機,容易亂

同樣的精神:開發單位是抽象、不是功能

一旦發現需要某抽象,一次設計完整(至少完整到核心功能合理),不要分散在多次小迭代裡建構。

TDD 適合的場合:修 bug#

修 bug 之前先寫測試,讓它因 bug 失敗。

修完後跑測試 → 通過 → 確認真的修對。

若先修再寫測試,可能寫出觸發不了 bug 的測試,這時你不知道有沒有真的修好。

設計模式(Design Patterns)#

設計模式:解決常見問題的公認做法(iterator、observer 等)。GoF 那本《Design Patterns: Elements of Reusable Object-Oriented Software》讓它流行起來。

設計模式是「取代從零設計」的選項:能套用得當,多半比你重新設計來得好。

風險:過度套用#

不是每個問題都能用既有設計模式乾淨解決。

不要硬把問題塞進模式,當客製化做法更乾淨時。

「設計模式好」不代表「用愈多愈好」。

Getter / Setter#

Java 社群盛行的模式:每個 instance 變數搭一對 getFoo / setFoo

理論上的論點:方便日後增加副作用(更新相關值、通知 listener、強制限制),介面不必改。

雖然「萬一要暴露 instance 變數時用 getter / setter 是合理的**——但根本上就不該暴露 instance 變數**。

問題:

  • 暴露變數 = 暴露實作 = 違反資訊隱藏 + 增加介面複雜度
  • getter / setter 是淺方法(多半單行)→ 對介面只增雜訊,不增實質功能

設計模式被「應用得越多越好」的迷思害得最深的,就是 Java 的 getter / setter。

結語#

看到任何「新的軟體開發 paradigm」時,從複雜性的角度挑戰它:

這個提案真的能降低大型系統的複雜性嗎?

很多提案表面看來不錯,深入看就會發現它們讓複雜性變得更糟,而不是更好