《A Philosophy of Software Design 軟體設計的哲學》Podcast 準備稿:註解寫不顯而易見的事
書名: 軟體設計的哲學 A Philosophy of Software Design 作者: John Ousterhout 系列: 旗艦評書(恩普拉氏) 公開標題建議: 評書|A Philosophy of Software Design.好註解的完整方法論:別重複程式碼,寫它看不出的事 (8/11) 涵蓋範圍: 第13章 註解寫不顯而易見的事(全書最厚一章)
背景速覽
這是全書篇幅最厚的一章,所以單獨成一集。上一集 Ousterhout 已經駁倒「不用寫註解」,這一集回答「那到底怎麼寫」。他給的不是「多寫註解」這種廢話,而是一套有判準、有反例、有改寫範例的完整方法論。核心就一句:註解要寫程式碼裡看不出來的東西——重複程式碼的註解是垃圾,比程式碼更低層(補精確度)或更高層(補直覺與意圖)的註解才有價值。這一章也是整本書「可操作性」最強的一章,幾乎每個論點都配一組「壞版對好版」的真實程式碼對照,非常適合 podcast 一條條演。
一句話重點
好註解的鐵律只有一條——寫程式碼看不出來的事:跟程式碼同層次的註解(把名字翻成句子、逐行翻譯)全是垃圾;真正有用的註解要嘛比程式碼更精確(補單位、邊界、誰負責釋放、不變量),要嘛比程式碼更抽象(一句話講清整段在幹嘛、為什麼會走到這),而判斷你抽象好不好的最快試紙,就是「描述它的註解能不能寫得又短又完整」。
值得討論的重點
1. 選定慣例,並決定「什麼一定要寫」
第一步是定慣例:用語言的文件工具(Java 用 Javadoc、C++ 用 Doxygen、Go 用 godoc),慣例帶來一致性、也督促你真的去寫。Ousterhout 給了清楚的「必寫清單」:每個類別要有介面註解、每個類別變數要有註解、每個方法要有介面註解。他甚至建議:與其每次糾結「這個需不需要寫」,不如全部寫——通常更省力、更可靠。註解分四類:介面註解(給使用者)、資料結構成員註解、實作註解(給維護者,多數方法不需要)、跨模組註解(很少寫但需要時極重要)。
2. 別重複程式碼——最常見的爛註解
這是本章火力最集中的地方。大量無用註解的病根,就是它們重複了旁邊的程式碼:逐行加翻譯(# Get pointer copy 配 get_copy(obj))、什麼都沒講(// Add a horizontal scroll bar)、最陰險的是把名字翻成句子(getNormalizedResourceNames 配「Obtain a normalized resource name」,整句唯一不在 code 裡的字只有那個介系詞「to」)。自我檢查法超實用:「一個沒看過這段 code 的人,能光看旁邊 code 就寫出這條註解嗎?」 能的話,這註解沒用。紅旗的徵兆:註解用了跟被描述對象一樣的字眼。而這些爛註解往往漏掉真正該補的——什麼是 normalized resource name?padding 的單位是什麼、是一邊還兩邊?金句:用跟名字不一樣的字寫註解。
3. 兩個方向補強:底層加精確、高層加直覺
註解能在不同細節層次補強 code,同層次=重複、所以要嘛往下、要嘛往上:
- 底層註解/加精確度:最適合用在變數宣告。名字和型別通常不夠精確,註解補:單位?邊界 inclusive 還 exclusive?允許 null 嗎、null 代表什麼?這個資源誰負責 free/close?有沒有不變量(這 list 永遠至少一個元素)?另一條金律:描述變數時想「名詞」不想「動詞」——寫它「代表什麼」,不是「怎麼被操作」。把「heartbeat 收到時設 true、reset 時設 false」改成「true 表示自上次 reset 以來收到過 heartbeat」,更短、更有用,讀者自己就能推出怎麼操作。
- 高層註解/加直覺:省略細節、講整段的意圖與結構。把一段十行的迴圈條件,濃縮成一句「Try to append the current key hash onto an existing RPC…」,讀者光看這句就能解釋大半程式碼。寫高層註解比低層難,因為要換個角度問自己:這段 code 想做什麼?能用最簡單的話總結嗎?最重要的事是什麼?——這正是抽象的本質,也是偉大設計者和普通工程師的分水嶺。特別有價值的一種:「我們是怎麼走到這的」(這個分支在什麼情況下會進來、這個方法在什麼情況下被呼叫),往往比描述方法本身更值錢。
4. 介面註解 vs 實作註解:必須分開,而且內容要不一樣
這是本章最重要的設計洞見。介面註解寫「使用者要知道什麼」(行為、每個參數與回傳值、副作用、例外、前置條件),實作註解寫「內部怎麼運作」。兩者必須分開(使用者不該被迫看實作),而且內容應該不一樣——如果介面註解必須描述實作,那這個類別/方法就太淺了。所以「寫註解這件事本身」就提供了「設計品質」的線索(這條會在下一集 EP9 的「先寫註解」全面展開)。對應的紅旗叫「實作文件汙染介面」:介面註解裡寫了使用者不需要知道的實作細節(書裡 IndexLookup 那段把 DCFT、rule-based、內部 RPC 名全寫進介面註解,就是反例)。
5. 實作註解寫 what 與 why;跨模組決策放對地方
實作註解的目的是幫讀者懂「在做什麼(what)」而非「怎麼做(how)」——懂了 what,how 通常自然就懂。長方法分階段、每段前加一句高層說明(// Phase 1: ...)。另外也常用來寫為什麼:bug 修正引入的不明顯 code,註解說明為何需要(甚至連到 bug 系統 Fixes RAM-436,避免重複)。最後是最難的「跨模組設計決策」——理想上每個決策封裝在一個類別,但網路協定這種天生跨多模組的決策怎麼辦?兩招:放在開發者自然會打開的中央位置(RAMCloud 在 Status enum 結尾寫「加新值要連動改這 7 個地方」,因為加值的人一定會打開 enum);找不到自然位置時,用中央 designNotes 檔分節寫,各處放短註解 // See "Zombies" in designNotes 指過去。沒有完美解,關鍵是選一個方法、建立團隊去找它的習慣。
注意事項
⚠️ 「別重複程式碼」別矯枉過正成「能從 code 看出的一律不寫」。Ousterhout 的「程式碼」指的是註解旁邊那段 code,不是「整個應用所有 code」。有些變數的不變量理論上能從所有使用處推出來,但那既慢又易錯,在宣告處寫清楚仍然值得。判準是「旁邊看得出嗎」,不是「全專案翻得到嗎」。
⚠️ 「全部都寫」這條建議要帶語境。Ousterhout 是說「與其糾結需不需要、不如都寫」,但他同時承認 getter/setter 這種偶爾真的明顯到沒什麼好加。重點是降低判斷成本、避免漏寫,不是逼你給 i++ 寫三行詩。對照「別重複程式碼」一起講,聽眾才不會以為大師自相矛盾。
⚠️ 跨模組註解那兩招都有缺點,講的時候要平衡:「中央位置」要靠運氣有個自然位置、「designNotes 檔」則離 code 遠、容易和 code 演化脫節。Ousterhout 自己都說這是「試驗中的做法、沒有完美解」。別把它講成銀彈。
專家補充
💡 這一章其實是本季對照線上「Ousterhout 比 Uncle Bob 更細緻」的一面。Uncle Bob《Clean Code》第四章「註解」整體偏負面(「註解是必要之惡」「好註解是想辦法不寫的註解」),列了一堆「壞註解」類型。Ousterhout 不否認那些壞註解,但他正面建立了一套『好註解』的方法論——把名字翻成句子是垃圾(兩人同意),但「介面與實作分開、底層加精確、高層加直覺、寫 rationale」是 Uncle Bob 著墨較少的建設性部分。Uncle Bob 教你刪爛註解,Ousterhout 教你寫好註解。 這是本季「對照而非對立」最好的示範之一,呼應藍圖 §5。
💡 「介面註解必須描述實作,就代表類別太淺」這個診斷法極其強大,值得單獨記住。它把「文件」和「設計」綁在一起:你寫不出乾淨的介面註解,不是你文筆差,是你的設計有問題。這條會在下一集「先寫註解」變成一個更激進的主張——用寫註解來驅動設計。
💡 RAMCloud Status enum 那招(把「加新值的連動清單」寫在開發者一定會打開的地方)是「把文件放在會被看到的位置」這個更大原則的精華。它跟「資訊隱藏」是一體兩面:資訊該藏的藏,該被看到的文件就要放在動線上。這對任何維護過「有 N 個地方要同步改」的人都極有共鳴。
討論問題
🎙️ 開場鉤子:「你一定看過這種註解——// 把 x 加一 配 x++。它不只沒用,還浪費你讀它的時間。今天這集要回答一個更難的問題:如果同層次的註解是垃圾,那什麼樣的註解才真的有價值?」
🎙️ 自問自答:「怎麼一秒判斷一條註解是不是廢話?」——給出那個檢查法:「一個沒看過這段 code 的人,能光看旁邊的 code 就寫出這條註解嗎?」能的話,刪掉。
🎙️ 帶走的一題:「打開你最近寫的一個類別,看它的介面註解。問自己一個狠問題:這段註解裡,有沒有在描述『內部怎麼實作』的句子?如果有——那不是註解問題,那是 Ousterhout 在告訴你:你這個類別,可能太淺了。」
更大範圍關聯
- 與《Clean Code》的建設性對照:本集對應 Uncle Bob 第四章「註解」——同樣鄙視「重複 code 的註解」,但 Ousterhout 正面建立了好註解方法論(介面/實作分開、補精確、補直覺、寫 why),是「對照而非對立」的範例,延伸 [軟體工程領域切分藍圖] §5。
- 文件工程傳統:「介面註解=抽象」呼應 Knuth 的 Literate Programming、以及 Javadoc/Doxygen/godoc 的文件即契約理念;「跨模組決策放對地方」接到 ADR(Architecture Decision Records)的當代實踐。
- 設計與文件的耦合:「寫不出乾淨介面註解=類別太淺」這個診斷,是本書把「文件」當「設計工具」的核心,下一集(先寫註解)會把它推到極致。
錄製建議
- 建議時長:約 26–30 分鐘(全書最厚一章,內容最多,可放最長)。配比:慣例+別重複 code 約 9 分、底層加精確+高層加直覺約 10 分、介面vs實作註解約 7 分、實作註解what/why+跨模組約 5 分。
- 討論策略:這章「壞版對好版」對照組超多,主打「一條一條演」的節奏感——每講一個原則就配一個改寫範例,讓聽眾邊聽邊對照自己的 code。把「介面註解必須跟實作不一樣,否則類別太淺」當成本集最重的洞見來收,並預告下一集會用它驅動設計。
- 因為是最長一集,注意留 1-2 個「呼吸點」(停下來總結、或拋個討論問題),避免資訊密度太高聽眾跟丟。