第 8 / 15 部 待錄

不尋常控制表格驅動與複雜度

《Code Complete 軟體建構之道》Podcast 準備稿:不尋常控制結構、表格驅動法與複雜度控制

書名: 軟體建構之道 Code Complete(A Practical Handbook of Software Construction, 2nd Edition) 作者: Steve McConnell 系列: 啃一本大書(恩普拉氏) 公開標題建議: 啃一本大書|Code Complete.return、遞迴、goto,與用一張表幹掉一堆 if (8/15) 涵蓋範圍: 第17章 不尋常的控制結構 + 第18章 表格驅動法 + 第19章 一般控制問題

背景速覽

這集收尾「陳述式」這一大部,把三個進階的控制主題打包:第17章談有爭議的控制結構(多處 return、遞迴、goto)、第18章談表格驅動法(用查表取代一堆 if/case)、第19章談一般控制問題(布林運算式、深層巢狀、結構化程式設計理論、McCabe 複雜度)。這集是陳述式部的「高階班」,也帶到一些軟工史上最有名的論戰(goto 大戰)和最經典的度量(循環複雜度)。

一句話重點

當基本的 if/迴圈不夠用時,你有更鋒利但也更危險的工具——提前 return 和衛述句能讓主流程清爽、遞迴對少數問題優雅、goto 在極少數場景(統一清理)有用;而對付一堆複雜邏輯,最強的武器往往不是更聰明的 if,而是把判斷搬進一張表;最終所有控制結構的好壞,都可以用「複雜度」這把尺來量。

值得討論的重點

1. 多處 return 與衛述句——讓主流程不被巢狀淹沒

第17章先談 return。McConnell 的立場是務實的:在能提升可讀性時使用——一旦確定答案就立即回傳,別寫多餘的 code 走到最後再統一 return。最有價值的應用是衛述句 guard clause:連續檢查多個前置條件時,與其層層 if 巢狀(縮排越來越深、主邏輯被遮住),不如「偵測到錯誤就提前離開」,讓正常路徑攤平、清晰。這正是 EP7「先寫正常路徑」的進階版。但他也提醒:盡量減少 return 數量——讀到常式底部時若不知道上面可能已經回傳,會增加理解難度。所以是「謹慎而非禁止」。

2. 遞迴與 goto——兩個被神化也被妖魔化的工具

遞迴適合「小規模好解、大規模好分解」的問題(QuickSort、迷宮)。McConnell 的戒律:確保有終止條件(base case)、用安全計數器防無窮遞迴、限制在單一常式內(循環遞迴 A 呼叫 B 呼叫 C 又回到 A 極難 debug)。然後他丟了一句很反骨的話:不要用遞迴算階乘或費氏數列——那是教科書的壞示範,遞迴版比迭代版更慢、更難懂、記憶體不可預測。至於 goto,他帶聽眾重溫了軟工史上最著名的論戰:Dijkstra 1968 年〈Go To Statement Considered Harmful〉。McConnell 的結論很平衡:goto 在極少數場景(統一的資源清理)是最簡潔的,但現代語言有更好的替代(try-finally)。他比較了四種錯誤處理方案(goto / 巢狀 if / 狀態變數 / try-finally),結論是:有 try-finally 就用它,沒有的話狀態變數略優於 goto;無論選哪種,關鍵是整個專案一致。

3. 表格驅動法——用一張表幹掉一大堆 if/case

第18章是這集的亮點,也是「把知識放進資料而非邏輯」這條線的極致(EP6 埋的線在這爆發)。核心思想:幾乎所有能用 if/case 選擇的事,都能改用查表選擇。當邏輯鏈變複雜時,表格的優勢就壓倒性。最有畫面的例子是「彈性訊息格式」:20 種訊息類型——邏輯導向要寫 20 個處理常式,物件導向要建 20 個子類別(複雜度沒降),而表格驅動只需要一個通用常式,依據表格描述去讀和印任何訊息。如果訊息描述從檔案讀進來,新增類型甚至不用改 code。三種存取方式:直接存取(資料當索引)、索引存取(先查索引表)、階梯存取(資料落在範圍內,逐級比對,如成績分級)。這招的威力在於:把程式的知識從「邏輯」移到「資料」,讓 code 更短、更好讀、更好改。

4. 複雜度——所有控制結構好壞的終極度量

第19章把這部收束到一把尺:複雜度。先談布林運算式的清廉寫法:用 true/false 不用 0/1、用正面表述(DeMorgan 定理化簡否定)、充分加括號別賴運算子優先序、用數線排列(MIN <= i && i <= MAX)。再談深層巢狀——研究顯示多數人無法理解超過三層的巢狀 if,這直接違反「管理複雜度」的首要使命,他列了一整套降巢狀的技巧(衛述句、抽取函式、轉 case、用多型、重新設計)。最後祭出全書最有名的度量之一——McCabe 循環複雜度(Cyclomatic Complexity):從 1 開始,每個 if/while/for/and/or 各加 1,case 每分支加 1。判讀:0-5 沒問題、6-10 該考慮簡化、10 以上該拆函式。這是把「這段 code 太複雜」這種模糊感覺變成一個可計算的數字

注意事項

