第 7 / 15 部 待錄

直線碼條件與迴圈

《Code Complete 軟體建構之道》Podcast 準備稿:直線碼、條件與迴圈

書名: 軟體建構之道 Code Complete(A Practical Handbook of Software Construction, 2nd Edition) 作者: Steve McConnell 系列: 啃一本大書(恩普拉氏) 公開標題建議: 啃一本大書|Code Complete.把陳述式排好:順序、條件、迴圈 (7/15) 涵蓋範圍: 第14章 組織直線碼 + 第15章 使用條件述句 + 第16章 控制迴圈

背景速覽

進入第四大部「陳述式 Statements」。前面講變數(資料),這部講控制流程(邏輯)。這集涵蓋三種最基本的控制結構:直線碼(循序執行)、條件(if/case)、迴圈(for/while)。聽起來都是 programming 101,但 McConnell 的功力在於——他能把「怎麼把這些最基礎的東西排得好讀、不出錯」講出一整套有研究數據的方法論。迴圈那章尤其精彩,是程式設計最容易出錯的領域之一。

一句話重點

陳述式的好壞取決於「順序」和「清晰」——直線碼按相依性排列、讓相關的東西靠在一起;條件式讓正常路徑一眼可見、別讓錯誤處理把它淹沒;迴圈則要保持簡單、把控制邏輯集中、確保它一定會結束。

值得討論的重點

1. 直線碼——讓「順序相依性」顯而易見

第14章講最簡單的東西:一句接一句的循序執行。重點在相依性。當 A 必須在 B 之前執行時,要讓這個順序明顯。McConnell 給了一條優先級鏈:程式碼結構自明 > 命名和參數暗示 > 註解補充 > 斷言強制檢查。最好的做法是讓每個函式接收輸入、回傳更新後的資料(data = Step1(data); data = Step2(data)),順序就藏在資料流裡。反過來,當陳述式之間沒有順序相依時,依鄰近原則把相關的放一起——把同一個物件的所有操作集中,而不是把所有物件的「季度計算」混在一起、「年度計算」又混在一起。有個很實用的測試:把 code 印出來,用方框圈起相關的陳述式——方框不重疊代表分組好,方框互相交叉代表該重組

2. 條件式——正常路徑優先,別讓錯誤處理淹沒主流程

第15章的核心原則:先寫正常路徑,把正常情況放 if、異常放 else,讓讀者第一眼就看到「正常時會發生什麼」。最有力的招數是用布林函式簡化複雜判斷——把一長串 if (c == ' ' || c == ',' || ...) 換成 if (IsPunctuation(c)),可讀性立刻翻倍。一個反直覺的數據:GM 的研究指出 50%-80% 的 if 述句應該要有 else 子句——很多人漏掉 else,沒處理「另一種情況」。即使真的不需要 else,也該加註解說明為什麼。case 述句方面:用 default 偵測錯誤(放診斷訊息)、C++/Java 裡每個 case 結尾記得 break 避免 fall-through、別為了硬用 case 而製造假變數(只取指令第一個字母做 case,會把 copy、cement、cellulite 都當成 ‘c’)。

3. 迴圈——選對種類,並把它當黑盒子

第16章是這集最硬的部分,因為迴圈是程式設計最複雜、最容易出錯的領域之一。McConnell 先教你選迴圈種類:計數迴圈用 for、不知次數用 while、遍歷容器用 foreach、退出條件在中間用 while(true) + break。他提到一個有趣的研究:loop-with-exit(退出條件在中間)更接近人類思維,學生理解度測試分數提高 25%——用來解決「迴圈半圈 loop-and-a-half」問題(必須在迴圈前後重複同一段 code)。迴圈控制的兩大原則:簡化(減少影響迴圈的因素)和把迴圈內部當子程式(控制邏輯放外面,讓迴圈成黑盒子)。

4. 迴圈的安全守則——確保它會結束、別竄改索引、給索引好名字

McConnell 列了一串迴圈戒律,最值得講的幾條:一律用大括號包住迴圈體(不影響效能,卻防止修改時引入 bug)、確認迴圈一定會結束(用心智模擬跑過第一次、中間一次、最後一次)、別竄改 for 迴圈索引來強制終止i = 100 是業餘做法,該改用 while)、別依賴迴圈索引的終值(索引在正常/異常終止時值不同,且因語言而異,該用額外的布林變數記錄結果)。巢狀迴圈要用有意義的名字(transaction[month][payCode][division] 勝過 [i][j][k],還能避免索引交叉干擾)。還有一條很重要的對比:高效率的程式設計師會做心智模擬和手算,低效率的傾向隨機亂試(改 <<=、加減 1),即使碰巧成功也不懂為什麼。

