第 9 / 11 部 待錄

命名與先寫註解

《A Philosophy of Software Design 軟體設計的哲學》Podcast 準備稿:命名與先寫註解

書名: 軟體設計的哲學 A Philosophy of Software Design 作者: John Ousterhout 系列: 旗艦評書(恩普拉氏) 公開標題建議: 評書|A Philosophy of Software Design.一個爛變數名追了六個月,以及「先寫註解」這招設計神器 (9/11) 涵蓋範圍: 第14章 命名 + 第15章 先寫註解

背景速覽

這一集是「文件篇」的後半,把命名和「先寫註解」這兩個高度互補的章節合在一起。命名章 Ousterhout 用一個親身故事開場——一個還算貼切的變數名 block,害他追了一個檔案損毀的 bug 整整六個月。而第15章「先寫註解」則是全書最像「神器」的一招:把寫註解的時機從「最後」提前到「最開始」,讓註解不只是文件,更變成一個驅動設計、即時暴露壞抽象的工具。這兩章合起來,剛好是「把名字取對」和「把抽象想對」這對工夫,也把前兩集建立的文件方法論收束成一個完整的工作流。

一句話重點

命名是抽象的一種,好名字要精確(讓讀者光看名字就能準確猜出它是什麼、不是什麼)又一致(同一概念到處用同一個名字、且這名字不挪作他用)——取不出好名字本身就是設計有問題的訊號;而「先寫註解」把文件提前到寫 code 之前,讓註解變成設計工具:如果一個方法或變數需要一長段註解才講得清,那就是紅旗——你還沒有好抽象。

值得討論的重點

1. 一個爛名字追了六個月:命名是真的會出人命的事

開場故事必講。1980 年代末 Ousterhout 和學生做的分散式作業系統 Sprite,偶爾有檔案資料區塊莫名變成 0,極難重現、好幾個學生放棄。他親自接手,花了六個月才找到——病根是一個叫 block 的變數,在不同地方被當成兩種完全不同的東西:physical block number(磁碟實體區塊)和 logical block number(檔案邏輯區塊),某處拿著邏輯區塊號卻用在需要實體區塊號的位置,覆寫了無關的磁碟區塊。最扎心的是:包括他自己在內的多位開發者都讀過那段 code,沒人發現——因為看到 block 用作實體區塊號,大家反射式地假設它就是。如果當初叫 fileBlockdiskBlock,這 bug 幾乎不會發生。教訓:多數人挑「第一個還算貼切」的名字,而 block 確實還算貼切——壞就壞在「還算貼切」也能害你追半年。

2. 名字=抽象:精確,且暗示「不是什麼」

Ousterhout 把名字定位成一種抽象——在讀者腦中建立一幅清晰意象,而好名字不只說「是什麼」,也暗示「不是什麼」。檢驗法:「如果有人只看到這個名字、不看宣告不看文件,他能多接近地猜出它指什麼?」最常見的病是太籠統getCount(數什麼?應改 numActiveIndexlets)、x/y 表字元位置(也可能是像素,應改 charIndex/lineIndex)、blinkStatus(boolean 該用述詞,應改 cursorVisible)、在無回傳值的方法裡用 result(誤導)。對應紅旗「模糊名稱」。但也可能太具體:delete(Range selection)selection 暗示只能刪選取的,其實能刪任何範圍,應改 range

3. 取不出好名字,是設計問題不是命名問題

這是命名章最深的洞見。紅旗「難以命名」:如果你發現某變數很難取出一個精確、直覺、又不太長的名字——那暗示這變數可能沒有清楚的定義或用途。處理法不是硬湊一個名字,而是回頭看設計:也許你想用一個變數同時代表好幾件事,拆成多個變數、每個就有清楚定義了。金句:「難以命名」是設計問題的訊號。挑名字的過程,會透過暴露弱點來改善你的設計。

4. 一致性:三個要求+一場與 Go 短名派的辯論

好名字第二個性質是一致:同一用途永遠用同一名字、這名字永不挪作他用、且該用途夠窄使所有同名變數行為一致(Sprite bug 正是違反第三點)。同類多變數用「約定名+區分前綴」(srcFileBlock/dstFileBlock);迴圈變數 i 永遠最外層、j 次外層。Ousterhout 還誠實記錄了一場辯論:Go 社群有人主張名字該短、常常單字母(Andrew Gerrand:「長名稱會遮蔽 code 在做什麼」)。他不覺得長名版 RuneCount 比短名版難讀,並指出 Go 文化「用同一個短名表達多種東西」(ch 可能是 character 或 channel、d 可能是 data/difference/distance)正是 Sprite block 的同類陷阱。但他也讓步:可讀性由讀者判斷不是作者——別人覺得你的短名好懂就 OK,開始被嫌 cryptic 就改長;反之他自己被嫌長名難讀也會改短。雙方都同意的一條:名字宣告與使用之間距離越大,名字就該越長(短迴圈用 i 正是這條的應用)。

5. 先寫註解:把文件變成設計工具

第15章是全書最像「神技」的一招。多數人把寫註解拖到最後(code 和測試都做完才補),這幾乎保證寫出爛文件——因為心理上已結案、急著做下一個、設計記憶模糊,最後只能邊看 code 邊寫,註解就淪為重複 code,而漏掉的剛好是文件最該描述的東西(rationale)。Ousterhout 的做法反過來:新類別先寫類別介面註解,寫最重要 public 方法的介面註解和簽章(主體留空), 在註解間反覆迭代到結構對勁,寫重要變數的宣告和註解,才填方法主體。code 寫完時註解就完成了,永遠沒有「待寫文件」的積欠。

