《The Pragmatic Programmer 務實的程式設計師》Podcast 準備稿:務實的偏執
書名: 務實的程式設計師 The Pragmatic Programmer(Your Journey to Mastery, 20th Anniversary Edition) 作者: David Thomas & Andrew Hunt 系列: 啃一本大書(恩普拉氏) 公開標題建議: 啃一本大書|The Pragmatic Programmer.合約、斷言、盡早崩潰:連自己都不信任 (5/11) 涵蓋範圍: 第4章 務實的偏執(Topic 23-27:合約式設計、死程式不說謊、Assertion 式程式設計、如何平衡資源、不要跑得比車頭燈還快)
背景速覽
第四章的開場白是一句公理:Tip 36「你寫不出完美的軟體」。 在電腦短短的歷史裡,從沒有人寫過一段完美的軟體,你也不太可能是第一個。接受這件事不該傷你的自尊——它是這整章的起點。我們被教防禦性編程(不信任別人的 code 和輸入),但務實程式設計師更進一步:他們連自己都不信任。 這章談的就是「對自己的錯誤建立防禦」的五種具體紀律。引言用 Woody Allen 那句「當大家真的都想害你時,偏執就只是好的思考」——務實的偏執不是悲觀,是務實地面對「一定會出錯」這個現實。
一句話重點
既然沒有人(包括你自己)寫得出完美的軟體,務實程式設計師就用一整套「主動防禦自己」的紀律——用合約寫清楚權利義務、用斷言檢查「不可能發生」的事、發現異常就盡早崩潰而非帶傷前進、誰分配資源誰負責釋放、永遠只走車頭燈照得到的一小步。
值得討論的重點
1. 合約式設計(DBC):把權利與責任寫進程式碼
Tip 37「Design with Contracts」。源自 Bertrand Meyer 為 Eiffel 設計的 Design by Contract。核心問題:什麼叫「正確的程式」?不多不少、恰好做到它宣稱要做的事。 用三個元件把這個宣稱寫清楚:前置條件(呼叫前必須為真——是呼叫者的責任,傳合法資料進來)、後置條件(常式保證完成後的狀態,存在後置條件也暗示「常式一定會結束」、不准無限迴圈)、類別不變量(從外部看永遠為真的條件)。合約的精神是「懶惰的程式碼」——接受的條件要嚴格,承諾的回傳要盡可能少(如果你號稱什麼都接受、什麼都保證,那你要寫的 code 就無限多了)。這跟第二章正交性的「害羞程式碼」是一對——害羞講「別多管別人的事」,懶惰講「別多接、別多許」。即使你的語言不原生支援 DBC,它仍是一種設計技術——寫進註解或單元測試也能收穫大半好處。
2. 死程式不說謊:盡早崩潰,崩潰勝過帶傷
Tip 38「Crash Early」。所有錯誤都會給你資訊,最糟的反應是「這不可能發生」然後忽略它。兩個漂亮的子原則:「Catch and Release is for Fish」——別到處 catch 例外、印個 log、再 rethrow,那只是用錯誤處理淹沒主邏輯、而且製造耦合(你被迫列舉對方可能拋的每個例外,對方新增一個你就悄悄過時)。直接讓例外自然往上傳播。「Crash, Don’t Trash」——當程式發現「不可能的事剛剛發生了」,它就不再可靠,從此它做的每件事都可疑,所以盡快終止。一個死掉的程式,通常比一個殘廢的程式造成的損害小得多(總比把壞資料寫進重要資料庫、或叫洗衣機連轉二十次脫水好)。這正是 Erlang/Elixir 的「Let it crash」哲學——失敗交給 supervisor 管理,supervisor 失敗交給它的 supervisor,形成 supervisor tree。
3. 斷言式編程:別練習自我欺騙
Tip 39「Use Assertions to Prevent the Impossible」。如果某件事「不可能發生」,就用斷言確保它真的不會。每當你發現自己在想「但這當然不可能」,加一段斷言去驗證它——因為「這不可能」這四個字,正是 bug 最愛躲的地方(呼應第三章除錯的 Tip 33「select 沒壞掉」)。斷言檢查的是絕不該發生的事,不是用來做正常的錯誤處理(使用者輸入錯誤不是用斷言擋的,那是預期內的事)。它是你寫給未來的一張保險:當你的假設哪天被打破,斷言會在第一時間、在離錯誤最近的地方喊出來,而不是讓錯誤偽裝成別的症狀飄到三層之外。
4. 平衡資源 + 別跑得比車頭燈快:善始善終、永遠小步
兩條收尾的紀律。平衡資源:Tip 40「Finish What You Start」(分配資源的那個函式,也要負責釋放它——記憶體、交易、執行緒、檔案 handle,誰開的誰關)、Tip 41「Act Locally」(把資源的生命週期局部化在小範圍內,別讓「誰該關」變成跨模組的謎題)。不要跑得比車頭燈快:Tip 42「Take Small Steps—Always」與 Tip 43「Avoid Fortune-Telling」。車頭燈只照得到前方一小段、且照不到彎道——軟體開發的「視距」也很短,你只看得到未來一兩步。任何需要「算命」的任務(估幾個月後的完成日、替不確定的未來擴展性過度設計、猜使用者未來要什麼)都太大了。與其替不確定的未來設計,不如設計可替換的程式碼——讓它容易被丟掉換成更好的(這還順便改善內聚、耦合與 DRY)。因為彎道後面總可能有黑天鵝。
注意事項
⚠️ 「盡早崩潰/Let it crash」最容易被誤解成「到處不處理錯誤、讓程式亂死」。請說清楚適用邊界:在有 supervisor、能安全重啟、無狀態污染的環境(Erlang/Elixir、無狀態服務)這很自然;在已佔用資源、需收尾交易、要與其他行程互動的場景,不能莽撞 kill,但原則不變——一旦發現不該發生的事,盡快、乾淨地終止,別帶傷繼續。
⚠️ 斷言不是錯誤處理。預期內的失敗(使用者輸入非法、網路斷線、檔案不存在)要用正常流程處理,斷言是給「絕不可能」的事用的。另外提醒:很多語言的斷言在 release build 會被關掉,所以別把有副作用的程式碼或必要的檢查放進斷言裡。
專家補充
💡 「合約式設計 vs 防禦式編程」是個值得展開的對照。傳統防禦式編程要求每個函式都不信任所有輸入、層層檢查——結果是檢查邏輯到處重複(違反 DRY)。DBC 用「合約」明確劃分責任:前置條件是呼叫者的事,函式內部就不必再重複檢查。這把「在哪裡檢查」變成設計決策,而不是到處塞 if。Clojure 的 spec、Elixir 的 guard clause 是現代的原生支援。
💡 「Let it crash」是分散式系統可靠性思想的源頭之一。Erlang 因電信系統而生(要求九個九的可用性),Joe Armstrong 的洞見是:與其追求「不出錯」,不如假設「一定會出錯」,然後讓系統從錯誤中快速、隔離地恢復。這條思想後來長成 Kubernetes 的「pod 死了就重啟」、微服務的 circuit breaker、混沌工程(Netflix Chaos Monkey 主動弄死服務來驗證恢復力)。本書 1999 年就把它寫進來了。
💡 「不要跑得比車頭燈快」其實是把第二章曳光彈/迭代估算的精神,從「方法」升級成「世界觀」——承認我們的視距有限,所以一切都要小步、要回饋、要可替換。它也預示了 Nassim Taleb 的黑天鵝與「反脆弱」:別賭你能預測未來,要讓系統在意外來臨時還能換掉壞掉的部分。書裡用 1999 年「Motif vs OpenLook 誰會贏桌面 GUI 大戰」當例子——答案是兩個都沒贏,瀏覽器來了把整桌掀了。
討論問題
🎙️ 開場鉤子:「防禦性編程的意思是不相信別人寫的 code。但這集要講一個更狠的層次——連你自己寫的都別信。當你寫下『這不可能發生』那一刻,bug 通常就藏在那裡。」
🎙️ 自問自答:「程式出事了,是該想辦法撐下去,還是直接讓它死?」——本書的答案違反直覺:通常直接死比較好。因為一個發現了『不可能的事』的程式已經不可靠了,讓它帶傷繼續,只會把損害擴散到資料庫、檔案、其他系統。死掉的程式不會說謊,也不會繼續搞破壞。
🎙️ 帶走的一題:「翻一段你最近寫的 code,找出一句你心裡默念過『這當然不會發生』的地方——把它變成一個斷言。如果它真的不會發生,這行斷言永遠沉默;如果哪天它發生了,這行斷言會救你好幾個小時。」
更大範圍關聯
- 與三經典:本章的「合約」「斷言」與《Clean Code》的錯誤處理章、邊界章呼應;但 Pragmatic 的角度更偏「設計責任的劃分」。「盡早崩潰」與 Uncle Bob 的「別回傳 null、別傳 null」是同一個「讓錯誤無所遁形」的家族。
- DBC 的學術源頭:Bertrand Meyer《Object-Oriented Software Construction》是 DBC 的權威出處,Eiffel 是唯一把合約做進語言核心的主流語言。本書把這個學院派概念翻譯成所有語言都能用的設計紀律。
- 可靠性工程:「Let it crash」接 Erlang/OTP、《Release It!》(Michael Nygard) 的穩定性模式、混沌工程。本章是「韌性系統」思想在 1999 年的早期種子。
- 承先啟後:「別跑比車頭燈快」直接回扣第二章曳光彈與迭代估算(EP3),也預告第七章重構(EP8,可替換程式碼的具體手法)與第八章敏捷的本質(EP10,回應變化而非預測變化)。
錄製建議
- 建議時長:約 24-26 分鐘(本章五個 Topic 密度高)。配比:開場「你寫不出完美的軟體」這句公理約 2 分(定調)、合約式設計約 7 分(最有深度、與正交性的呼應好講)、死程式不說謊約 6 分(Catch-and-Release、Let-it-crash 都是金句)、斷言約 4 分、平衡資源 + 車頭燈合併約 5 分(後者當收尾與向 EP8 的橋)。
- 討論策略:用「連自己都不信任」這條主線貫穿——防禦的對象不是別人,是自己的假設。合約與斷言是「事前防禦」,盡早崩潰是「事中止損」,車頭燈是「事前別自欺能預測未來」。把「Let it crash 如何長成今天的 K8s 和混沌工程」這條現代回扣放進去,讓 1999 年的書顯出縱深。
- 本章主題沉重但金句最多,適合放慢、把每個 Tip 講透。結尾預告 EP6「彎曲或弄壞」——既然變化必然、未來不可預測,怎麼寫出能彎而不折的程式碼?