在 Java 開發過程中,代碼重複、過長方法、臃腫類別等問題屢見不鮮。這些「代碼壞味道」不僅降低可讀性,更增加維護成本。Martin Fowler 在《重構》一書中系統梳理了常見壞味道,本文結合 Java 實戰場景,深度解析 21 種核心壞味道及其重構策略,助你寫出更優雅、可維護的代碼。
最普遍的壞味道,通常源於「複製-貼上」編程習慣。重複代碼分散邏輯,修改時易遺漏。重構核心原則是 OAOO (Once And Only Once):提取公共方法、引入模板方法模式、或通過繼承/組合消除重複。
方法承擔過多職責,導致難以理解、測試與復用。遵循「單一意圖原則」:一個方法只做一件事。可通過 Extract Method 拆分邏輯,配合 Compose Method 重組調用鏈。
類別職責過重,違反 單一職責原則 (SRP)。拆分策略:識別內聚的屬性與方法群,提取為獨立類別(Extract Class),或引入組合模式將行為委託給協作對象。
單一類別因不同原因頻繁變更(如業務規則變動、資料庫結構調整、UI 交互修改)。重構方向:將變化方向隔離,按變化原因拆分類別,使每個類別只因單一原因變化。
一處需求變更引發多個類別連鎖修改。表明相關行為與數據分散在不同類別。解決方案:將共同變化的數據與行為聚合到同一類別(Move Method、Move Field)。
方法頻繁調用其他對象的 getter 進行計算,而非操作自身數據。判斷標準:若方法對某類數據的訪問遠超自身類,應 將行為移至數據所在類(Move Method),或引入 Visitor 模式處理跨對象操作。
多個類別、方法參數中反覆出現相同數據組合(如 `startDate/endDate`、`street/city/zip`)。這些數據應 封裝為值對象(如 `DateRange`、`Address`),提升語義表達與復用性。
用 `int` 表示金額、`String` 表示電話號碼,缺乏領域語義與驗證邏輯。重構:引入小對象封裝基本型別(如 `Money`、`PhoneNumber`、`PostalCode`),利用類型系統約束業務規則。
基於類型碼或常量的 `switch/if-else` 鏈,破壞開閉原則。重構路徑:以多態取代條件邏輯——引入子類、狀態模式或策略模式,將分支行為封裝為獨立類別。
新增一個子類,必須同步新增另一體系的對應子類(如 `Employee`/`EmployeeFactory`、`Document`/`DocumentPrinter`)。本質是 霰彈式修改的特例,解法:將從屬關係內化為單一體系內的組合(如 `Employee` 持有 `Factory` 引用)。
類別職責過輕,維護成本超過價值。常見於過度設計的中介類、僅委託的包裝類。果斷 內聯類別,將職責併入調用者。
為「未來可能用到」預留的抽象層、鉤子方法、參數化配置。YAGNI 原則指導:僅為當前需求設計,未被生產代碼調用的通用性代碼應刪除,測試用例除外。
對象某些字段僅在特定場景下有效(如演算法中間狀態),導致對象狀態語義模糊。重構:提取專用類別持有臨時字段,將相關演算法遷移至此類(Extract Class + Move Method)。
調用鏈過長:`a.getB().getC().doSomething()`。客戶端過度依賴對象內部結構。解法:隱藏委託關係,在中介對象添加便捷方法,或引入 外觀模式簡化接口。
類別過度委託,接口多數方法僅轉發調用。雖封裝內部結構,但造成「空殼類」。重構:移除中間人,讓客戶端直接訪問委託對象,或將實質行為下沉至委託類。
雙向直接訪問私有成員、互相調用內部方法。破壞封裝邊界。重構手段:將共享行為提取為獨立類、引入中介者模式、或重新劃分職責邊界。
功能相似的類擁有不同方法簽名(如 `load()` vs `read()`)。重構:統一協議接口,向上提取公共介面,配合適配器模式兼容既有實現。
第三方庫缺乏所需功能且難以修改。對策:引入包裝器/裝飾器擴展行為,或採用 擴展點模式隔離對庫的依賴。
類別僅含字段與 getter/setter,無業務行為。違反「對象 = 數據 + 行為」原則。重構:將操作該數據的行為遷移至類內(Move Method),若屬於 DTO 則明確標記為數據傳輸對象。
子類僅使用父類少量功能,繼承關係語義不清。通常意味著 繼承層次設計錯誤。解法:改用組合委託,或重新設計層次結構,確保「Is-A」關係成立。
代碼自述性差,需大量註釋解釋「做什麼」。註釋應解釋「為什麼」,而非「怎麼做」。重構目標:通過命名、結構、提取方法使代碼自文檔化,註釋僅補充業務語境與非顯性決策。
識別壞味道不是目的,重構才是核心。建議在日常開發中:
記住:代碼質量不是寫出來的,是重構出來的。