第 3 / 11 部 待錄

資訊隱藏與通用模組

《A Philosophy of Software Design 軟體設計的哲學》Podcast 準備稿:資訊隱藏與通用模組

書名: 軟體設計的哲學 A Philosophy of Software Design 作者: John Ousterhout 系列: 旗艦評書(恩普拉氏) 公開標題建議: 評書|A Philosophy of Software Design.資訊隱藏:把秘密藏好,模組才會深 (3/11) 涵蓋範圍: 第5章 資訊隱藏與洩漏 + 第6章 通用模組更深

背景速覽

上一集講「模組要深」是目標,這一集講「怎麼把模組做深」的第一套手法——資訊隱藏。這個概念不是 Ousterhout 發明的,它來自 David Parnas 1971/1972 年的傳奇論文,是整個軟體模組化的祖師爺級智慧。Ousterhout 把它接地氣:每個模組都該藏住一些「設計決策」這種秘密,秘密藏得越多、介面就越簡單、模組就越深。反過來,當同一個秘密散落在好幾個模組裡,就是「資訊洩漏」——本書最重要的紅旗之一。後半段第6章再給一個反直覺的結論:把模組做得「稍微通用一點」,反而比為當下需求量身訂做更簡單、更深。

一句話重點

設計一個模組時,最該問的第一個問題是「我能在這裡藏住什麼秘密?」——藏得越多,介面越簡單、模組越深;而當你發現同一份知識(檔案格式、UI 概念、協定細節)出現在兩個以上的地方,那就是「資訊洩漏」的紅旗,要想辦法把它收攏到單一模組裡。

值得討論的重點

1. 資訊隱藏不等於加 private:getter/setter 把秘密又漏出去了

資訊隱藏就是把「設計決策」這種知識嵌進實作、不放上介面(B 樹怎麼平衡、TCP 怎麼跑、JSON 怎麼解析,呼叫端通通不必知道)。它從兩個方向砍複雜性:簡化介面、方便演進(TCP 加新壅塞控制,上層不用改)。但 Ousterhout 點破一個常見誤解:把變數設 private 不等於資訊隱藏——如果你又配一組 public getter/setter 把它的本質和用法整個露出來,那跟設 public 沒兩樣。藏的是「決策與機制」,不是「欄位的存取修飾字」。

2. 資訊洩漏與後門洩漏:本書最重要的紅旗之一

當同一個設計決策反映在多個模組,就是洩漏,它會在這些模組間綁出依賴——改一處就得改全部。兩種路徑:介面洩漏(資訊出現在介面上,定義即洩漏)和更陰險的後門洩漏(一個類別寫某檔案格式、另一個讀,介面都不露格式,但格式一改兩邊都得改)。處理心法是一句自問:「我能怎麼重組,讓這份知識只影響一個類別?」——可能是合併,也可能是抽出新類別(但抽出只有在能找到「簡單、抽離細節的介面」時才有效,否則只是把後門洩漏換成介面洩漏)。

3. 時序分解陷阱:別讓「執行順序」決定模組結構

這是洩漏最常見的成因,也最值得演。寫程式時,「先讀檔、再修改、再寫回」這個時間順序是腦中最強的線索,於是很多人就拆成讀/改/寫三個類別。問題是——讀和寫都得懂同一個檔案格式,洩漏了。Ousterhout 的金句:設計模組時,聚焦在「執行每項任務需要哪些知識」,而不是「任務發生的順序」。 HTTP 伺服器的學生作業是最好的案例:把「讀請求」和「解析請求」拆兩個類別,但 Content-Length 不解析就不知道請求多長——讀和解析根本分不開,合成一個類別反而更短更簡單。

4. 通用模組更深:為「當下功能、通用介面」設計

第6章給一個反直覺結論:模組做得「稍微通用」反而更簡單、更深——功能反映你當下的需求,但介面要夠通用、不被當下需求綁死。文字編輯器的案例是經典:

  • 專用做法:文字類別直接開 backspace(cursor)delete(cursor)deleteSelection(selection) 對應 UI 鍵——結果文字類別塞滿淺方法,UI 詞彙(selection、backspace)洩漏進文字類別,每加一個 UI 操作就要回頭改文字類別。
  • 通用做法:只要 insert(position, text)delete(start, end) 兩個基本操作,加一個 changePosition 工具。UI 端用這兩個基本操作組出 backspace/delete——而且更妙的是,「backspace 到底刪了哪些字元」這件 UI 真正關心的事,直接寫在 UI 程式碼裡、一目了然,不用繞到文字類別查。

而那個 backspace 正是「假抽象」的活標本:它假裝藏起「刪了什麼」,但 UI 偏偏需要知道——藏錯了東西,反而製造模糊。

