《A Philosophy of Software Design 軟體設計的哲學》Podcast 準備稿:修改既有程式、一致性與顯而易見
書名: 軟體設計的哲學 A Philosophy of Software Design 作者: John Ousterhout 系列: 旗艦評書(恩普拉氏) 公開標題建議: 評書|A Philosophy of Software Design.改 code、一致性、與「讓程式碼顯而易見」 (10/11) 涵蓋範圍: 第16章 修改既有程式 + 第17章 一致性 + 第18章 程式應該顯而易見
背景速覽
前面九集大多在談「從零設計」,但現實是——你絕大多數時間都在改別人(和過去的自己)寫的 code。這一集把三個「維護期」的章節合在一起:第16章談改 code 時怎麼不讓複雜性偷偷爬回來(戰略型思維的延續)、第17章談一致性這個降低複雜性的強大槓桿、第18章談本書的終極目標——把 code 寫得顯而易見(obvious),也就是第2章「未知的未知」和「模糊」的正面解藥。三章共同的精神是:好設計不是一次完成的,是每次修改都「讓它好一點點」維持出來的。
一句話重點
成熟系統的設計多半由「無數次修改」決定,而非當初的構思——所以每次改 code 的標準不是「能動的最小修改」,而是**「改完之後,系統結構要像一開始就帶著這個需求去設計時的樣子」**;要做到這點,靠的是一致性(讓「看起來像 X 就真的是 X」)和顯而易見(讓讀者第一直覺就猜對、不必蒐集額外資訊)這兩個讓系統「可被快速且安全理解」的支柱。
值得討論的重點
1. 改 code 最危險的問題:「能動的最小修改」
第16章一針見血。開發者改不熟的 code 時典型心態是「能達成目的的最小修改是什麼?」——出於怕引入 bug,可以理解,但每次最小修改都塞進幾個特殊情況、幾條依賴,系統設計每改一次就壞一點。Ousterhout 的標準高得多:改完之後,系統結構應該跟「一開始就帶著這個變更去設計」時一樣。做法是抗拒快速修補的誘惑,每次都問「在這個新需求下,現有設計還是最佳的嗎?」不是就重構到最佳。甚至——即使這次不需重構,也要主動找可以順手改善的設計缺陷:任何時候改 code,都試著至少讓設計好一點,沒讓它變好多半就是讓它變糟(這就是 Clean Code 童子軍規則的 Ousterhout 版)。
2. 現實妥協與「註解貼近程式碼」
Ousterhout 不天真,他承認商業現實會逼你妥協(「正確重構要 3 個月、快補只要 2 小時」、重構會破壞跨團隊相容性)。但他要你盡量抗拒,並問「在現有限制下,我能做到的最乾淨設計是什麼?」——也許有個介於 3 個月和 2 小時之間、幾天能完成、效果接近完整重構的方案;真來不及,就請主管在 deadline 後排時間回頭做。每個組織都該規劃一小部分總工時做清理重構。 第16章也收束了文件維護的關鍵手法:註解放在它描述的 code 旁邊(最佳位置是實作檔緊鄰方法主體上方,改 code 時自然看到)、註解離 code 越遠就該越抽象(才不會被局部修改弄失效)、重要資訊寫進 code 而不是 commit log(後人不會想到去搜 commit)、每個設計決策恰好記錄一次(避免重複文件、否則改一份漏一份還沒有任何訊號告警)、提交前掃 diff 確認每個變更都對應到文件。
3. 一致性:在一處學會、處處能套用
第17章把一致性立成降低複雜性的強大工具。原理是認知槓桿——相似的事相似地做,讀者在一處學會、看到別處立刻能套用,不必每個情境重新學。它還直接降低錯誤:一致時「看起來像就真的是」,假設安全;不一致時兩個本質不同的東西長得像,讀者套錯經驗就出 bug。一致性體現在命名、coding style、介面(多實作)、設計模式、不變量等層次。怎麼建立與維持?Ousterhout 給四招:文件化(把重要慣例寫在開發者一定看到的地方)、強制(最好寫工具自動檢查、commit 不過就擋——他舉了 CRLF/LF 換行符用 commit hook 擋的真實案例;code review 越吹毛求疵團隊學得越快)、入境隨俗(When in Rome——進新檔案先看周圍長相,只要像個慣例就跟隨)、別動既有慣例(「我有更好的想法」不是引入不一致的充分理由,一致性的價值幾乎總是大於「某做法略好」)。
4. 顯而易見:本書終極目標,「未知的未知」的解藥
第18章回到第2章——模糊是兩大複雜性成因之一,解藥就是把 code 寫得顯而易見:讀者能快速讀過、不必多想、第一直覺就對。和「複雜性由讀者定義」一致,顯而易見也由讀者判斷——別人說「不顯而易見」就是不顯而易見,再清楚都不能反駁,所以 code review 是最好的判斷工具。讓 code 顯而易見的技巧:好命名(第14章)、一致性(第17章)、善用空白(參數文件之間空行、方法內以空白分邏輯區塊並各加註解、for (int pass = 1; ...) 加空白比擠成一團好讀)、以及用註解補上無法避免的不顯而易見。
5. 什麼讓 code 變得不顯而易見
Ousterhout 點名幾個常見來源:事件驅動程式設計(handler 從不被直接呼叫、控制流極難追蹤,對策:在每個 handler 的介面註解寫清楚「它何時被呼叫」)、通用容器(Pair<Integer, Boolean> 的 getKey/getValue 完全看不出真實意義,對策:別用通用容器,為該用途定義專屬類別、用有意義的名字)、宣告型別與分配型別不一致(宣告 List 卻 new ArrayList,讀者看不出效能/執行緒特性)、違反讀者預期的 code(main 結束了 RaftClient 的執行緒還在跑,對策:在建構式註解和 main 結尾都加說明)。總綱兩句金句:「軟體應為易讀設計,不是為易寫設計」(通用容器對寫的人方便、對所有後來的讀者製造混亂);以及從資訊角度看,不顯而易見=讀者缺乏理解它所需的某些資訊,三條解藥——減少所需資訊量(靠抽象與消除特殊情況)、利用讀者已有的知識(遵循慣例與符合預期)、用好命名和戰略性註解把重要資訊呈現出來。
注意事項
⚠️ 「每次改 code 都重構到最佳」會被聽成「每個 PR 都該大改」。要把 Ousterhout 自己的現實妥協帶到——他承認 deadline、相容性是真的約束,要的是「在約束下盡量乾淨」和「規劃固定比例工時還債」,不是要你為了完美主義拖垮交付。平衡感很重要,否則聽眾覺得這是象牙塔。
⚠️ 一致性會過頭。Ousterhout 特別強調:一致性不只是「相似的事相似做」,也包括「不相似的事用不同方式做」。熱衷過頭、把意思不同的東西命同名、把不適合的任務硬塞進同一個設計模式,反而製造混亂。一致性帶來價值的前提是「看起來像 X 就真的是 X」——強行統一不相似的東西會破壞這個前提。
⚠️ 「別動既有慣例」不是「慣例永遠不能改」。Ousterhout 給了兩個前提:你有當初定慣例時不存在的新資訊、且新做法好到值得更新所有舊地方——兩者都成立、團隊同意,才動,而且動完舊慣例不該留任何痕跡。重點是「改慣例的門檻很高」,不是「禁止」。別讓聽眾以為這是僵化保守。
專家補充
💡 這三章是本季對照線上「Ousterhout 與 Uncle Bob 高度同意」的一段,可以講得很融洽。第16章「每次改都讓設計好一點」幾乎就是 Clean Code 的童子軍規則(讓營地比你來時更乾淨);一致性、coding style、顯而易見也都是兩派共識。把這個點出來很重要——整季對照下來,會發現兩人真正吵的只集中在「模組/函式該多大」(深模組 vs 小函式),其餘八成的功夫(命名、一致性、可讀性、別讓 code 腐爛)他們是站在一起的。 這正好幫聽眾校正「Ousterhout vs Uncle Bob」的對立被誇大的印象,呼應藍圖 §5。
💡 「軟體應為易讀設計、不為易寫設計」這句,是整本書可讀性哲學的總結,也直接呼應 Clean Code 第一集講的「讀寫比 10:1」——你寫一次、別人讀十次,為那十次優化。兩本書在這個底層信念上完全一致。通用容器(Pair)的批評尤其實用,是日常 code review 抓得到的具體紅旗。
💡 「用工具強制一致性、commit hook 擋違規」這條,在今天已經是 CI/lint/pre-commit/formatter 的標配(Prettier、gofmt、spotless、ESLint)。Ousterhout 在 2018 年講的「寫工具自動檢查比靠人記更可靠」,現在已是業界默認——gofmt 甚至直接消滅了「大括號位置」這種爭論。可接到「把慣例自動化,是消除 bikeshedding 最有效的辦法」。
討論問題
🎙️ 開場鉤子:「你改一份不熟的 code 時,心裡是不是常想:『能動的、最小的改動是什麼?』——今天這集要告訴你,這個聽起來最安全的念頭,正是讓 codebase 一點一點爛掉的元兇。」
🎙️ 自問自答:「『我有個更好的寫法』——這算不算打破專案既有慣例的好理由?」——用 Ousterhout 的答案回答:不算。一致性的價值,幾乎總是大於「某個做法略好一點」。
🎙️ 帶走的一題:「下次 code review 時,當你讀一段 code 卡了一下、要回頭重讀才懂——別放過那個瞬間。那不是你笨,那是這段 code 不顯而易見。把那個『卡住的點』講給作者聽,因為顯而易見這件事,永遠是讀者說了算。」
更大範圍關聯
- 對照線的共識區:本集三章與《Clean Code》高度一致——「每次改讓設計更好」=童子軍規則、「為易讀設計」=讀寫比 10:1,凸顯兩派真正的分歧只集中在「模組/函式大小」,呼應 [軟體工程領域切分藍圖] §5「Clean 到底乾不乾淨」。
- 工程自動化趨勢:「用工具強制一致性」預示了今日 formatter/linter/pre-commit(gofmt、Prettier、spotless)的標配地位——把慣例自動化是消除團隊內耗最有效的手段。
- 可讀性傳統:「軟體為易讀而設計」上承 SICP「程式要寫得讓人讀、順便讓機器執行」、Knuth 的 Literate Programming,是貫穿軟工史的母題。
錄製建議
- 建議時長:約 24–26 分鐘(三章合併)。配比:修改既有程式約 9 分、一致性約 8 分、顯而易見約 9 分。
- 討論策略:用「能動的最小修改=慢性毒藥」開場製造反差;中段把一致性的「四招」(文件/強制/入境隨俗/別亂改)講成可操作的團隊守則,用 CRLF commit hook 案例落地;下半場把「顯而易見」立成本書的終極目標,用通用容器、事件驅動等具體反例讓聽眾對號入座。全集反覆強調「讀者說了算」這個本書一貫的謙遜立場。
- 這集是「對照線收斂」的關鍵——務必點明「吵了一整季,其實兩人八成的功夫是一致的」,為下一集的總結與評價鋪路。