⚠️ goto 那場論戰今天聽起來像「古代史」——現代主流語言(Java/Python/JS/Kotlin)根本沒有 goto 或極少用。但論戰背後的精神至今鮮活:McConnell 點破「goto 辯論的現代變體——多處 return、多處 break、例外處理——至今仍在上演」。所以講這段別當考古,要翻譯成今天的版本:「能不能用 break/return/exception」這類『要不要打破單一出口』的爭論,本質上就是 goto 大戰的後代。

⚠️ McCabe 複雜度的「10 以上要拆」不是鐵律——McConnell 自己說含多個 case 的 switch 可能超過 10 但仍合理。重點是它背後的精神:降低任何時刻需要同時理解的東西。別讓聽眾把工具當教條,盲目追求降低某個數字。

⚠️ 表格驅動法雖然強大,但不是萬靈丹。簡單情境下直接寫 if 更直覺,硬套表格反而增加間接層。McConnell 自己也說「簡單情境邏輯語句更直覺,邏輯鏈複雜時表格才占優」——記得平衡,別讓聽眾回去把每個 if 都改成表。

專家補充

💡 表格驅動法是這集最該大推的「思維升級」。它跟物件導向的多型其實是「同一個問題的兩種解法」——都是為了消滅一長串 if/case。McConnell 的洞見很深:那個彈性訊息的例子裡,物件導向建 20 個子類別並沒有真正降低複雜度,因為複雜度只是換了個地方藏。真正的關鍵設計洞見「不在於物件導向或函式導向,而在於精心設計的查找表」。這是一個能讓資深工程師都點頭的觀點——有時候資料結構選對了,邏輯就消失了(呼應 Linus Torvalds 那句名言:好程式設計師關心資料結構,而非程式碼)。

💡 結構化程式設計(Dijkstra 1969)的「單一進入、單一退出」原則,加上 Bohm-Jacopini 定理(任何控制流程都能用循序、選擇、迭代三種結構組合出來),是整個現代程式設計的理論地基。McConnell 在這章致敬了這段歷史。可以告訴聽眾:你今天寫的每個 if、每個 while,背後都站著 1960 年代那場讓程式設計從「義大利麵 goto」走向「結構化」的革命。

💡 McCabe 複雜度(1976 提出)至今還活在你的 IDE 和 CI 裡——SonarQube、各種 linter 都在算它。這又是 Code Complete「凡事能量化就量化」氣質的代表(呼應 EP5 的跨距/存活時間)。可以拋:四十多年前的一個數學度量,今天還在你每次 commit 時默默幫你把關。

討論問題

🎙️ 開場鉤子:「軟體史上有一場吵了幾十年的架——叫 goto 大戰。但你知道嗎,這場架到今天還沒結束,只是換了個名字:該不該用多個 return?該不該用 break 跳出迴圈?這集,我們從這場古老的論戰,一路聊到一個能讓一堆 if 憑空消失的魔法——表格驅動法。」

🎙️ 自問自答:「一堆複雜的 if/case,除了寫得更聰明,還有別的解嗎?」——有,把判斷搬進一張表。當你發現自己在寫第八個 else if,停下來問:這能不能變成一張查找表?很多時候,選對資料結構,邏輯就自己消失了。

🎙️ 帶走的一題:「打開你最近寫的一個函式,心裡默算一下它的 McCabe 複雜度——每個 if、while、for、&&、|| 加一分。超過 10 了嗎?如果是,這不是要你焦慮,是 McConnell 在 1976 年就替你裝好的一個警報器,提醒你:該拆了。」

更大範圍關聯

  • 「資料結構消滅邏輯」的思想線:表格驅動法接 Linus「關心資料結構」的名言、接函數式的 data-driven 設計,是 EP6「知識放進資料」的高潮。可在 [軟體工程領域切分藍圖] 標為跨範式的設計洞見。
  • 軟工史的兩座里程碑:結構化程式設計(Dijkstra)與循環複雜度(McCabe)都在這章現身,是理解「程式設計如何從手藝走向工程」的關鍵節點。
  • 暗線收束:「衛述句/提前 return」接 EP7 的「正常路徑優先」、McCabe 複雜度接 EP5 的量化精神、降巢狀接 EP2 的管理複雜度——這集把多條暗線收在一起。
  • 時代座標:goto 大戰是 1960-80 年代的事,2004 年 McConnell 用「現代變體仍在上演」把它接到當代,正是這本書「站在歷史縱深看當下實務」的典型筆法。

錄製建議

  • 建議時長:約 25-28 分鐘。配比:return/衛述句 5 分;遞迴與 goto 大戰 7 分;表格驅動法(亮點)9 分;複雜度與 McCabe 6 分。
  • 討論策略:用「goto 大戰至今未結束、只是改名」當開場鉤子,把一段古代史講得跟當代有關。表格驅動法是全集最該濃墨重彩的部分——用「20 種訊息:邏輯導向 20 個常式、OO 20 個子類別、表格驅動 1 個通用常式」這組對比當高潮,並接到「資料結構選對、邏輯就消失」的深層洞見。McCabe 複雜度用「1976 年就裝好的警報器,今天還活在你的 CI 裡」收尾,呼應全書的量化氣質。
  • 提醒自己:goto 的四種錯誤處理方案細節快速帶過,把重點放在「精神至今鮮活」的翻譯上;表格驅動法值得多花時間,因為它最能給聽眾「思維升級」的爽感。