Files
jiantw83 f6cd1f9ef7 feat(api-doc): 範例只掛純量成員,類別型往下遞迴
- 使用者要求改判準。類別型參數與中間層的類別屬性只留說明,範例責任往下推給屬性,一路走到最內層的純量。
- 一個值只有一個出處。範例掛在父層,屬性一改就過期,讀的人拿到的是沒有屬性定義背書的一份資料。
- 集合看元素型別,不看外殼;字典看值型別。字串集合與字串判出來一樣,位址集合與位址判出來也一樣。
- 壞味道清單原本寫「輸入與輸出參數都必須有範例」,與新判準衝突,兩支技能會對同一段程式碼給出不同標準。一併改成同一套。
- 說明與範例的兩份檢核表併成一個 sub agent,同一批資料模型檔只讀一次。
- 順帶補上偵測腳本結束碼二漏掉的一個原因:環境缺 grep。原本只寫參數與路徑,遇到這個原因換路徑重跑永遠清不掉。
2026-08-31 11:07:05 +08:00

17 KiB
Raw Permalink Blame History

程式碼壞味道審查清單

來源:《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 組要求的介面契約註解,兩者不衝突。

2.5 文件編號夾帶(Document Reference Leak)

  • 定義:註解寫的是「這件事記在哪份文件」,不是「為什麼這樣寫」。編號會過期、會搬家、會在存取權限外,讀程式碼的人查不到,只剩一串無意義的代號。
  • 偵測訊號:註解含議題編號(// #123、// ABC-123)、wiki 頁編號(// PLAN_A1B2C3D4)、工作包編號(// WP-01)、commit hash(// 見 commit a1b2c3d)、@ 提及(// @someone 認領)、外部文件連結(Confluence、Notion、Google Docs)。完整禁止清單、允許清單與適用範圍看 references/comment-scope.md。
  • 建議重構手法:把編號指向的內容搬進註解,再刪掉編號;搬不動就代表那件事不該用註解表達,改寫進文件。
  • 注意:本項與 2.4、第 5 組分工明確:2.4 刪解釋性廢話,第 5 組補介面契約,2.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 拆分再各自標註;條列長到十項以上,代表方法本身太肥,同時列 1.2 並建議 Extract Function。

5.2 輸入參數說明

  • 定義:所有輸入參數都要有用途說明。
  • 偵測訊號:參數無說明;說明只是重複參數名稱;可選參數未說明預設行為。
  • 建議重構手法:逐參數補「用途」說明;參數多到說明困難時同時列 1.3 並建議 Introduce Parameter Object。

5.3 輸出說明

  • 定義:輸出(回傳值)要說明回傳的資料內容概要。回傳值是自訂資料模型,而註解格式支援導向時(XML 的 <see cref>、JSDoc 的 {@link}),說明要附上該資料模型的檔案連結,讀的人一鍵就跳到定義。
  • 偵測訊號:無回傳說明;未說明 null、空集合、錯誤時的回傳;回傳布林但未說明 true/false 意義;回傳自訂資料模型卻只寫型別名稱純文字,沒有 <see cref> 或 {@link} 導向。
  • 建議重構手法:補回傳內容概要與邊界情況說明;自訂資料模型改用註解格式的導向標籤指到型別定義,泛型集合指到元素型別。

5.4 輸入與輸出範例

  • 定義:輸入與輸出參數都必須有說明,範例則只掛在純量成員上。純量參數與純量屬性(字串、數值、布林、日期、列舉)要有範例。類別型參數與中間層的類別屬性只要說明,自己不掛範例,範例責任往下推給該類別的屬性。集合看元素型別判斷:元素是純量就比照純量,附一份列出幾個元素的範例;元素是類別就比照類別,只留說明,往下走進元素型別。字典看值型別,判準相同。範例內容優先嘗試從資料庫取得真實資料,失敗才透過邏輯推理產生。
  • 偵測訊號:純量參數或純量屬性缺範例;範例與型別不符;範例顯然是佔位假資料而環境可取得真實資料;範例掛在類別型成員上,該類別的屬性卻一個範例都沒有。
  • 建議重構手法:以可用的連線查詢一筆代表性資料當範例(去識別化,不可含個資);無法連線才以邏輯推理造出合理範例並標明為推理值;範例掛錯層就往下搬到該類別的每個純量屬性上。
  • 注意:型別判準與 jsc-review:api-doc 的檢核表 A 完全相同,同一個成員在兩邊判出來的答案一樣。分工不變:本項只看原始碼註解,Swagger 文件屬性歸 api-doc,同一個標的不重複回報。

5.5 巢狀結構註解

  • 定義:如果參數有巢狀結構(例如 class 內還有 class),就必須完全補齊每一層的註解。每一層的純量屬性要有說明與範例,中間層的類別屬性只要說明;集合往元素型別走,遞迴走到「屬性全是純量」那一層為止。
  • 偵測訊號:DTO/ViewModel 僅頂層有註解;內層類別、集合元素型別的欄位無說明;任一層的純量屬性缺範例;遞迴半途停住,某一層的屬性完全沒被走到。
  • 建議重構手法:逐層補齊 5.1–5.4;巢狀過深(≥ 3 層)時同時評估 Extract Class 是否被濫用。

5.6 內含功能的導向連結

  • 定義:一個功能內部呼叫了其他功能時,註解要交代呼叫了誰、為什麼呼叫。註解格式支援導向就用導向標籤(XML 的 <see cref>、JSDoc 的 {@link}),讓 IDE 直接跳過去。
  • 偵測訊號:方法內呼叫其他公開方法或服務,註解卻完全沒提;提了但只寫純文字方法名,沒有 <see cref> 或 {@link};寫了連結卻沒寫用途,讀的人還是要自己點進去猜。
  • 建議重構手法:在條列步驟裡把被呼叫的功能改成導向標籤,後面接一句用途;被呼叫的功能多到列不完,代表這個方法在當協調中心,同時列 1.2 或 3.1 評估職責搬移。
  • 注意:註解格式不支援導向(例如純 // 行註解)就只寫用途,不硬造連結字串。

5.7 XML 註解標籤排版

  • 定義:註解採 XML 格式時,起始標籤與結束標籤各自獨立一行,內容夾在中間。這樣多行內容、條列與巢狀標籤才排得整齊,diff 也只動到真正改的那幾行。
  • 偵測訊號:起始標籤、內容、結束標籤擠在同一行(例:/// <summary>取得訂單</summary>);結束標籤跟在內容尾巴後面沒有換行(例:/// 取得訂單</summary>);<param>、<returns>、<remarks>、<example> 同樣擠成一行;巢狀標籤(<list> 內含 <item>)沒有逐層換行與縮排。
  • 建議重構手法:把每個標籤拆成三行——起始標籤一行、內容一行或多行、結束標籤一行;巢狀標籤逐層縮排;<param> 這類帶屬性的標籤,屬性留在起始標籤那一行。
  • 注意:只針對 XML 格式註解。JSDoc、docstring 沒有結束標籤,不適用本項。

5.8 專有名詞與變數標示

  • 定義:註解裡提到程式碼元素時,要用該語言註解格式的標示語法標起來,不能混在純文字裡。標示的目的是讓 IDE 與文件產生工具讀得懂,並在文件上呈現成程式碼樣式。

  • 偵測訊號:XML 註解裡直接以純文字寫參數名、型別名、成員名或程式碼字面;JSDoc 或 docstring 裡以純文字寫變數名與型別名,沒有加反引號;標錯類別(例如拿 <c> 標參數名、拿 <paramref> 標型別)。

  • 建議重構手法:依語言慣例補標示。

    註解格式 對象 標示語法
    XML 變數、參數 <paramref name="orderId" />
    XML 型別、成員 <see cref="OrderService.GetOrder" />
    XML 程式碼字面、列舉值 <c>null</c>
    JSDoc、docstring 變數、型別、程式碼字面 反引號 `orderId`
  • 注意:標示規則跟著語言慣例走,不是跟著個人喜好走。IDE 的跳轉與文件工具的交叉引用都靠這些標籤,標錯等於沒標。

第 6 組:淺模組(Shallow Module)

  • 定義:介面複雜度相對於功能深度過高的模組——使用它要懂的事,跟自己寫差不多(出自《A Philosophy of Software Design》:好模組要「介面簡單、實作深」)。
  • 偵測訊號:只有一行轉呼叫(pass-through)的方法或類別;包裝層與被包裝者介面幾乎相同;參數原封不動往下傳的層層委派;為每個底層方法都開一個對應方法的「殼」。
  • 建議重構手法:Inline Class / Inline Function 移除殼層;或反向加深模組——把散在呼叫端的邏輯(驗證、轉換、錯誤處理)收進模組內,讓介面吸收複雜度。

嚴重度分級

級別 意義 例
高 會造成錯誤或已阻礙修改 吞掉異常、重複程式碼改漏、死碼誤導
中 持續增加維護成本 巨型類別、臃腫函式、巢狀地獄、Couplers 全組
低 可讀性與一致性 命名、魔術數字、註解缺漏、文件編號夾帶、淺模組、XML 標籤排版、專有名詞未標示

第 5 組新增項目的級別另外標明,避免全部壓在「註解缺漏」一句話裡:

項目 級別 理由
5.1 缺條列式步驟與規則 中 讀的人要重讀整個實作才知道規則,維護成本直接上升
5.3 回傳值缺資料模型連結 低 型別名稱還查得到,只是多花幾秒
5.6 缺內含功能的導向連結 低 同上,影響的是查找速度
5.7 XML 標籤排版擠在同一行 低 純排版,不影響語意
5.8 專有名詞未標示或標錯 低 影響 IDE 跳轉與文件呈現,不影響執行