Bad Smells in Code

程式碼的壞味道 (Bad Smells in Code) #

Martin Fowler 提出的「程式碼壞味道(Code Smells)」並非指程式碼有 Bug,而是指程式碼結構中可能存在深層設計問題的跡象。 識別這些味道,是進行重構的第一步。本章列出常見的壞味道及其對應重構策略。


1. 命名與基本邏輯 (Naming & Basics) #

程式碼的可讀性始於命名與去重。

Code Smell症狀與根本問題Refactoring核心筆記/重要性
Mysterious Name苦思不出好名稱;代表對功能理解不足設計有缺陷Rename好名稱是程式碼清晰的關鍵,能大幅降低理解成本
Duplicated Code在不同地點看到相同的程式結構或邏輯提取函式 (Extract Function),將相同邏輯統一起來違反 DRY 原則 (Don’t Repeat Yourself),是維護的夢魘
Comments註釋被用來解釋複雜、難懂的程式碼,而非解釋「為什麼這麼做」將邏輯抽為函式,或用斷言 (Assertion) 說明狀態規則優秀的程式碼應該是自解釋的,註釋不應彌補不良設計

註釋往往像是「除臭劑」,用來掩飾程式碼的低劣品質。
如果你覺得需要寫註釋來解釋程式碼在做什麼,不如先試著重構它。


2. 函式與參數 (Functions & Parameters) #

函式應專注於「做什麼(What)」而非展示冗長的「如何做(How)」。

Code Smell症狀Refactoring核心筆記與優化效益
Long Function (過長函式)函式包含過多行數拆分成小函式並給予良好名稱長度不是絕對標準,重點在於「意圖 (What)」與「實作 (How)」的語意距離。小函式具備解釋性、共用性與選擇性
Long Parameter List (過長參數列)函式需要傳入過多參數將多個參數組合成一個物件或參數類別 (Parameter Object)過長的參數列會增加函式的複雜度呼叫成本,組合成物件可提高可讀性和維護性

3. 資料與變數管理 (Data Handling) #

資料的範圍與型態管理不當,是導致系統脆弱的主因。

Code Smell症狀Refactoring核心筆記與優化效益
Global Data (全域資料)資料可在任何地方被修改,卻無法輕易追蹤是誰改的存取函式 (Accessor) 封裝它。封裝能限縮範圍,進而監控修改來源
Mutable Data (可變資料)變數的值頻繁變動,作用域越大,風險越高封裝變數;將查詢與修改分離。呼應 Functional Programming 理念,盡可能保持資料不可變 (Immutable)
Data Clumps (資料泥團)某些資料項目 (如:地址、城市、郵遞區號) 總是成群結隊地一起出現將它們提取並集合成一個獨立的類別
Primitive Obsession (基本型態依戀)過度使用基本型態 (int, string) 來代表具備業務意義的概念 (如:電話號碼、貨幣)將其改為「數值物件 (Value Object)」效益: 賦予資料明確意義,並可重用相關的驗證或處理函式

4. 變更的擴散與隔離 (Change Prevention) #

當需求變更時,好的設計應能將修改範圍限縮在單一模組內。

Code Smell症狀Refactoring核心概念與目標
Divergent Change (發散式修改)一個模組 (類別) 經常因為**「不同的原因」而被以「不同的方式」修改**拆分邏輯成不同階段 (Phase) 或類別,建立明確的領域邊界違反單一職責原則 (SRP),一個模組應該只有一個修改理由
Shotgun Surgery (散彈式修改)為完成一個小修改,須同時更動許多不同的類別使用 Inline 重構或是搬移函式,將相關邏輯集中相關邏輯被不當地分散,應將其聚集起來,以利維護

比較: * 發散式修改是「一個類別受多種變化影響」。

  • 散彈式修改是「一種變化影響多個類別」。

5. 類別與物件導向濫用 (Object-Orientation Abusers) #

這些味道顯示程式碼未能有效利用物件導向的特性。

Code Smell症狀與根本問題Refactoring核心概念與優化方向
Repeated Switches (重複的 Switch)相同的 Switch 邏輯散落在各處,新增條件時,所有 Switch 都須修改使用多型 (Polymorphism) 取代條件判斷遵循開放/封閉原則 (OCP),對擴展開放,對修改封閉
Large Class (過大類別)類別擁有太多欄位或職責依據使用方法,將其拆解為數個子類別或元件違反單一職責原則 (SRP),提高內聚力 (Cohesion)
Alternative Classes with Different Interfaces (異曲同工的類別)兩個類別做類似的事,但函式命名或介面不同更改函式簽名、移動函式,或提取共同父類別提高一致性,使類別可互換,方便使用者
Refused Bequest (被拒絕的遺贈)子類別繼承了父類別,卻只需要一小部分功能,或覆寫掉不需要的方法建立旁系 (Sibling) 類別,或將繼承關係轉為委託 (Delegation)違反 Liskov 替換原則,繼承關係不恰當
Temporary Field (暫時欄位)類別中的某些欄位,只在「特定情況」下才有值,其他時候是空的將這些欄位與相關行為放入一個新類別類別的欄位應該在所有情況下都有效,確保物件的狀態始終是完整的

6. 模組間的耦合 (Coupling) #

模組間應低耦合,避免過度親密或不必要的傳話。

Code Smell症狀與根本問題Refactoring核心概念與目標
Feature Envy (依戀情結)一個函式對**「別的類別」的資料**感興趣的程度,超過對自己所在類別的興趣。將該函式(或部分邏輯)移到它該去的地方(資料所在的類別)。提高內聚力,確保行為靠近數據。
Message Chains (訊息鏈)程式碼像長鏈一樣層層呼叫(如 a.getB().getC().doSomething())。隱藏委託關係 (Hide Delegate)。最好直接把該動作抽取成函式,而非僅是縮短鏈條。違反迪米特法則 (Law of Demeter),增加耦合性。
Middle Man (中間人)某個類別有一半以上的函式都只是在委託給另一個類別做事。移除中間人,直接讓呼叫方與實作方溝通;或使用繼承。降低不必要的間接層,減少重複委託。
Insider Trading (內線交易)模組之間過度且隱晦地交換資料,破壞封裝。搬移函式或使用中介模組來斷開緊密耦合。確保模組之間的溝通清晰、公開,不破壞資訊隱藏原則。

7. 其他可疑之處 (Others) #

Code Smell症狀與根本問題Refactoring核心概念與目標
Loops (迴圈)使用傳統迴圈處理集合資料 (Collection)使用 Pipeline 操作(如 filter, map 等高階函式)宣告式的 Pipeline 通常比指令式的迴圈更易讀,符合函式式編程風格
Lazy Element (冗贅元素)為了「將來可能有用」而存在的函式或類別,目前沒什麼作用或只有一行程式碼使用 Inline 函式或類別將其消除應保持程式碼精簡,避免不必要的抽象層級
Speculative Generality (誇誇其談未來性)為了處理「未來可能出現的情況」而設計的複雜機制,目前只有測試程式在用移除它們遵循 YAGNI 原則 (You Ain’t Gonna Need It),只實現現在需要的東西
Data Class (純資料類別)類別只擁有欄位與 Getter/Setter,沒有行為檢查誰在使用這些資料,將相關的行為搬移進這個類別中違反物件導向的封裝原則,數據應與其操作行為結合

如何看待壞味道

識別壞味道不是為了追求完美,而是為了風險管理
當你聞到味道時,不代表必須立即修改,但它是個強烈訊號,告訴你這裡未來可能會出問題,或這裡是難以維護的熱點。