注意事項

⚠️ 這三章是全書最歷久彌新、最沒有時代色彩的部分——if、迴圈的本質四十年沒變,這集的建議幾乎可以原封不動套到任何現代語言。唯一要點出的是:現代語言提供了更多消滅顯式迴圈的工具(map/filter/reduce、stream、list comprehension),很多 McConnell 在意的「迴圈索引出錯」問題,在函數式風格下根本不會發生(因為你不寫索引了)。可以補一句:最安全的迴圈,是你根本不用寫的那個。

⚠️ 「50%-80% 的 if 該有 else」這個數據別講死——它是針對特定研究的統計,不是要你每個 if 都硬加 else。背後的精神是:別漏掉「另一種情況」,要嘛處理、要嘛明確註明「這裡可以安全忽略」。

專家補充

💡 「把迴圈內部當成子程式、讓迴圈成為黑盒子」這個原則,正好是函數式編程的核心思想之一。當你寫 items.map { transform(it) },你就是把「對每個元素做什麼」這件事抽成一個函式(黑盒子),把「怎麼遍歷」交給 map。McConnell 在 2004 年用命令式的方式講這個道理,今天的函數式工具則把它變成了預設做法。這是一個很好的「老智慧、新工具」連結。

💡 「高手做心智模擬,菜鳥隨機亂試」這個對比,是這集最有價值的職業心態洞見,它會在 EP10 的除錯章節再次強力登場(科學方法 vs 巫毒程式設計)。可以預告:這條「理解 vs 碰運氣」的線是 Code Complete 區分專業與業餘的核心判準,後面還會回來。

💡 「先寫正常路徑」這個原則,其實跟後面 EP8 要講的「衛述句 guard clause、提前 return」是一體兩面——都是為了讓主流程清晰、把雜訊(錯誤處理、邊界檢查)推到旁邊。可以埋線:下一集會看到這條原則的進階版。

討論問題

🎙️ 開場鉤子:「if 跟 for 迴圈,是你寫程式第一週就學會的東西。但你有沒有想過——為什麼有些人的迴圈總是乾淨好讀,有些人的卻像迷宮?這集講的,就是把這些最基礎的控制結構排到『好』的那套功夫。」

🎙️ 自問自答:「為什麼那麼多 bug 都出在迴圈裡?」——因為迴圈要同時管初始化、終止條件、索引遞增、邊界處理,任何一個差一點就 off-by-one。McConnell 的解法是把這些變因簡化、集中,並且真的用腦袋跑一遍第一次、中間、最後一次,而不是改個符號碰運氣。

🎙️ 帶走的一題:「下次寫迴圈,逼自己在腦中跑三遍——第一次迭代、隨便一次中間迭代、最後一次迭代。光是這個習慣,就能擋掉一大半的 off-by-one。如果你的語言有 map/filter,更好——能不寫索引就不寫。」

更大範圍關聯

  • 最普世的一集:直線碼、條件、迴圈是全書最不受時代影響的內容,可在 [軟體工程領域切分藍圖] 標為「跨語言通用基礎」。
  • 與函數式的對話:「迴圈當黑盒子」直通 map/filter/reduce 的思想;現代函數式工具是這套原則的自動化實現。
  • 承先啟後:「先寫正常路徑」接 EP8 的衛述句與提前 return;「高手做心智模擬」接 EP10 的科學方法除錯——兩條暗線都從這集出發。
  • 回扣管理複雜度:簡化迴圈、減少巢狀、集中控制邏輯,全是 EP2 最高原則在陳述式尺度的落地。

錄製建議

  • 建議時長:約 25 分鐘。配比:直線碼與相依性 5 分;條件式與正常路徑優先 7 分;迴圈選種類+黑盒子原則 7 分;迴圈安全守則 6 分。
  • 討論策略:這集內容最「基礎」,反而要靠「為什麼基礎的東西也有高下之分」這個切角來抓住聽眾——強調 McConnell 把 programming 101 講出了方法論。迴圈是重頭戲,用「最容易出 bug 的領域」當定位,把「心智模擬三遍」當最實用的乾貨。全程穿插「現代函數式工具讓很多迴圈問題消失」的對照,讓 2004 年的內容有當代感。「高手模擬、菜鳥亂試」這個心態洞見當高潮,並預告除錯集會再回來。
  • 這集相對輕鬆,可以講快一點、舉多一點具體 code 對照例(好/壞並陳),聽眾邊聽邊對照自己的 code。