第 11 / 15 部 待錄

重構

《Code Complete 軟體建構之道》Podcast 準備稿:重構

書名: 軟體建構之道 Code Complete(A Practical Handbook of Software Construction, 2nd Edition) 作者: Steve McConnell 系列: 啃一本大書(恩普拉氏) 公開標題建議: 啃一本大書|Code Complete.重構:讓每次修改都讓程式變得更好 (11/15) 涵蓋範圍: 第24章 重構

背景速覽

第24章單獨成集,因為它份量足、主題完整、而且是整本書「程式碼改善」部的精神所在。這章某種程度是 Martin Fowler 1999 年那本《Refactoring》聖經的濃縮精華版——McConnell 借用了 Fowler 的定義、code smell 清單和重構手法,但放進他自己的框架:軟體一定會變,問題只在於每次變,它是變好還是變壞。 這集很適合跟前面 EP10(缺陷集中、重寫 bug 王)和後面的維護主題串起來講。

一句話重點

所有成功的軟體都會被修改,而每次修改都是一個分岔路口——用爛膠帶硬補,品質就劣化;把修改當成收緊原始設計的機會,品質就提升;重構就是那條「讓修改使程式變得更好」的紀律。

值得討論的重點

1. 軟體演化的基本法則——修改應當改善內部品質

McConnell 先打破一個神話:很多人以為 code 是交付後才大改,現實是初始開發期間就在劇烈演化。他借用生物演化的比喻——有些變異有益、有些有害。區分的關鍵問題只有一個:改完之後,程式品質是提升還是下降? 由此引出全書一條重要原則「軟體演化的基本法則(The Cardinal Rule of Software Evolution):演化應當改善程式的內部品質」。這個 framing 很有力,因為它把「重構」從「有空才做的清潔工作」提升成「每次碰 code 時的職業義務」——你不是被要求額外花時間重構,你是被要求別讓 code 在你手上變爛。這直接呼應 EP9《Clean Code》的童子軍規則「讓營地比你來時更乾淨」。

2. Code Smells——程式碼的壞味道清單

什麼時候該重構?McConnell 借用 Fowler 的「壞味道 smells」概念,列了一長串警訊:重複的程式碼(違反 DRY)、過長的常式、巢狀過深的迴圈、類別內聚力差、參數列表過長、修改需要同時動好幾個類別、case 述句需要平行修改(考慮改多型)、基本型別被過度使用(如用 int 表金額,該建 Money 類別)、傳遞流浪資料(tramp data)、用註解解釋難懂的 code(「不要註解爛 code——重寫它」)、以及一條很反直覺的——「看似未來可能會用到」的程式碼(專家共識是不要寫推測性 code,把現在的寫清楚就好,呼應 YAGNI)。這份清單的價值是:它把「我覺得這 code 怪怪的」這種模糊直覺,變成一張可以逐條對照的檢查表

3. 重構手法的層級——從資料到系統

McConnell 把重構手法按層級組織:資料層級(取代魔術數字、基本型別轉類別、拆分多用途變數)、敘述層級(分解布林表達式、用多型取代 case、立即 return)、子程式層級(抽取子程式 Extract Method——最常用的重構之一、分離查詢與修改 Command-Query Separation)、類別實作層級(上移/下移成員、抽取子類別)、類別介面層級(搬移子程式、隱藏委派、封裝公開成員變數)、系統層級(提供工廠方法、例外與錯誤碼互換)。這份手法目錄不需要逐條唸(那是 Fowler 整本書的內容),但要傳達一個觀念:重構不是「亂改改看」,而是有名字、有套路、可重複的標準動作——就像棋譜裡的定石。

4. 安全重構的紀律——小步、單一、可回退

這是這集最實用、最該講透的部分。重構若誤用,傷害比好處大。McConnell 的安全守則:備份起始 code(進版控)、保持重構規模小一次只做一個重構(改完重新編譯測試再做下一個)、列步驟清單、設「暫存區 parking lot」(記錄途中發現但不急著處理的想法)、頻繁建檢查點、善用編譯器警告、重新測試(搭配回歸測試)、審查變更。最反直覺的數據:修改 1-5 行 code 時出錯機率最高(一個組織引入單行變更審查後錯誤率從 55% 降到 2%)——所以 McConnell 說:對待簡單的修改,要像對待複雜的修改一樣謹慎。 還有兩條「不該重構的時機」:別用重構掩飾「邊寫邊修」(重構針對的是能運作的 code,對壞掉的 code 亂改不是重構是 hacking);別用重構代替重寫(需要大翻修就重新設計)。

注意事項

⚠️ 重構的鐵律前提是要有測試保護網——「在不改變可觀察行為的前提下改內部結構」這個定義,只有在你能驗證「行為沒變」時才安全。沒有測試的重構,本質上是「我猜我沒改壞」。所以這集一定要跟 EP10 的測試串起來講:先有回歸測試,才敢放手重構。 這也是為什麼 Fowler 的書和 TDD 是綁在一起的。

