refactoring-patterns

refactoring-patterns

熱門

套用命名的重構轉換來改善程式碼結構,同時不改變行為。當使用者提到「重構這段程式碼」、「程式碼壞味道」、「提煉方法」、「取代條件式」、「技術債」、「搬移方法」、「內聯變數」、「分解條件式」或「清理這堆亂七八糟的程式碼」時使用。也適用於清理遺留程式碼、透過重構為新功能做準備,或判斷哪種轉換適合特定程式碼壞味道。涵蓋壞味道驅動的重構、安全轉換序列與測試防護。關於程式碼品質基礎,請參閱 clean-code。關於管理複雜度,請參閱 software-design-philosophy。

1698星標
171分支
更新於 2026/7/16
SKILL.md
唯讀
名稱
refactoring-patterns
描述

套用命名的重構轉換來改善程式碼結構,同時不改變行為。當使用者提到「重構這段程式碼」、「程式碼壞味道」、「提煉方法」、「取代條件式」、「技術債」、「搬移方法」、「內聯變數」、「分解條件式」或「清理這堆亂七八糟的程式碼」時使用。也適用於清理遺留程式碼、透過重構為新功能做準備,或判斷哪種轉換適合特定程式碼壞味道。涵蓋壞味道驅動的重構、安全轉換序列與測試防護。關於程式碼品質基礎,請參閱 clean-code。關於管理複雜度,請參閱 software-design-philosophy。

重構模式框架

一種有紀律的方法,在不改變可觀察行為的前提下改善既有程式碼的內部結構。每個重構都遵循相同的循環:確認測試通過、套用一個小型結構變更、再次確認測試通過。

核心原則

重構不是重寫。它是一連串小型、保留行為的轉換,每一步都有測試支撐。 你永遠不改變程式碼的功能——只改變它的組織方式。大爆炸式的重寫之所以失敗,是因為它們混合了結構變更與行為變更,讓人無法判斷哪個環節出了問題。

基礎: 糟糕的程式碼是在時間壓力下交付的自然結果,不是人格缺陷。程式碼壞味道是結構劣化的客觀訊號;壞味道目錄告訴你該往哪裡看,重構目錄告訴你該做什麼

評分

目標:10/10。 根據快速診斷中八個項目通過多少來評分結構品質——score = round(passed / 8 × 10),當單一壞味道嚴重時向下調整。等級:

  • 9-10:沒有明顯的壞味道,每個函式只做一件事,名稱揭示意圖,消除重複,條件式在適當處使用多型,測試涵蓋重構後的路徑。
  • 5-6:仍有少數壞味道(長函式、一些重複),但結構大致良好。
  • ≤3:壞味道普遍存在——糾結的條件式、上帝類別、到處重複——或者沒有測試可以安全重構。

始終說明當前分數、導致分數下降的壞味道名稱,以及達到10/10所需的具體重構。

重構模式框架

系統性改善程式碼結構的六個重點領域:

1. 以程式碼壞味道為觸發

核心概念: 程式碼壞味道是更深層結構問題的表面指標——不是錯誤,而是設計讓程式碼更難理解、擴充或維護的訊號。每種壞味道對應到可修正它的命名重構。

為什麼有效: 命名的壞味道讓團隊有客觀標準,而不是主觀的「我不喜歡這個」——「這是特權依戀」直接指向修正方法。

關鍵見解:

  • 壞味道分為五大類:膨脹體、物件導向濫用者、變更阻礙者、冗贅物、耦合者
  • 長函式是最常見的壞味道;重複程式碼是最昂貴的
  • 需要註解來說明做什麼的方法就是壞味道——改為提煉並命名該區塊
  • 散彈式修改(一個變更,許多類別)與發散式變更(一個類別,許多變更原因)是責任錯置的相反訊號
  • 基本型別偏執——使用原始字串/整數而非小型領域物件——會散播錯誤與重複

程式碼應用:

情境 模式 範例
方法超過10行 提煉方法 將迴圈主體拉出為 calculateLineTotal()
一個變更影響許多類別(散彈式修改) 搬移方法/欄位 將分散的行為集中到一個類別
許多方法有相同參數 引入參數物件 startDate, endDateDateRange
複製貼上的邏輯 提煉方法 + 提升方法 透過共同方法或基底類別共享

當你需要命名壞味道及其修正時,請參閱 references/smell-catalog.md——包含五大類別(膨脹體、OO濫用者、變更阻礙者、冗贅物、耦合者)的偵測啟發式與對應的重構。

2. 組合方法

核心概念: 大多數重構從這裡開始:將長方法拆解成更小、命名良好的片段,讀起來像散文——高層次步驟委派給命名清晰的輔助方法。

為什麼有效: 短方法加上意圖揭示的名稱消除了註解,讓錯誤一目了然,並促進重用;當名稱說明一切時,方法呼叫的閱讀成本為零。

關鍵見解:

  • 提煉方法是唯一最重要的重構——先精通它
  • 想寫註解?提煉該區塊並用註解作為方法名稱
  • 當方法本體與名稱一樣清晰時,使用內聯方法——沒有價值的間接性就是雜訊
  • 對在多處使用的計算值,以查詢取代暫存變數;當一個暫存變數服務兩個目的時,分割暫存變數
  • 當區域變數太糾結無法提煉時,以方法物件取代方法——它們變成欄位

程式碼應用:

情境 模式 範例
帶有註解的區塊 提煉方法 // check eligibilityisEligible()
只使用一次的暫存變數 內聯變數 移除 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 物件封裝了所有這些,讓商業規則集中在一個地方,型別系統在編譯時捕捉錯誤。

關鍵見解:

  • 以符號常數取代魔術數字——最簡單的資料重構;它命名了意圖
  • 以物件取代資料值可治癒基本型別偏執(EmailAddressMoneyTemperature
  • 封裝欄位與封裝集合——永遠不要暴露原始欄位或可變的內部列表
  • 當型別碼影響行為時,以子類別取代型別碼;當子類別化不可行時,以策略取代
  • 當你需要識別語義(一個共享的 Customer,而非副本)時,將值物件改為參考物件

程式碼應用:

情境 模式 範例
if (status == 2) 取代魔術數字 if (status == ORDER_SHIPPED)
String email 到處傳遞 以物件取代資料值 帶有驗證的 EmailAddress 類別
Getter 回傳可變列表 封裝集合 回傳 Collections.unmodifiableList(items)
帶有 switch 的 int typeCode 以子類別取代型別碼 EmployeeEngineerManager

當以物件取代基本型別時,請參閱 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 陳述式 以多型取代條件式
你是否在每一步後提交? 有遺失工作、混合變更的風險 在每次綠燈到綠燈轉換後提交
變更後程式碼是否更容易閱讀? 重構增加了複雜度 還原並嘗試不同方法

延伸閱讀

改善既有程式碼的權威指南:

關於作者

Martin Fowler 是 Thoughtworks 的首席科學家、敏捷宣言簽署人,以及《重構:改善既有程式的設計》(1999年;第二版2018年)的作者,該書將基於目錄的命名重構引入主流開發。他的目錄支撐著每個主要 IDE 中的自動化重構工具。