第 2 / 11 部 待錄

模組應該深

《A Philosophy of Software Design 軟體設計的哲學》Podcast 準備稿:模組應該深

書名: 軟體設計的哲學 A Philosophy of Software Design 作者: John Ousterhout 系列: 旗艦評書(恩普拉氏) 公開標題建議: 評書|A Philosophy of Software Design.深模組:本書的核心武器,也是對 Uncle Bob 的正面開火 (2/11) 涵蓋範圍: 第4章 模組應該深(模組化設計、介面、抽象、深模組、淺模組、Classitis、Java vs Unix I/O)

背景速覽

如果整本書只能留一章,就是這一章。這是 Ousterhout 全書的核心武器——「深模組」(deep module),也是他跟《Clean Code》正面對撞的引爆點。前一集我們講了複雜性是大敵、要靠戰略型心態對付它;這一集回答的是「那具體要怎麼設計?」。Ousterhout 的答案濃縮成一句口號:模組應該是「深」的——簡單的介面,藏起強大的實作。而他會直接點名:今天業界流行的「類別要小、函式要短」,正在製造一堆「淺模組」,反而讓系統更複雜。

一句話重點

最好的模組,是介面比實作簡單得多的模組——大量功能藏在一道短短的介面之後(這叫「深」);反過來,介面相對於它提供的功能而言過於複雜的模組就是「淺」的,而小模組往往是淺的,這也是 Ousterhout 對「把類別拆小、把函式切短」這套主流教條最尖銳的質疑。

值得討論的重點

1. 模組 = 介面 + 實作,目標是讓「成本」小於「效益」

Ousterhout 把每個模組(class、function、subsystem、service 都算)拆成兩塊:介面(別人要用你必須知道的一切)和實作(兌現承諾的 code)。關鍵心法:在某模組工作的人,需要懂自己模組的介面與實作,加上他呼叫到的別人模組的介面——但不必懂別人的實作。好模組的判準就一句話:介面比實作簡單得多。這帶來兩個好處:對外造成的複雜性低、改實作不動介面時不波及他人。

2. 介面不只是 signature——非正式部分往往更大

很多人以為「介面」就是方法簽章。Ousterhout 提醒:介面有正式部分(簽章、型別,編譯器能檢查)和非正式部分(高階行為、使用約束,例如「A 必須先於 B 呼叫」,只能靠註解講)。而對大多數介面來說,非正式部分比正式部分更龐大、更難。判準很簡單:只要開發者「需要知道它才能正確使用」,它就是介面的一部分——這直接呼應上一集的「未知的未知」。

3. 深模組的兩座神主牌:Unix I/O 與垃圾回收

這兩個例子一定要講,它們是「深」的活教材:

  • Unix I/O:只有五個系統呼叫(open / read / write / lseek / close),簽章極簡;底下卻是數十萬行——磁碟佈局、目錄解析、權限、快取、併發排程……全部對呼叫端隱形。更驚人的是:實作這幾十年劇烈演化,那五個介面從沒變過
  • 垃圾回收:介面是——它在背景默默運作。加入 GC 不但沒擴大介面,反而縮小了系統介面(把「手動釋放物件」這個介面整個拿掉了)。

這兩個是「容易使用 + 隱藏巨大複雜性」的完美示範。

4. 淺模組與 Classitis:本書對「小就是美」的正面開火

反面就是淺模組——介面相對於功能太複雜。Ousterhout 舉了那個經典的學生反例:

private void addNullValueForAttribute(String attribute) {
    data.put(attribute, null);
}

這方法沒提供任何抽象:思考介面的力氣約等於思考實作,文件比 code 還長,連敲鍵盤次數都比直接寫 data.put(...) 多——它引入了「要學一個新介面」的成本,卻沒有任何補償。Ousterhout 把「類別越多越好」的迷思命名為 classitis(類別過敏):每個小類別都很簡單,但它們各自的介面加總起來,形成系統層級的巨大複雜度,外加滿地的 boilerplate。

Java 類別庫就是 classitis 的頭號展品:讀一個序列化物件要疊三個 stream(FileInputStream 包 BufferedInputStream 包 ObjectInputStream),最糟的是 buffering 要呼叫端自己記得包,忘了就慢到爆。Ousterhout 由此提煉出一條金律:介面設計要讓「常見情況」盡可能簡單——幾乎人人都要 buffering,就該設成預設;極少數不要的人,再去找關掉的開關。對比 Unix:循序 I/O 是常見情況,就設成預設,只做循序的人根本不必知道 lseek 存在。

注意事項

