Files
review/references/smells.md
T
2026-08-21 14:29:47 +08:00

159 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 程式碼壞味道審查清單
來源:《Refactoring》(Martin Fowler)與《A Philosophy of Software Design》(John Ousterhout,第 6 組)。
`code-review` 技能依此清單分六組審查,每組由一個 sub agent 執行。
每項格式:**定義、偵測訊號、建議重構手法**。
## 第 1 組:結構與體積問題(Bloaters)
### 1.1 巨型類別(Large Class)
- **定義**:單一 Class 數千行,包山包海,違反單一職責原則。
- **偵測訊號**:類別行數過大(> 500 行需注意、> 1000 行必列);欄位過多;方法分成多個互不相干的群組;類別名稱含 Manager、Util、Helper 等萬用字。
- **建議重構手法**:Extract Class、Extract Subclass、Extract Interface;依職責拆分後以組合取代繼承。
### 1.2 臃腫函式(Long Method)
- **定義**:一個 Function 寫了幾百行,邏輯複雜到沒人敢動。
- **偵測訊號**:函式行數 > 50 行需注意、> 100 行必列;一個函式內有多段以空行或註解分隔的「段落」;區域變數過多;圈複雜度高。
- **建議重構手法**:Extract Function(每個段落抽成具名函式)、Replace Temp with Query、Decompose Conditional、Replace Function with Command。
### 1.3 過長參數列(Long Parameter List)
- **定義**:傳入超過 4、5 個以上的參數,呼叫時極易傳錯。
- **偵測訊號**:參數 ≥ 5 個必列、4 個需注意;連續同型別參數(例:三個 string 相鄰);boolean 旗標參數;呼叫端常以固定組合傳值。
- **建議重構手法**:Introduce Parameter Object、Preserve Whole Object、Replace Parameter with Query、Remove Flag Argument。
### 1.4 基本型態偏執(Primitive Obsession)
- **定義**:不用物件包裝,全部用字串或數字代替(金額、電話、狀態碼、範圍等)。
- **偵測訊號**:以 string/int 表達有格式或規則的概念;同一組基本型態欄位在多處一起出現;以字串常數當型別碼;到處重複的驗證邏輯。
- **建議重構手法**:Replace Primitive with Object、Replace Type Code with Subclasses、Introduce Parameter Object、Extract Class。
## 第 2 組:可讀性與命名問題(Obscurity)
### 2.1 神秘命名(Mysterious Name)
- **定義**:使用毫無意義的變數名(a、tmp、data2、doStuff)。
- **偵測訊號**:單字母或縮寫命名(迴圈索引除外);名稱與實際行為不符;需要讀實作才知道用途;名稱含編號(data1、data2)。
- **建議重構手法**:Rename Variable、Rename Function、Rename Field;名稱表達「意圖」而非「實作」。
### 2.2 魔術數字(Magic Numbers)
- **定義**:程式碼中直接出現沒人知道代表什麼的字面值。
- **偵測訊號**:條件式或運算中出現裸數字、裸字串(0、1、-1、空字串與明顯單位換算除外需判斷);同一字面值在多處出現。
- **建議重構手法**:Replace Magic Literal(抽成具名常數或 enum);有行為就升級為 Replace Type Code with Class。
### 2.3 死碼(Dead Code)
- **定義**:已經不用卻不刪除的註解掉程式碼、變數、函式或類別。
- **偵測訊號**:被註解掉的程式碼區塊;無人呼叫的函式、類別(grep 全庫零引用);永遠不成立的條件分支;未使用的 import、參數、變數。
- **建議重構手法**:Remove Dead Code(直接刪除,歷史交給版本控制)。
### 2.4 過度註解(Comments)
- **定義**:因為程式碼寫太爛,只好寫一堆註解來解釋邏輯;好的程式碼應該能「自我解釋」。
- **偵測訊號**:註解在解釋「這段在做什麼」而非「為什麼這樣做」;註解與程式碼不同步;每隔幾行就一條註解;用註解分段的長函式(同時列 1.2)。
- **建議重構手法**:Extract Function(用函式名取代註解)、Rename(用命名取代註解)、Introduce Assertion;保留「為什麼」與外部限制類註解。
- **注意**:本項與「註解問題」(第 5 組)互補:刪除解釋性廢話註解,補齊第 5 組要求的介面契約註解,兩者不衝突。
## 第 3 組:耦合與設計問題(Couplers)
### 3.1 依賴嫉妒(Feature Envy)
- **定義**:A 類別的函式不斷去存取、修改 B 類別的資料,顯示該函式放錯地方。
- **偵測訊號**:函式內對別的物件的 getter/setter 呼叫次數多於對自身成員的使用;連續鏈式取值後運算(`order.customer.address.city`)。
- **建議重構手法**:Move Function(搬到資料所在的類別)、Extract Function 後再 Move。
### 3.2 散彈式修改(Shotgun Surgery)
- **定義**:每當要修改一個小功能,就必須同時修改多個不同的檔案。
- **偵測訊號**:本次 diff 為了單一需求橫跨多檔做同質小改動;同一常數或規則散落多處;歷史上同組檔案總是一起被改。
- **建議重構手法**:Move Function / Move Field 集中職責、Combine Functions into Class、Inline Function 後重新抽取。
### 3.3 發散式變化(Divergent Change)
- **定義**:一個類別因為各種完全不相關的原因需要被修改。
- **偵測訊號**:類別的修改歷史來自多種不相干需求;類別內方法可依「變更原因」分成多群;「如果要加 X 就改這裡,要加 Y 也改這裡」。
- **建議重構手法**:Split Phase、Extract Class、Move Function,讓每個模組只有一個變更理由。
### 3.4 親密關係(Inappropriate Intimacy)
- **定義**:兩個類別過度了解彼此的私有實作細節。
- **偵測訊號**:存取對方的 private/internal 成員或繞過封裝(反射、friend、直接操作內部集合);雙向依賴;子類別依賴父類別實作細節。
- **建議重構手法**:Move Function / Move Field、Change Bidirectional to Unidirectional、Hide Delegate、Replace Subclass with Delegate。
## 第 4 組:邏輯與壞習慣(Dispensables & Others)
### 4.1 重複程式碼(Duplicated Code)
- **定義**:到處複製貼上(Copy-Paste),改一個 Bug 要改好幾個地方。
- **偵測訊號**:相同或僅參數不同的程式片段出現 ≥ 2 處;兄弟類別有相同方法;diff 中新增的程式碼與既有程式碼雷同。
- **建議重構手法**:Extract Function、Pull Up Method、Form Template Method、Slide Statements 後合併。
### 4.2 巢狀地獄(Nested If Hell)
- **定義**:If-Else 或 Loop 疊了 5、6 層以上,箭頭型程式碼(Arrow Anti-pattern)。
- **偵測訊號**:縮排深度 ≥ 4 層需注意、≥ 5 層必列;else 鏈過長;條件式中混合多個否定。
- **建議重構手法**:Replace Nested Conditional with Guard Clauses(衛語句早退)、Decompose Conditional、Replace Conditional with Polymorphism、Extract Function。
### 4.3 誇誇其談未來性(Speculative Generality)
- **定義**:為了解決「未來可能」會用到的功能,寫了一堆現在用不到的複雜架構。
- **偵測訊號**:只有一個實作的抽象層或介面;從未被覆寫的 hook 方法;只在測試中使用的參數或彈性;「以後可能會需要」的註解。
- **建議重構手法**:Collapse Hierarchy、Inline Function / Inline Class、Remove Dead Code、移除未用參數(Change Function Declaration)。
### 4.4 吞掉異常(Swallowed Exceptions)
- **定義**:try-catch 裡面留白,發生錯誤時直接隱瞞,導致難以 Debug。
- **偵測訊號**:空的 catch 區塊;catch 後只留註解或 `// ignore`;catch 住廣義 Exception 後回傳 null 或預設值而不記錄;錯誤訊息被丟棄後重包。
- **建議重構手法**:最少要記錄(log)並保留原始例外鏈;能處理才 catch,不能處理就往上拋;以 Introduce Special Case 取代以 null 掩蓋錯誤。
## 第 5 組:註解問題(介面契約註解)
此組檢查「該有而沒有」的註解,與 2.4(該刪的註解)互補。
### 5.1 方法描述
- **定義**:每個公開方法要有一句話描述,並標明所屬層級:**顯示層、邏輯層、存取層**。
- **偵測訊號**:公開方法無描述;描述未標層級;描述與方法實際行為不符。
- **建議重構手法**:補上單句描述與層級標記;若一個方法橫跨多層,先依 Split Phase 拆分再各自標註。
### 5.2 輸入參數說明
- **定義**:所有輸入參數都要有用途說明。
- **偵測訊號**:參數無說明;說明只是重複參數名稱;可選參數未說明預設行為。
- **建議重構手法**:逐參數補「用途」說明;參數多到說明困難時同時列 1.3 並建議 Introduce Parameter Object。
### 5.3 輸出說明
- **定義**:輸出(回傳值)要說明回傳的資料內容概要。
- **偵測訊號**:無回傳說明;未說明 null、空集合、錯誤時的回傳;回傳布林但未說明 true/false 意義。
- **建議重構手法**:補回傳內容概要與邊界情況說明。
### 5.4 輸入與輸出範例
- **定義**:輸入與輸出參數都必須有範例;範例內容**優先嘗試從資料庫取得真實資料,失敗才透過邏輯推理**產生。
- **偵測訊號**:註解缺範例;範例與型別不符;範例顯然是佔位假資料而環境可取得真實資料。
- **建議重構手法**:以可用的連線查詢一筆代表性資料當範例(去識別化,不可含個資);無法連線才以邏輯推理造出合理範例並標明為推理值。
### 5.5 巢狀結構註解
- **定義**:如果參數有巢狀結構(例如 class 內還有 class),就必須完全補齊每一層的註解。
- **偵測訊號**:DTO/ViewModel 僅頂層有註解;內層類別、集合元素型別的欄位無說明或無範例。
- **建議重構手法**:逐層補齊 5.1–5.4;巢狀過深(≥ 3 層)時同時評估 Extract Class 是否被濫用。
## 第 6 組:淺模組(Shallow Module)
- **定義**:介面複雜度相對於功能深度過高的模組——使用它要懂的事,跟自己寫差不多(出自《A Philosophy of Software Design》:好模組要「介面簡單、實作深」)。
- **偵測訊號**:只有一行轉呼叫(pass-through)的方法或類別;包裝層與被包裝者介面幾乎相同;參數原封不動往下傳的層層委派;為每個底層方法都開一個對應方法的「殼」。
- **建議重構手法**:Inline Class / Inline Function 移除殼層;或反向加深模組——把散在呼叫端的邏輯(驗證、轉換、錯誤處理)收進模組內,讓介面吸收複雜度。
## 嚴重度分級
| 級別 | 意義 | 例 |
| --- | --- | --- |
| 高 | 會造成錯誤或已阻礙修改 | 吞掉異常、重複程式碼改漏、死碼誤導 |
| 中 | 持續增加維護成本 | 巨型類別、臃腫函式、巢狀地獄、Couplers 全組 |
| 低 | 可讀性與一致性 | 命名、魔術數字、註解缺漏、淺模組 |