注意事項

⚠️ 資訊隱藏會「過度應用」。Ousterhout 特別開了一節:如果某資訊模組外部真的需要(例如影響效能、不同用途要不同設定的參數),那就該露就露,硬藏只會製造假抽象。一句話濃縮:能藏就藏,該露的也得露。

⚠️ 「通用」也會走火入魔。「稍微」兩個字是重點——把文字類別做成「單字元 insert/delete」雖然又簡單又通用,但對編輯器超難用(到處是迴圈、效能差)。判準是「API 能不能輕鬆滿足當下需求」;要寫一堆額外 code 才能用,就是介面粒度錯了。通用不等於原始。

⚠️ 這集的「合併小類別、稍微做大一點反而更好」會跟下一輪聽眾腦中的《Clean Code》「單一職責、類別要小」直接打架。提醒聽眾:Ousterhout 不是反對職責清晰,他反對的是讓「時間順序」或「UI 操作」這種錯誤的線索去切模組——切錯了線,才會洩漏。

專家補充

💡 對照線在這集要接到 Parnas。Uncle Bob 的 SRP(單一職責原則)說「一個類別只該有一個改變的理由」,這跟 Ousterhout 的「資訊隱藏」其實系出同源——都來自 Parnas「按『會變的設計決策』來切模組」。差別在:SRP 容易被誤讀成『一個類別只做一件小事』(於是切太碎),而 Ousterhout 的資訊隱藏強調『把一個完整的秘密整包藏好』(於是傾向稍大、稍通用)。同一個祖宗,兩種詮釋,這正是藍圖 §5 想釐清的張力。

💡 HTTP 伺服器那個「讀和解析分不開」的例子,是反駁「分層越多越好」的鐵證。很多人以為「網路層」「解析層」分開很乾淨,但只要兩層共享同一份協定知識,分層就是洩漏。這對今天動不動就 N 層架構的服務端開發很有提醒意味。

💡 「假抽象(false abstraction)」這個詞值得單獨記住——它是「表面簡單、實際害人」的抽象。判斷法:使用者最後是不是還是得去讀你的實作才能用對? 是的話,你藏的東西藏錯了。這比「抽象好不好」更可操作。

討論問題

🎙️ 開場鉤子:「你一定遇過這種 bug——改了一個檔案格式,結果三個地方同時爆炸。Ousterhout 說,這不是你倒楣,這是一種有名字的設計病,叫『資訊洩漏』,而且它最愛躲在你『按步驟拆出來的類別』裡。」

🎙️ 自問自答:「為什麼把『讀檔、改檔、寫檔』拆成三個類別,聽起來這麼乾淨,做起來卻這麼痛?」——用「讀和寫都得懂同一個格式」一句點破:你是按時間順序切的,但知識不是按時間分布的。

🎙️ 帶走的一題:「找一個你最近寫的工具方法,它的名字是不是直接對應某個 UI 操作或某個業務步驟(像 backspacehandleCheckoutButton)?如果是,試著問:有沒有一個更基本、更通用的操作,能讓呼叫端自己組出它——而且組出來的 code 反而更看得懂?」

更大範圍關聯

  • 資訊隱藏的祖師爺 David Parnas:本集核心觀念直接承自 Parnas(1971/72)。這條線也是 Uncle Bob SRP 的源頭——對照 [軟體工程領域切分藍圖] §5「Clean 到底乾不乾淨」,可看清 SRP 被「切太碎」誤讀的來龍去脈。
  • 與《Clean Code》對撞:本集「稍微做大、稍微通用反而更深」正面挑戰「類別要小、單一職責」,建議搭配 Reading Uncle Bob 的相關集對聽。
  • API 設計傳統:「讓常見情況最簡單」「該露就露」呼應 Josh Bloch《Effective Java》與「最小驚訝原則」;HTTP 預設值的處理也是 REST/協定設計的日常課題。

錄製建議

  • 建議時長:約 24–26 分鐘(本集含兩章,內容偏厚)。配比:資訊隱藏+洩漏+時序分解約 12 分(HTTP 案例好演)、通用模組(文字編輯器案例)約 12 分。
  • 討論策略:上半場把「資訊洩漏」立成一個會抓人的紅旗,用「改格式三處爆炸」的痛感切入,再用時序分解解釋「為什麼你會不小心製造它」;下半場用文字編輯器的 backspace 案例,把「專用 vs 通用」演成一個具體可感的取捨,並在 backspace 是「假抽象」這裡收一個漂亮的洞見。
  • 適度點對照線即可(Parnas 到 SRP),主戰場留給上一集,本集把「秘密藏對地方」這個直覺講透就夠。