⚠️ 「深模組好、淺模組壞」不能推到極端變成「方法越長越好、別拆」。Ousterhout 不是反對拆分,他反對的是為拆而拆——拆出來的東西如果介面比省下的實作還複雜,就是淨虧損。判準永遠是「拆完之後,整體的介面複雜度有沒有變小」,不是「類別有沒有變多」。

⚠️ 也要替 Uncle Bob 講句公道話:「小函式、單一職責」在某些抽象層確實對——尤其是讀起來像散文、一個函式一個意圖的業務邏輯層。兩派其實常常在不同層級各自成立:Ousterhout 關心的是模組/類別這層的介面成本,Uncle Bob 關心的是函式這層的可讀性。聽眾不必選邊站隊,而是學會「在哪一層該聽誰的」。

⚠️ linked list 那種「本來就藏不了多少東西」的淺類別,Ousterhout 自己也承認有時不可避免。淺不一定是罪,只是它對「管理複雜性」這個目的幫不上忙——別把「淺」直接等同於「爛」。

專家補充

💡 這一集是整季對照線的主戰場,值得花時間把對撞講透。Uncle Bob《Clean Code》第三章主張「函式要小、再小、只做一件事」;Ousterhout 直接說這套做過頭會製造淺模組與爆炸的介面成本。但別把它演成「誰對誰錯」——更精準的講法是:Uncle Bob 優化的是「讀單一函式的瞬間體驗」,Ousterhout 優化的是「用整個模組、改整個系統的長期成本」。兩個都是真的痛點,只是站的距離不同。這正是藍圖 §5「Clean 到底乾不乾淨」要回答的核心。

💡 「深模組」這個詞雖然是 Ousterhout 發明的,但骨子裡就是 David Parnas 1972 年那篇傳奇論文〈On the Criteria To Be Used in Decomposing Systems into Modules〉的「資訊隱藏」原則的現代包裝——下一集(資訊隱藏與洩漏)會正式接到 Parnas。可以預告:深模組不是新發明,是把一個五十年的老智慧,重新講成今天工程師聽得懂的話。

💡 那條「讓常見情況簡單」的金律,其實是 API 設計的通用真理,在很多地方都有回音:好的 library 應該「easy things easy, hard things possible」。Java stream 的反例至今還在荼毒新手,是個百講不厭的活教材。

討論問題

🎙️ 開場鉤子:「業界現在最流行的一句話是『把類別拆小、把函式切短』。今天這位史丹佛教授要告訴你:這句話做過頭,正是你的系統越來越難維護的元兇。他甚至發明了一個病名叫它——classitis,類別過敏。」

🎙️ 自問自答:「一個只有一行 data.put(attribute, null) 的方法,到底幫了你什麼?」——帶聽眾算一筆帳:它要不要寫文件?文件比 code 長不長?呼叫端要不要學它?學完有沒有省到力氣?算完你就懂什麼叫淨虧損。

🎙️ 帶走的一題:「打開你最近寫的一個小工具類別,數一數它的 public 方法。再問自己:如果我把這些功能塞進別人已有的一個深模組裡,整個系統的『介面總量』會變多還是變少?」

更大範圍關聯

  • 與《Clean Code》的正面對撞:這集是整季對照組的核心戰場,直接回應 [軟體工程領域切分藍圖] §5「Clean 到底乾不乾淨」——小函式小類別究竟是良藥還是病。建議搭配 Reading Uncle Bob 的「函式」那一集對著聽。
  • 資訊隱藏的祖師爺:「深模組」承接 David Parnas(1972)的資訊隱藏原則,下一集會正式展開;也與《人月神話》Brooks 對「概念完整性」的執念同源。
  • API 設計傳統:「讓常見情況簡單」呼應 Josh Bloch《Effective Java》的 API 設計準則、以及「最小驚訝原則」——軟工三經典之外,這條是所有寫 library 的人的共同戒律。

錄製建議

  • 建議時長:約 22–25 分鐘。配比:模組/介面/抽象基礎約 7 分、深模組(Unix I/O + GC)約 8 分、淺模組+Classitis+Java 反例約 10 分(這段是本集高潮,也是對照線主戰場)。
  • 討論策略:先把「介面 vs 實作、深 vs 淺」這把尺立清楚;中段用 Unix I/O 和 GC 兩個神主牌讓「深」具體可感;後段把 Java stream 反例和 classitis 演成「業界流行做法的反思」,並在這裡正式點燃跟 Uncle Bob 的對照,但收在「不同抽象層各有道理」而非「誰勝誰負」。
  • 提醒聽眾:這是整本書的軸心,後面講資訊隱藏、把複雜性往下拉、合併分離,全都是「怎麼把模組做得更深」的不同手法。