⚠️ code smell 清單裡那條「看似未來可能會用到的 code 要砍」(YAGNI, You Aren’t Gonna Need It)容易被誤解成「永遠別考慮擴展性」。McConnell 的意思是反對推測性的過度設計,不是反對好的抽象。平衡點:把現在需要的寫乾淨、寫得容易改,而不是預先蓋一堆用不到的彈性。

⚠️ 「修改 1-5 行出錯機率最高」這個反直覺數據要強調——因為它對抗了「小改動不用測、不用 review」的普遍鬆懈心態。但別把 55% 降到 2% 這個特定案例當普世定律,用它支撐「小改也要謹慎」的方向就好。

專家補充

💡 這章是 Martin Fowler《Refactoring》(1999)的精華導讀——McConnell 自己在「更多資源」就推薦讀 Fowler 原著看每個手法的逐步範例。可以告訴聽眾:Code Complete 給你重構的框架和心態(為什麼、什麼時候、怎麼安全做),Fowler 給你完整的招式手冊(每招的詳細步驟)。兩本搭配讀最完整。Fowler 後來 2018 出了第二版,把範例改成 JavaScript,更貼近現代。

💡 「重構是每次碰 code 的義務,不是有空才做的奢侈品」這個 framing,跟《Clean Code》的童子軍規則、跟敏捷的「持續重構」是同一條線。它的革命性在於:把「整理 code」從專案管理問題(要不要排一個 refactoring sprint)變成個人習慣問題(你每次 commit 有沒有讓它變好一點)。後者更可持續,因為它不需要老闆批准——這是給聽眾最重要的 mindset shift。

💡 「對待簡單修改要像對待複雜修改一樣謹慎」+「1-5 行最容易出錯」,是這章最該被記住的反直覺洞見。它背後的心理學是:簡單的改動讓人鬆懈、跳過測試和 review,而正是這種鬆懈製造了 bug。今天的「一行 hotfix 直接 push 上 production」翻車故事,每一個都是這條原則的活教材。

討論問題

🎙️ 開場鉤子:「你的程式碼從寫出來那一刻起,就注定會被改。問題從來不是『要不要改』,而是——每次改,它是變得更好,還是更爛?這集講的重構,就是那條讓每次修改都讓程式變得更好的紀律。」

🎙️ 自問自答:「重構跟『亂改改看』差在哪?」——差在三件事:重構針對的是能正常運作的 code、每次只做一個有名字的標準動作、而且有測試幫你確認行為沒變。少了任何一個,那就不是重構,是 hacking。

🎙️ 帶走的一題:「下次你要改一行『小小的』code,正準備不寫測試、不開 PR 直接 push——停一下。數據說,1 到 5 行的修改出錯機率最高,因為你最容易在這種小改動上鬆懈。對待簡單的修改,請像對待複雜的修改一樣謹慎。」

更大範圍關聯

  • 重構聖經導讀:本集是 Martin Fowler《Refactoring》的框架版精華;Code Complete 給心態與安全紀律,Fowler 給完整招式手冊,兩本互補。可在 [軟體工程領域切分藍圖] 把它們接成一條「程式碼改善」線。
  • 童子軍規則同源:「演化應改善內部品質」直通《Clean Code》的童子軍規則與敏捷的持續重構——三者是同一個 mindset 的不同表述。
  • 依賴測試:重構的安全前提是回歸測試,與 EP10 緊密綁定;可提醒聽眾「沒有測試的重構是在賭運氣」。
  • 接續缺陷集中:「針對高錯誤率、高複雜度模組重構」直接承接 EP10 的「找出 20% 的 bug 王並重寫」。
  • 時代座標:2004 年重構工具剛起步(IDE 的自動 Extract Method 還很新),所以 McConnell 花了篇幅講「手動安全重構的紀律」;今天 IDE 自動重構成熟,但「小步、單一、可回退」的精神依然適用。

錄製建議

  • 建議時長:約 22-25 分鐘。配比:軟體演化基本法則(核心 framing)6 分;code smells 清單 5 分;重構手法層級(不逐條唸、講概念)5 分;安全重構紀律(最實用)8 分。
  • 討論策略:用「每次修改是變好還是變爛」這個分岔路口當主軸貫穿全場——它把重構從技術提升成職業習慣。code smells 那段挑幾個最有共鳴的講(重複 code、過長常式、用 int 表金額、推測性 code),別逐條念。安全重構是最該濃墨重彩的部分,用「1-5 行最容易出錯、簡單修改要像複雜修改一樣謹慎」當高潮反直覺點。全程把「先有測試才敢重構」跟 EP10 串起來。
  • 明確定位這集:Code Complete 給框架、Fowler 給招式手冊,推薦聽眾兩本一起讀,給出明確的延伸閱讀路徑。