《Refactoring 重構》Podcast 準備稿:建構測試・第一組重構手法
書名: 重構 Refactoring(Improving the Design of Existing Code, 2nd Edition) 作者: Martin Fowler 系列: 啃一本大書(恩普拉氏) 公開標題建議: 啃一本大書|Refactoring.先有安全網,再談手法 (3/8) 涵蓋範圍: 第4章 Building Tests(建構測試)+ 第6章 A First Set of Refactorings(第一組重構手法)
背景速覽
前兩集講了「重構是什麼」和「從哪聞出問題」。這集補上重構安全的前提——測試,再正式進入手法目錄的第一章。第6章是整本目錄的基底:Extract / Inline Function、Extract / Inline Variable、Change Function Declaration、Rename、Encapsulate Variable、Introduce Parameter Object、Split Phase 等 11 個最基礎、最高頻的手法。後面所有複雜重構,都是這些基本動作的組合。把這集當成「重構的基本功與安全須知」。
一句話重點
沒有測試就不是重構,只是「改 code 並祈禱」——一套能自我驗證的測試是重構的安全網;而真正天天在用的重構,其實就是 Extract、Inline、Rename 這幾個樸實到不行的基本動作,它們是所有複雜手法的原子。
值得討論的重點
1. 自我驗證的測試:重構的安全網
Fowler 的立場很硬:重構的前提是你有一套自我驗證(self-verifying)的測試——能全自動執行、自己判斷 Pass/Fail、不需人盯著看輸出。他特別點出一個常見錯誤:寫了測試卻永遠通過(assert 寫錯、根本沒測到東西)——永遠通過的測試等於壞掉的測試。另一個重點是「測試要夠多,但多數專案的問題是測試太稀疏,不是太多」。寫測試別追求完美覆蓋,集中火力在最容易出錯的邊界條件,有部分測試遠勝於沒有。
2. 收到 Bug 時的黃金流程:先寫一個會失敗的測試
這是這章最該落地的習慣。收到 bug 回報時,不要直接跳去修 code,而是:(1) 先寫一個能重現該 bug 的測試 → (2) 跑它,確認它失敗(證明你真的抓到問題了)→ (3) 改 code 直到它通過。 這樣不只修好當下,還永久防止 regression(這個 bug 以後再也回不來)。這套流程把「修 bug」變成一種會累積資產的行為。
3. 重構的兩個原子:Extract 與 Inline
第6章一上來就是 Extract Function / Inline Function、Extract Variable / Inline Variable 兩對「互逆」操作,這個編排有深意:重構不是單向的「拆得越碎越好」,而是雙向的塑形。
- Extract Function:把一坨邏輯抽成有名字的函式,核心是把「意圖」和「實作」分離——如果你得讀內文才知道它在幹嘛,就該抽出來給它一個說清楚意圖的名字。Fowler 的判準很激進:該不該抽,看的是「語意距離」而非長度,哪怕只有一行,只要函式名比實作更能說明意圖,就值得抽。
- Inline Function / Variable:反過來,當一個函式的本體和名字一樣清楚、甚至更清楚時,就把它塞回去。重構不是教條地拆,有時把過度抽象的東西塞回來,反而更清楚。
4. Rename 與 Change Function Declaration:改名字是大事
Rename Variable、Change Function Declaration(改名/改參數)看起來瑣碎,Fowler 卻很重視——因為函式宣告是連接程式碼各部分的「關節」,一個好名字能讓人不看實作就懂用途。改名/改簽章往往會牽動很多呼叫端,這正是重構工具(IDE)最能幫上忙、也最能安全完成的地方。延伸的 Introduce Parameter Object(把總是一起出現的參數打包成物件)則直接對應 EP2 的 Data Clumps 味道。
注意事項
⚠️ 「沒測試就別重構」這條,現實中常卡關。 很多人面對的就是「一坨沒測試的祖傳 code」。這時不是放棄重構,而是順序顛倒:先想辦法架測試,再重構——但「怎麼在沒測試的 code 上架測試」是另一門學問(Legacy Code 的主場,下面關聯區會接)。這集要誠實告訴聽眾:Fowler 第4章假設你站在有測試的起點,現實常常不是。
⚠️ Extract Function 別矯枉過正。 「短函式」是 Fowler 的信仰,但 EP1 提過《A Philosophy of Software Design》的 Ousterhout 對「把函式切太碎」有強烈反對意見(淺模組過多反而增加認知負擔)。提醒聽眾:抽函式的判準是**「名字能不能讓人不看內文就懂」**,不是「行數有沒有超過 N 行」。為了短而短,會製造一堆要跳來跳去的小函式。
⚠️ 改名/改簽章雖然 IDE 能自動化,但跨越 API 邊界(公開給別人用的介面)時要小心——你改不到別人的呼叫端。這時要用「保留舊介面、標 Deprecated、漸進遷移」的策略(呼應 EP2 的挑戰章)。
專家補充
💡 Extract / Inline 這種「互逆操作成對出現」的設計,洩漏了 Fowler 對重構的根本觀點:重構是雙向可逆的塑形,不是單向的淨化。 好的設計往往是「先抽出來看清楚、發現抽錯了再塞回去、換個地方再抽」這樣試出來的。這跟雕塑很像——加一塊、減一塊,逼近你想要的形狀。聽眾如果只記得「Extract」忘了「Inline」,就會掉進「越碎越好」的陷阱。
💡 「先寫一個會失敗的測試再修 bug」這個流程,其實就是 TDD 的精神用在維護場景:紅(寫出失敗測試)→綠(修好)→(重構)。它和《Clean Coder》強調的「修 bug 要附帶一個防止它復發的測試」是同一套職業紀律。Bug 不該只是被修掉,它該變成測試套件裡一個永久的哨兵。
💡 為什麼 Fowler 說「現代編譯器讓小函式呼叫成本不再是問題」很重要?因為「函式呼叫有開銷、所以別拆太細」是一個過時的效能迷思,它曾經阻止整個世代的人寫小函式。Fowler 明確替它除魅:先寫清楚,效能等量測出瓶頸再說。這條和 EP1「重構階段不管效能」是同一個立場的延續。
討論問題
🎙️ 開場鉤子:「我問你一個尷尬的問題——你上次重構,是真的有測試保護,還是改完之後手動點兩下、覺得『應該沒壞吧』就 commit 了?今天這集就是在補這個洞。」
🎙️ 自問自答:「Extract Function 到底什麼時候該用?是不是函式超過 N 行就要拆?」——不是。判準是語意:你得讀進去才懂它在幹嘛,那就抽出來給它一個說明意圖的名字;哪怕一行,只要名字比實作更清楚就值得。
🎙️ 帶走的一題:「下次你收到一個 bug,逼自己照這個順序:先寫一個會失敗的測試,看它紅,再去修。你會發現你對『到底哪裡壞了』的理解,瞬間清楚很多。」
更大範圍關聯
- 與 TDD / Kent Beck:第4章基本上是把 TDD 的測試紀律搬進重構脈絡。Fowler 和 Beck 共享「紅綠重構」這套節奏;沒有測試,重構的「安全」就無從談起。
- 與《Working Effectively with Legacy Code》的分工:Fowler 假設你有測試;當你「想重構卻無法先寫測試,因為 code 糾纏到測不了」,那就是 Michael Feathers 的領域——他教你找「接縫(Seams)」把依賴撬開、讓無法測的 code 變可測。Fowler 教你重構,Feathers 教你怎麼先讓老 code 進到能被 Fowler 重構的狀態(見 [軟體工程領域切分藍圖])。
- 與《Clean Code》命名章:Rename、Change Function Declaration 的精神(名字即文件、命名困難是設計訊號)和 Uncle Bob 的命名章完全同源,可互相引用。
- 思想座標:Extract / Inline 這對基本功,是「演進式設計」能運作的微觀機制——你靠這些小到不會出錯的動作,持續把結構推向更好的形狀。
錄製建議
- 建議時長:約 20 分鐘。配比:建構測試(安全網+bug 黃金流程)約 8 分、第一組手法(Extract/Inline/Rename/Parameter Object)約 10 分、收束約 2 分。
- 討論策略:前半把測試講成「不是潔癖,是讓你敢動手的膽量來源」,用「改完手動點兩下就 commit」這種真實畫面戳聽眾。後半別把 11 個手法逐一念,挑 Extract/Inline 這對(講雙向塑形)+ Rename/Change Declaration(講名字即關節)+ Introduce Parameter Object(接 Data Clumps 味道)三組講透就好,強調「所有複雜手法都是這些原子的組合」。
- 這集是「基本功」集,語氣可以樸實一點——刻意傳達「最常用的重構其實最不起眼」這個反差。