Martin Fowler《重構》經典代碼異味目錄的 Java 語境解讀。識別異味是重構的前提,本文對 21 種異味進行分類、成因分析、危害說明、重構手法映射與預防實踐,助力寫出整潔、可維護代碼。
分類總覽
| 類別 | 異味 | 核心問題 |
| 重複與規模 | 1. Duplicated Code | 邏輯分散、修改遺漏、Copy-Paste 程式設計 |
| 2. Long Method | 單方法承擔過多職責、難理解、難測試、難復用 |
| 類級異味 | 3. Large Class | 類職責過重、違反單一職責原則 (SRP)、實例變數過多 |
| 4. Long Parameter List | 參數過多、調用困難、語義模糊、易傳錯順序 |
| 變化與耦合 | 5. Divergent Change | 單類因不同原因頻繁修改、職責邊界不清 |
| 6. Shotgun Surgery | 單一變更引發多類連鎖修改、關聯邏輯分散 |
| 7. Parallel Inheritance Hierarchies | 平行繼承體系、新增子類必須同步修改多處 |
| 8. Inappropriate Intimacy | 類間過度訪問私有成員、破壞封裝、耦合過緊 |
| 特徵依賴 | 9. Feature Envy | 方法更關心其他類數據、行為歸屬錯位 |
| 10. Data Clumps | 數據項總是成群出現、應提取為獨立對象 |
| 11. Primitive Obsession | 濫用基本類型表達領域概念、缺乏類型安全與行為 |
| 結構與邏輯 | 12. Switch Statements | 基於類型碼/常量分支、違反開閉原則 (OCP)、多態替代 |
| 13. Temporary Field | 字段僅特定情境有效、語義混淆、狀態管理混亂 |
| 14. Refused Bequest | 子類拒絕繼承父類行為、繼承體系設計錯誤 |
| 間接與中介 | 15. Message Chains | 客戶端穿越多層委託獲取對象、脆弱依賴、違反迪米特法則 |
| 16. Middle Man | 類大部分方法僅委託、無實質行為、過度封裝 |
| 17. Alternative Classes with Different Interfaces | 同功能不同介面、缺乏統一抽象、多態受阻 |
| 18. Incomplete Library Class | 庫類缺失所需功能、無法修改源碼、需包裝/擴展 |
| 職責缺失 | 19. Data Class | 純數據容器、無行為、貧血模型、邏輯外泄 |
| 20. Lazy Class | 類職責過輕、維護成本大於價值、應內聯或移除 |
| 文檔異味 | 21. Comments | 過度註釋掩蓋代碼不清、代碼未自說明、重構信號 |
高頻異味深度解析與重構映射
1. Duplicated Code (重複代碼)
- 識別:相似代碼塊出現 ≥2 次、相同邏輯不同字面量
- 重構:Extract Method、Pull Up Method、Form Template Method、Extract Class
- 原則:OAOO (Once And Only Once) / DRY (Don't Repeat Yourself)
2. Long Method / 3. Large Class
- 指標:方法 > 30 行 / 類 > 500 行、圈複雜度 > 10、實例變數 > 10
- 重構:Extract Method/Class、Replace Method with Method Object、Decompose Conditional
5/6. Divergent Change vs Shotgun Surgery
| 維度 | Divergent Change | Shotgun Surgery |
| 現象 | 一類因多原因變 | 一變動牽多類 |
| 本質 | 類內聚力低 | 模組耦合高 |
| 重構 | Extract Class、Split Phase | Move Method/Field、Inline Class |
9. Feature Envy (特徵依賴)
- 代碼味:`other.getX().getY().calculate()`、大量 Getter 調用
- 重構:Move Method (移至數據所在類)、Extract Method + Move Method
- 例外:策略模式、訪問者模式、DTO 組裝器 合理「依賴」
11. Primitive Obsession (基本類型偏執)
- 反模式:`String phone`、`double money`、`int[] range`、`Map params`
- 重構:Replace Primitive with Object (Value Object)、Introduce Parameter Object
- 領域建模:`PhoneNumber`、`Money`、`DateRange`、`Email`、`Id`
12. Switch Statements
- 重構:Replace Conditional with Polymorphism、Replace Type Code with Subclasses/State/Strategy
- 現代 Java:Sealed Class + Pattern Matching (JDK 17+) 優雅解決
15. Message Chains (消息鏈)
- 違反:迪米特法則 (最少知識原則)
- 重構:Hide Delegate、Extract Method、Introduce Local Extension
19. Data Class (數據類)
- 特徵:全 Getter/Setter、無業務方法、被動被操作
- 重構:Encapsulate Field、Move Method、Remove Setting Method (不可變)
- 區別:DTO/VO 邊界層允許,領域層嚴禁
21. Comments (註釋)
- 異味註釋:解釋「做什麼」(代碼應自說明)、過時註釋、嘗試補救壞代碼
- 良性註釋:解釋「為什麼」(業務規則、演算法選擇、非顯而易見決策)、Javadoc 公開 API
- 重構:Extract Method (註釋變方法名)、Rename Variable、Introduce Assertion
識別工具與實踐流程
靜態分析工具
- SonarQube / SonarCloud:規則集覆蓋大部分異味、技術債務量化
- IntelliJ IDEA Inspections:實時檢測、快速修復 (Alt+Enter)
- SpotBugs / PMD / Checkstyle:CI 管道強制閘門
- ArchUnit:架構規則驗證 (層級依賴、命名規範、異味模式)
重構節奏
- 紅燈停:新代碼提交前跑靜態分析,異味阻斷合併
- 綠燈行:重構有測試保護 (單元/整合測試覆蓋核心邏輯)
- 小步走:單一重構手法、單一提交、持續集成驗證
- 持續改進:定期技術債務專項、Boy Scout Rule (留下比到達時更乾淨)
預防異味的設計原則
| 原則 | 對應異味 | 核心思想 |
| SRP (單一職責) | Large Class, Divergent Change | 一個類只有一個變化理由 |
| OCP (開閉原則) | Switch Statements, Shotgun Surgery | 對擴展開放、對修改關閉 |
| LSP (里氏替換) | Refused Bequest, Parallel Hierarchies | 子類型可替換父類型 |
| ISP (介面隔離) | Alternative Interfaces, Data Clumps | 介面越小越好、客戶不依賴不需介面 |
| DIP (依賴倒置) | Feature Envy, Inappropriate Intimacy | 依賴抽象而非具體 |
| LoD (迪米特法則) | Message Chains, Middle Man | 只與直接朋友交談 |
| Tell, Don't Ask | Feature Envy, Data Class | 告知行為而非詢問數據 |
總結
代碼異味不是 Bug,而是設計腐化的早期預警信號。掌握 21 種異味識別與重構映射,配合靜態分析工具鏈、測試保護網、SOLID 原則內化,可將重構從「大手術」轉化為「日常保養」。記住:重構的目標不是消除所有異味,而是將代碼保持在可控的技術債務水位,支撐業務快速、安全交付。