Ousterhout 的核心命題:好模組是「深的」——介面小,功能多。模組的價值 ≈ 它隱藏的複雜度(功能)÷ 它強加給使用者的複雜度(介面)。介面窄、實作厚,這個比值才高。
🧠 Why It's a Problem
- 介面就是成本:使用者要學的、要記的、會被它牽動的,全是介面。介面越大,認知負荷越高。
- 淺模組是負擔:如果一個模組的介面幾乎和它的實作一樣複雜,它沒有真正隱藏任何東西,只是多了一層要維護的轉手。
- 「分得更小」不一定更好:把系統切成一堆淺而瑣碎的類別,會製造大量介面與跨模組依賴,整體複雜度反而上升——這是過度拆分的常見誤區。
- 資訊隱藏是手段:深模組之所以可能,是因為它把設計決策(資料結構、演算法、格式)藏在介面背後,讓改動不外溢。
NOTE
經典反例是 Java I/O:要開一個帶緩衝的檔案串流得 new BufferedInputStream(new FileInputStream(path))——緩衝這個「幾乎人人都要」的細節被暴露在介面上。Unix 的 open / read / write / close 則相反:五個簡單呼叫,背後藏著排程、快取、權限、裝置驅動的巨量複雜度。那就是深。
⚖️ 設計對照(深 vs. 淺)
淺模組 — 介面幾乎等於實作,沒藏住複雜度
// 介面把每個內部步驟都攤開,呼叫端得自己編排、自己懂順序
class TextProcessor {
splitIntoLines(text) { /* ... */ }
tokenizeLine(line) { /* ... */ }
stripWhitespace(tokens) { /* ... */ }
countTokens(tokens) { /* ... */ }
}
// 呼叫端被迫了解全部內部結構
const p = new TextProcessor();
const lines = p.splitIntoLines(text);
const all = lines.flatMap(l => p.stripWhitespace(p.tokenizeLine(l)));
const total = p.countTokens(all);四個方法、四個概念,使用者扛下全部編排責任。模組沒有替任何人省事。
深模組 — 窄介面藏住厚實作
// 介面只有一個意圖;分行、tokenize、清理、計數全藏在裡面
class TextProcessor {
countWords(text) {
return text
.split('\n')
.flatMap(line => this.#tokenize(line))
.filter(Boolean).length;
}
#tokenize(line) { /* 私有:使用者永遠不需要知道 */ }
}
const total = new TextProcessor().countWords(text);一個方法、一個概念。內部要換演算法、加 Unicode 處理、改格式,呼叫端一行都不用動——複雜度被關在門後。
TIP
設計時反覆問:這個介面有沒有把複雜度真的藏起來,還是只是把它轉手給呼叫端? 若是後者,這個抽象不值得它的成本。
🔑 Takeaways
- 模組價值 ≈ 隱藏的複雜度 ÷ 暴露的介面;追求「窄介面、厚實作」。
- 介面是使用者必須付出的認知成本——能藏的細節就別放上介面(如 Java I/O 的緩衝)。
- 「拆得更細」不等於更簡單;一堆淺模組會用介面數與依賴把複雜度加回來。
- 資訊隱藏是讓模組變深的工具:把設計決策關在門後,改動才不外溢。
No notes yet — jot your takeaways or Q&A here.