套用命名的重構轉換來改善程式碼結構,同時不改變行為。當使用者提到「重構這段程式碼」、「程式碼壞味道」、「提煉方法」、「取代條件式」、「技術債」、「搬移方法」、「內聯變數」、「分解條件式」或「清理這堆亂七八糟的程式碼」時使用。也適用於清理遺留程式碼、透過重構為新功能做準備,或判斷哪種轉換適合特定程式碼壞味道。涵蓋壞味道驅動的重構、安全轉換序列與測試防護。關於程式碼品質基礎,請參閱 clean-code。關於管理複雜度,請參閱 software-design-philosophy。
重構模式框架
一種有紀律的方法,在不改變可觀察行為的前提下改善既有程式碼的內部結構。每個重構都遵循相同的循環:確認測試通過、套用一個小型結構變更、再次確認測試通過。
核心原則
重構不是重寫。它是一連串小型、保留行為的轉換,每一步都有測試支撐。 你永遠不改變程式碼的功能——只改變它的組織方式。大爆炸式的重寫之所以失敗,是因為它們混合了結構變更與行為變更,讓人無法判斷哪個環節出了問題。
基礎: 糟糕的程式碼是在時間壓力下交付的自然結果,不是人格缺陷。程式碼壞味道是結構劣化的客觀訊號;壞味道目錄告訴你該往哪裡看,重構目錄告訴你該做什麼。
評分
目標:10/10。 根據快速診斷中八個項目通過多少來評分結構品質——score = round(passed / 8 × 10),當單一壞味道嚴重時向下調整。等級:
- 9-10:沒有明顯的壞味道,每個函式只做一件事,名稱揭示意圖,消除重複,條件式在適當處使用多型,測試涵蓋重構後的路徑。
- 5-6:仍有少數壞味道(長函式、一些重複),但結構大致良好。
- ≤3:壞味道普遍存在——糾結的條件式、上帝類別、到處重複——或者沒有測試可以安全重構。
始終說明當前分數、導致分數下降的壞味道名稱,以及達到10/10所需的具體重構。
重構模式框架
系統性改善程式碼結構的六個重點領域:
1. 以程式碼壞味道為觸發
核心概念: 程式碼壞味道是更深層結構問題的表面指標——不是錯誤,而是設計讓程式碼更難理解、擴充或維護的訊號。每種壞味道對應到可修正它的命名重構。
為什麼有效: 命名的壞味道讓團隊有客觀標準,而不是主觀的「我不喜歡這個」——「這是特權依戀」直接指向修正方法。
關鍵見解:
- 壞味道分為五大類:膨脹體、物件導向濫用者、變更阻礙者、冗贅物、耦合者
- 長函式是最常見的壞味道;重複程式碼是最昂貴的
- 需要註解來說明做什麼的方法就是壞味道——改為提煉並命名該區塊
- 散彈式修改(一個變更,許多類別)與發散式變更(一個類別,許多變更原因)是責任錯置的相反訊號
- 基本型別偏執——使用原始字串/整數而非小型領域物件——會散播錯誤與重複
程式碼應用:
| 情境 | 模式 | 範例 |
|---|---|---|
| 方法超過10行 | 提煉方法 | 將迴圈主體拉出為 calculateLineTotal() |
| 一個變更影響許多類別(散彈式修改) | 搬移方法/欄位 | 將分散的行為集中到一個類別 |
| 許多方法有相同參數 | 引入參數物件 | startDate, endDate → DateRange |
| 複製貼上的邏輯 | 提煉方法 + 提升方法 | 透過共同方法或基底類別共享 |
當你需要命名壞味道及其修正時,請參閱 references/smell-catalog.md——包含五大類別(膨脹體、OO濫用者、變更阻礙者、冗贅物、耦合者)的偵測啟發式與對應的重構。
2. 組合方法
核心概念: 大多數重構從這裡開始:將長方法拆解成更小、命名良好的片段,讀起來像散文——高層次步驟委派給命名清晰的輔助方法。
為什麼有效: 短方法加上意圖揭示的名稱消除了註解,讓錯誤一目了然,並促進重用;當名稱說明一切時,方法呼叫的閱讀成本為零。
關鍵見解:
- 提煉方法是唯一最重要的重構——先精通它
- 想寫註解?提煉該區塊並用註解作為方法名稱
- 當方法本體與名稱一樣清晰時,使用內聯方法——沒有價值的間接性就是雜訊
- 對在多處使用的計算值,以查詢取代暫存變數;當一個暫存變數服務兩個目的時,分割暫存變數
- 當區域變數太糾結無法提煉時,以方法物件取代方法——它們變成欄位
程式碼應用:
| 情境 | 模式 | 範例 |
|---|---|---|
| 帶有註解的區塊 | 提煉方法 | // check eligibility → isEligible() |
| 只使用一次的暫存變數 | 內聯變數 | 移除 const price = order.getPrice() |
| 簡單的委派方法 | 內聯方法 | 如果只用一次,內聯 return deliveries > 5 |
| 有許多糾結區域變數的方法 | 以方法物件取代方法 | 區域變數變成新類別的欄位 |
當套用任何方法層級的轉換時,請參閱 references/composing-methods.md——包含提煉/內聯方法、提煉/內聯變數、以查詢取代暫存變數、分割暫存變數、以方法物件取代方法的逐步機制與前後對照程式碼。
3. 在物件之間搬移功能
核心概念: 物件導向設計的關鍵決策是責任該放在哪裡。當特權依戀、過度耦合或類別大小不平衡顯示方法或欄位放錯類別時,將它搬移到正確的位置。
為什麼有效: 方法放在遠離它所用資料的地方,會產生看不見的跨類別依賴,導致一個邏輯變更波及許多檔案——散彈式修改。將方法與資料放在一起,則將變更限制在一個類別內。
關鍵見解:
- 當方法使用另一個類別的功能多於自己的時,搬移方法;搬移欄位同理
- 當一個類別做兩件事時,提煉類別——沿著變更軸分割;當一個類別做太少時,內聯類別
- 隱藏委派強制執行迪米特法則;移除中間人則在轉發成為類別的全部功能時取消它
- 逐案解決這個張力:當呼叫鏈不穩定時隱藏委派,當中間人只是純轉發時移除它
程式碼應用:
| 情境 | 模式 | 範例 |
|---|---|---|
| 方法羨慕另一個類別 | 搬移方法 | 將 calculateShipping() 從 Order 搬到 ShippingPolicy |
| 上帝類別超過500行 | 提煉類別 | 將 Address 欄位/方法拉出成獨立類別 |
客戶端呼叫 a.getB().getC() |
隱藏委派 | 新增 a.getCThroughB() |
| 類別只轉發呼叫 | 移除中間人 | 讓客戶端直接呼叫委派對象 |
當決定責任歸屬時,請參閱 references/moving-features.md——包含搬移方法/欄位、提煉/內聯類別、隱藏委派、移除中間人的機制。
4. 組織資料
核心概念: 原始資料——魔術數字、公開欄位、整數型別碼——會產生微妙的錯誤並散播領域知識。用封裝行為與強制不變量的物件取代基本型別。
為什麼有效: int 金額沒有四捨五入規則或貨幣代碼;Money 物件封裝了所有這些,讓商業規則集中在一個地方,型別系統在編譯時捕捉錯誤。
關鍵見解:
- 以符號常數取代魔術數字——最簡單的資料重構;它命名了意圖
- 以物件取代資料值可治癒基本型別偏執(
EmailAddress、Money、Temperature) - 封裝欄位與封裝集合——永遠不要暴露原始欄位或可變的內部列表
- 當型別碼影響行為時,以子類別取代型別碼;當子類別化不可行時,以策略取代
- 當你需要識別語義(一個共享的
Customer,而非副本)時,將值物件改為參考物件
程式碼應用:
| 情境 | 模式 | 範例 |
|---|---|---|
if (status == 2) |
取代魔術數字 | if (status == ORDER_SHIPPED) |
String email 到處傳遞 |
以物件取代資料值 | 帶有驗證的 EmailAddress 類別 |
| Getter 回傳可變列表 | 封裝集合 | 回傳 Collections.unmodifiableList(items) |
帶有 switch 的 int typeCode |
以子類別取代型別碼 | Employee → Engineer、Manager |
當以物件取代基本型別時,請參閱 references/organizing-data.md——包含以物件取代資料值、將值物件改為參考物件、取代魔術數字、封裝欄位/集合,以及取代型別碼的變體機制。
5. 簡化條件邏輯
核心概念: 深度巢狀的 if/else 樹、長 switch 與散落的 null 檢查是最難閱讀且最容易出錯的程式碼。命名的重構可以分解、合併並以更清晰的結構取代條件式。
為什麼有效: 六分支的條件式迫使讀者在腦中模擬每一條路徑;命名良好的提煉分支是自我說明的,而多型消除了整類「忘了這個情況」的錯誤。
關鍵見解:
- 分解條件式:將條件、then 分支與 else 分支提煉成命名方法
- 合併條件式:將結果相同的條件合併為一個命名檢查
- 以防衛子句取代巢狀條件式:及早處理邊界情況並回傳,保持主路徑無縮排
- 以多型取代條件式是基於型別的條件式的黃金標準
- 引入特例(Null 物件)消除了散落的
if (x == null)檢查;引入斷言讓假設快速失敗
程式碼應用:
| 情境 | 模式 | 範例 |
|---|---|---|
帶有複雜條件的長 if |
分解條件式 | 提煉 isSummer(date) 與 summerCharge() |
深度巢狀的 if/else |
以防衛子句取代 | 邊界情況優先,及早回傳,主路徑扁平 |
| 根據物件型別的 switch | 以多型取代條件式 | 每種型別實作自己的 calculatePay() |
到處都是 if (customer == null) |
引入特例 | 帶有安全預設行為的 NullCustomer |
當理清分支時,請參閱 references/simplifying-conditionals.md——包含分解/合併條件式、防衛子句、以多型取代條件式、特例與斷言的前後對照範例。
6. 安全重構工作流程
核心概念: 重構只有在測試保護下才安全。工作流程是機械化的:執行測試(綠燈)、套用一個小型轉換、執行測試(綠燈)、提交。如果測試變紅,還原——不要除錯失敗的重構。
為什麼有效: 小步驟讓失敗很明顯(就是你做的最後一件事),還原只需幾秒鐘;除錯失敗的大爆炸重寫則需要好幾天。
關鍵見解:
- 三的法則:容忍一次重複,記錄兩次,第三次出現時重構
- 準備性重構:在加入功能之前重構讓功能更容易實作;理解性與撿垃圾重構讓程式碼在閱讀與修改時持續改善
- 何時不重構:重寫更容易、沒有測試且無法加入、或程式碼即將被刪除
- 先為清晰度重構,然後分析並最佳化測量到的瓶頸——清晰的程式碼更容易調校
- 抽象分支與平行變更讓大型重構可以在生產環境中進行,無需長期分支
程式碼應用:
| 情境 | 模式 | 範例 |
|---|---|---|
| 即將加入新功能 | 準備性重構 | 先清理插入點 |
| 第三次出現相同邏輯 | 三的法則 | 現在提煉共享邏輯 |
| 生產環境中的大型 API 變更 | 抽象分支 | 加入抽象層、遷移呼叫者、移除舊路徑 |
| 重新命名廣泛使用的方法 | 平行變更 | 加入新的、棄用舊的、遷移、移除 |
在進行大型或高風險重構前,請參閱 references/refactoring-workflow.md——包含完整的綠燈到綠燈循環、何時(不)重構、效能、抽象分支與平行變更。
常見錯誤
| 錯誤 | 為什麼失敗 | 修正 |
|---|---|---|
| 沒有測試就重構 | 沒有安全網來偵測行為變更 | 先撰寫特徵測試 |
| 大爆炸式重寫 | 混合結構與行為變更;無法除錯 | 最小步驟,每一步後測試 |
| 重構同時加入功能 | 同時戴兩頂帽子——兩種變更都無法驗證 | 先重構(提交),再加入功能(提交) |
| 重新命名但未更新呼叫者 | 建置失敗或死程式碼 | 使用 IDE 重新命名;搜尋所有參考 |
| 提煉太多小方法 | 當名稱不佳時,間接性反而沒有清晰度 | 每個名稱必須消除閱讀本體的需求 |
| 忽略壞味道目錄 | 重新發明修正方法,而非套用經過驗證的配方 | 學習命名的壞味道;每種對應到重構 |
| 重構註定要廢棄的程式碼 | 在即將淘汰的程式碼上打磨是浪費 | 檢查程式碼的生命週期是否值得投資 |
| 重構時最佳化 | 混淆清晰度與效能 | 先清晰,再分析,然後最佳化熱點路徑 |
快速診斷
| 問題 | 如果否 | 行動 |
|---|---|---|
| 開始前測試是否通過? | 沒有安全網 | 先撰寫或修正測試——永遠不要在紅燈時重構 |
| 你能說出正在修正的壞味道名稱嗎? | 憑直覺重構,而非目錄 | 識別壞味道,套用其規定的重構 |
| 每個方法是否在約10行以內? | 可能有長函式 | 將方法提煉成命名步驟 |
| 每個類別是否只有一個變更原因? | 發散式變更或過大類別 | 提煉類別以分離責任 |
| 是否有重複的程式碼區塊? | 最昂貴的壞味道 | 將共享邏輯提煉成共同方法/基底類別 |
| 條件式在適當處是否使用多型? | 仍有 switch 陳述式 | 以多型取代條件式 |
| 你是否在每一步後提交? | 有遺失工作、混合變更的風險 | 在每次綠燈到綠燈轉換後提交 |
| 變更後程式碼是否更容易閱讀? | 重構增加了複雜度 | 還原並嘗試不同方法 |
延伸閱讀
改善既有程式碼的權威指南:
- 《重構:改善既有程式的設計(第二版)》 by Martin Fowler
- 《有效處理遺留程式碼》 by Michael Feathers(無測試程式碼的配套)
- 《Clean Code:無瑕的程式碼》 by Robert C. Martin(補充命名與風格原則)
關於作者
Martin Fowler 是 Thoughtworks 的首席科學家、敏捷宣言簽署人,以及《重構:改善既有程式的設計》(1999年;第二版2018年)的作者,該書將基於目錄的命名重構引入主流開發。他的目錄支撐著每個主要 IDE 中的自動化重構工具。




