「程式碼壞味道(Code Smells)」並非指程式碼有 Bug,
而是指程式碼結構中可能存在深層設計問題的跡象。
識別這些味道,是進行重構的第一步。本章列出常見的壞味道及其對應重構策略。
mindmap
root((Code Smells))
命名與基本邏輯
Mysterious Name
Duplicated Code
Comments
函式與參數
Long Function
Long Parameter List
資料與變數
Global Data
Mutable Data
Data Clumps
Primitive Obsession
變更擴散
Divergent Change
Shotgun Surgery
OO 濫用
Repeated Switches
Large Class
Refused Bequest
Temporary Field
模組耦合
Feature Envy
Message Chains
Middle Man
Insider Trading
其他
Loops
Lazy Element
Speculative Generality
Data Class命名與基本邏輯 (Naming & Basics)#
程式碼的可讀性始於命名與去重。
| Code Smell | 症狀 | Refactoring |
|---|---|---|
| Mysterious Name | 難以命名,代表功能理解不足 | Rename |
| Duplicated Code | 相同邏輯散落各處 | Extract Function |
| Comments | 用註釋解釋複雜程式碼 | 抽為函式或用斷言說明 |
註釋往往像「除臭劑」,用來掩飾程式碼的低劣品質。
如果你需要寫註釋來解釋程式碼,不如先試著重構它。
函式與參數 (Functions & Parameters)#
函式應專注於「做什麼(What)」而非展示冗長的「如何做(How)」。
| Code Smell | 症狀 | Refactoring |
|---|---|---|
| Long Function | 函式過長 | 拆分成小函式並命名 |
| Long Parameter List | 參數過多 | 組合成 Parameter Object |
資料與變數管理 (Data Handling)#
資料的範圍與型態管理不當,是導致系統脆弱的主因。
| Code Smell | 症狀 | Refactoring |
|---|---|---|
| Global Data | 資料可在任何地方被修改 | 用存取函式封裝 |
| Mutable Data | 變數頻繁變動,作用域大 | 封裝變數,查詢與修改分離 |
| Data Clumps | 資料總是成群出現 | 提取為獨立類別 |
| Primitive Obsession | 過度使用基本型態代表業務概念 | 改為 Value Object |
變更的擴散與隔離 (Change Prevention)#
當需求變更時,好的設計應能將修改範圍限縮在單一模組內。
| Code Smell | 症狀 | Refactoring |
|---|---|---|
| Divergent Change | 一個類別因不同原因被不同方式修改 | 拆分成不同類別,建立領域邊界 |
| Shotgun Surgery | 一個修改需更動多個類別 | 搬移函式,將相關邏輯集中 |
- 發散式修改是「一個類別受多種變化影響」
- 散彈式修改是「一種變化影響多個類別」
flowchart LR
subgraph divergent ["發散式修改 (Divergent Change)"]
direction TB
D1[變化 A] --> C1[類別 X]
D2[變化 B] --> C1
D3[變化 C] --> C1
end
subgraph shotgun ["散彈式修改 (Shotgun Surgery)"]
direction TB
S1[變化 X] --> T1[類別 A]
S1 --> T2[類別 B]
S1 --> T3[類別 C]
end類別與物件導向濫用 (Object-Orientation Abusers)#
這些味道顯示程式碼未能有效利用物件導向的特性。
| Code Smell | 症狀 | Refactoring |
|---|---|---|
| Repeated Switches | 相同 Switch 邏輯散落各處 | 使用多型取代條件判斷 |
| Large Class | 類別有太多欄位或職責 | 拆解為子類別或元件 |
| Alternative Classes | 類似功能但介面不同 | 提取共同父類別 |
| Refused Bequest | 子類別只用父類別一小部分功能 | 改用委託 (Delegation) |
| Temporary Field | 欄位只在特定情況有值 | 提取為新類別 |
模組間的耦合 (Coupling)#
模組間應低耦合,避免過度親密或不必要的傳話。
| Code Smell | 症狀 | Refactoring |
|---|---|---|
| Feature Envy | 函式對別的類別資料更感興趣 | 將函式移到資料所在類別 |
| Message Chains | 層層呼叫如 a.getB().getC() | 隱藏委託或抽取成函式 |
| Middle Man | 過半函式只是委託給另一類別 | 移除中間人,直接溝通 |
| Insider Trading | 模組間過度隱晦交換資料 | 搬移函式或用中介模組 |
其他可疑之處 (Others)#
| Code Smell | 症狀 | Refactoring |
|---|---|---|
| Loops | 用傳統迴圈處理集合 | 使用 Pipeline(filter、map) |
| Lazy Element | 目前沒作用的函式或類別 | Inline 消除 |
| Speculative Generality | 為未來設計的複雜機制 | 移除(遵循 YAGNI) |
| Data Class | 只有欄位和 Getter/Setter | 將相關行為搬入類別 |
如何看待壞味道
識別壞味道不是為了追求完美,而是為了風險管理。
當你聞到味道時,不代表必須立即修改,但它是個強烈訊號,
告訴你這裡未來可能會出問題,或這裡是難以維護的熱點。