《Code Complete 軟體建構之道》Podcast 準備稿:防禦式設計與虛擬碼程式設計流程
書名: 軟體建構之道 Code Complete(A Practical Handbook of Software Construction, 2nd Edition) 作者: Steve McConnell 系列: 啃一本大書(恩普拉氏) 公開標題建議: 啃一本大書|Code Complete.防禦式設計,與寫 code 前先寫虛擬碼 (4/15) 涵蓋範圍: 第8章 防禦式程式設計 + 第9章 虛擬碼程式設計流程
背景速覽
這集收尾「建立高品質程式碼」這一大部。前面幾集講了怎麼設計類別、常式,這集講兩件更貼地的事:第8章談防禦式程式設計——怎麼讓你的 code 在收到爛資料時不崩潰;第9章談虛擬碼程式設計流程(PPP)——一套寫 code 前先用「類英文」打草稿的具體方法。前者是防守,後者是建構的施工流程。
一句話重點
防禦式程式設計的精神來自防禦性駕駛——你管不住別人傳什麼爛資料進來,所以要主動保護自己;而 PPP 則告訴你:別一坐下就敲 code,先用人話寫出意圖、反覆迭代到最佳,再把虛擬碼變成註解、把 code 填進去。
值得討論的重點
1. 「垃圾進、垃圾出」不夠——產品級軟體要「垃圾進、什麼都不出」
學校教「Garbage in, garbage out」,但 McConnell 說對產品軟體這遠遠不夠。好程式要做到「垃圾進、什麼都不出」「垃圾進、錯誤訊息出」或乾脆「不准垃圾進來」。三個策略:檢查所有外部來源的資料(特別警覺緩衝區溢位、SQL injection、整數溢位這些攻擊)、檢查所有常式輸入參數、決定怎麼處理爛輸入。但他也提醒一句很重要的話:防禦式程式設計是輔助手段,預防勝於治療——迭代設計、先寫虛擬碼、先寫測試、做設計審查的優先級,都應該高於到處加防禦碼。
2. 斷言(Assertion)vs 錯誤處理——一條關鍵的分界線
這是這集最該講清楚的觀念。McConnell 給了一條漂亮的劃分:錯誤處理用於「預期可能發生」的狀況;斷言用於「不應該發生」的狀況。 斷言是「可執行的文件」——它比註解更主動地記錄你的假設(這個指標不該是 null、這個值該在範圍內)。關鍵戒律:斷言裡不要放執行碼(產品版可能會把斷言移掉,裡面的 code 也會跟著消失)。對高健壯性的系統,他甚至建議同一個錯誤條件同時用斷言和錯誤處理——斷言在開發期把 bug 沖出來,錯誤處理在產品裡優雅應對(像 Microsoft Word 就這麼做)。
3. 隔離(Barricade)——把程式分成「安全區」和「不安全區」
McConnell 用「手術室」比喻:資料進手術室前必須消毒,室內的一切則假定安全。做法是指定某些介面當「安全區邊界」——類別的 public 方法假設資料不安全,負責檢查消毒;類別的 private 方法就可以假設資料是乾淨的。這條直接呼應前面的斷言/錯誤處理分界:隔離外的常式用錯誤處理(不能假設安全)、隔離內的用斷言(資料已消毒,再出事就是程式有 bug)。 還有一條金句:儘早把輸入轉成正確的型別——輸入常常是字串,拖著錯誤型別走越久,複雜度和被攻擊風險越高。
4. 虛擬碼程式設計流程(PPP)——寫 code 前先用人話打草稿
第9章是個很具體的施工方法。PPP 的核心是:用「意圖層次」的類英文寫虛擬碼(描述「做什麼的意義」,而非「在這個語言裡怎麼實作」),反覆迭代選出最佳設計,然後把虛擬碼直接變成註解,再在每行註解底下填入 code。它的好處一串:審查幾行虛擬碼比審查幾十行 code 容易、在虛擬碼階段改比寫完 code 改便宜、虛擬碼直接變註解所以註解永遠準。McConnell 點破一個心理學陷阱:一旦開始寫 code,你就會對它產生情感投入,更捨不得丟爛設計——所以務必在虛擬碼階段就迭代充分。最後他有句很硬的話:專業與業餘的差別在於從「迷信」走向「理解」——約 95% 的錯誤源自程式設計師自己,如果你常懷疑是編譯器或硬體的錯,你還停在迷信階段。
注意事項
⚠️ 「進攻式程式設計 Offensive Programming」——在開發版讓異常盡量明顯(斷言一定中止程式、switch 的 default 在開發期中止、刪物件前填垃圾值),在產品版優雅恢復——這是個很棒但容易被誤用的概念。提醒聽眾:別把開發版的激進檢查帶進產品版,要用建置工具/前置處理器把它們分開(這也呼應 EP1「適用產品的限制不一定適用開發版」)。
⚠️ 「對防禦式程式設計也要防禦」——McConnell 自己警告:過多的防禦碼本身也是問題。到處檢查每個參數會讓程式臃腫緩慢,而且防禦碼自己也可能有 bug。重點是想清楚哪裡真正需要防禦,設優先順序。這條很重要,因為很多人讀完「防禦」兩個字就開始無腦加 if,反而傷害可讀性。
專家補充
💡 「契約式設計 Design by Contract」(前置條件是呼叫端的義務,後置條件是被呼叫端的保證)這章帶到了,它出自 Bertrand Meyer 的 Eiffel 語言和《Object-Oriented Software Construction》。這是一條漂亮的思想線:斷言其實就是契約的可執行版本。現代的 type system、Kotlin 的 require/check、Java 的 Objects.requireNonNull,骨子裡都是契約式設計的後裔。
💡 PPP 這套「先寫虛擬碼註解、再填 code」的方法,在今天有個有趣的迴響——它的精神跟現在用 AI 寫 code 的工作流非常像:你先用自然語言把意圖講清楚(prompt / 虛擬碼),再生成實作。McConnell 在 2004 年就抓到了「在意圖層次思考、把實作推到最後」的價值。可以拋這個現代連結,很有意思。
💡 「95% 的錯誤是你自己的錯」這句,呼應後面第23章除錯會再講的「迷信式除錯」。它背後是一種專業心態:先假設是自己的問題。這不只是技術建議,更是一種職業誠實——也預告了 EP15 會講的「個人品格」。
討論問題
🎙️ 開場鉤子:「開車時你管不住對向那個亂切的駕駛,只能自己防禦。寫 code 也一樣——你管不住別人傳什麼爛資料給你的函式。這集講的就是:怎麼讓你的 code 在面對垃圾輸入時,不會跟著一起爛掉。」
🎙️ 自問自答:「斷言和錯誤處理,到底差在哪?」——一句話:斷言處理『不該發生』的事(發生了就是 bug),錯誤處理處理『預期會發生』的事(爛輸入、檔案不存在)。分清楚這條線,你的防禦碼就不會亂。
🎙️ 帶走的一題:「下次寫一個新函式前,逼自己先用一兩句人話寫下它『要做什麼』——不是怎麼做,是要做什麼。如果你連這句話都寫不順,那代表你還沒想清楚,先別急著敲 code。」
更大範圍關聯
- 契約式設計的源流:本集的斷言/前後置條件接 Bertrand Meyer 的 Design by Contract,現代型別系統與
require/check都是其後裔。可在 [軟體工程領域切分藍圖] 把這條接到型別理論與防禦性編碼線。 - 與《Pragmatic Programmer》的呼應:那本書也大力提倡斷言、「死掉的程式不會說謊」(crash early),跟本集的進攻式程式設計同源——兩本書都主張「讓錯誤早點、大聲地暴露」。
- 與測試的關係:PPP 的「先想測試怎麼寫」「先寫虛擬碼」直通 EP10 的開發者測試與 TDD——可預告聽眾,這條「意圖先行」的線會在測試集再回來。
- 時代座標:第8章的安全議題(SQL injection、緩衝區溢位)反映了 2000 年代初網路安全意識崛起;防禦式程式設計從「防呆」升級成「防駭」,是這版的時代印記。
錄製建議
- 建議時長:約 25 分鐘。配比:垃圾進什麼都不出+三策略 5 分;斷言 vs 錯誤處理(重點)8 分;隔離與手術室比喻 5 分;PPP 虛擬碼流程 7 分。
- 討論策略:斷言 vs 錯誤處理那條分界線是這集最有價值的乾貨,務必用「不該發生 vs 預期會發生」這組對比講透。隔離用「手術室消毒」的比喻最好記。PPP 那段可以拋「跟現在用 AI 寫 code 的意圖先行工作流其實同源」這個現代鉤子,讓 2004 年的方法瞬間有當代感。結尾用「先用人話寫意圖」給聽眾一個立刻能試的習慣。
- 記得平衡:別把「防禦」講成「到處加 if」,要強調 McConnell 自己的反向警告——過度防禦也是病。