它最重要的好處不是文件,是改善設計:註解是唯一能完整捕捉抽象的工具,在寫實作前先描述抽象,逼你想清楚「這東西的本質是什麼、最重要的是什麼」。而它附帶一個強大的診斷——註解是複雜性的「煤礦坑金絲雀」:判斷介面複雜度最好的方法就是看描述它的註解,介面註解短而完整=介面簡單,必須又長又複雜才講得完=介面複雜(紅旗「難以描述」)。把介面註解和實作對比,還能直接看出方法的「深度」。

注意事項

⚠️ 「名字要精確」別推到「所有名字都要落落長」。Ousterhout 自己給了例外:短迴圈裡的 i、作用域很小的區域變數,短名完全 OK,因為所有用途都看得到、意義從 code 就能推出。判準是「宣告與使用的距離」——距離小就能短。別讓聽眾把區域變數都取成 theCurrentIterationIndexOfTheOuterLoop

⚠️ Go 短名那段要中立呈現,別演成「Ousterhout 打臉 Go」。他的立場其實很平衡——承認可讀性由讀者定、自己長名被嫌也會改、且雙方在「距離越遠名字越長」上達成共識。這正好示範本書一貫的態度:原則背後的『為什麼』比規則本身重要,理解了「為了讓讀者不必猜」,你就知道何時該長何時該短。

⚠️ 「先寫註解」聽起來美好,但對習慣「先 hack 出能跑的東西」的人是反直覺的、需要刻意練習。Ousterhout 自己的結尾就是「試試看,堅持夠久讓自己習慣,再回頭評估」。別把它講成「立刻就會愛上」,而是「一個值得認真試一陣子的習慣」。它也和上一集 EP7 的「設計兩次」、第3章的「戰略型」是一套——都是投資心態的具體動作。

專家補充

💡 命名是本季對照線上少數「兩派完全同意」的地方——這和 Clean Code 第二章「有意義的命名」幾乎逐條呼應(避免誤導、避免太籠統、boolean 用述詞、作用域越大名字越長)。所以這集可以講得很篤定:無論你站 Ousterhout 還是 Uncle Bob,命名的功夫都是共識、沒有爭議。把它和 EP1 對 Clean Code 命名章的呼應接起來,整季的「哪些是共識、哪些是一家之言」這條線就很完整。

💡 「難以命名=設計問題」這個洞見,呼應了工程界那句老笑話「電腦科學只有兩件難事:快取失效、命名、還有 off-by-one」——命名難的真正原因,是它逼你想清楚這東西到底是什麼。Ousterhout 把這個直覺正式化成一條紅旗,比笑話更可操作。

💡 「先寫註解=用文件驅動設計」其實和 TDD「用測試驅動設計」是同構的——都是「在寫實作前先用某種外部視角逼自己想清楚」。但 Ousterhout 在第19章會說他不喜歡 TDD(嫌它太戰術、沒有明顯設計時機),卻擁抱「先寫註解」——因為註解逼你想的是抽象,而測試逼你想的是單一功能能否通過。這個對比是下一集(EP11 講趨勢時會碰到 TDD)的好伏筆。

討論問題

🎙️ 開場鉤子:「一個還算貼切的變數名,能讓你追一個 bug 追多久?這位史丹佛教授的答案是——六個月。而且那個名字一點都不爛,它叫 block,問題只是它有時指磁碟區塊、有時指檔案區塊。」

🎙️ 自問自答:「如果我發現一個變數怎麼取名都彆扭、總是又長又不精確——這是我詞彙量不夠,還是別的問題?」——用紅旗回答:那多半不是命名問題,是設計問題,這個變數可能根本沒有清楚的用途。

🎙️ 帶走的一題:「下次開一個新類別時,試一件反直覺的事:先別寫任何 code,先把這個類別的介面註解、最重要那幾個方法的註解寫出來。如果你發現某個方法的註解怎麼寫都又臭又長——停下來,那是 Ousterhout 在告訴你:這個抽象,還沒想對。」

更大範圍關聯

  • 命名是對照線上的共識區:本集與《Clean Code》第二章「有意義的命名」高度一致,呼應 EP1 埋下的「哪些是共識、哪些是一家之言」這條線,延伸 [軟體工程領域切分藍圖] §5。
  • 命名難的本質:「難以命名=設計沒想清楚」呼應 Phil Karlton 名言(快取失效與命名是電腦科學兩大難事),把工程笑話正式化為設計紅旗。
  • 「先寫註解」與 TDD 的對照:用文件驅動設計 vs 用測試驅動設計,是兩種「先想清楚再寫」的路徑——伏筆 EP11 Ousterhout 對 TDD 的保留態度,也接到《Pragmatic Programmer》的「思考型程式設計」。

錄製建議

  • 建議時長:約 22–24 分鐘。配比:命名(六個月 bug + 精確 + 一致 + Go 辯論)約 13 分、先寫註解約 10 分。
  • 討論策略:用「六個月 bug」這個真實故事開場最抓人,把命名從「龜毛小事」提升到「會出大事」的高度;中段把 Go 短名辯論演成一場平衡的對話(不偏袒),落在「可讀性由讀者定」這個本書一貫態度;下半場把「先寫註解」當成一個可以馬上試的神器來推,並用「金絲雀」比喻把它和設計品質綁在一起。
  • 這集是文件篇收尾,可在結尾把「命名+註解」整套工夫和前面的「深模組+資訊隱藏」接起來——文件不是設計的附屬品,它就是設計的一部分。