🎯 什麼情境該想到我

分工:這張是分診表——聞到味道後先處理哪個、交給哪個工具自動抓。24 種壞味道逐一的偵測方法、Go 裡「其實不是味道」的情況、對應重構手法 → reference/重構/(例:過長函式、重複程式碼、神秘命名、發散式變化、霰彈式修改、依戀情結)。

當你覺得某段程式碼「怪怪的、很髒」但講不出具體問題,或想判斷「該從哪裡開始重構」時。

⚙️ 怎麼用(把直覺變成可指認的清單)

常見壞味道 → 對應重構方向:

  • 重複程式碼 → 提煉並共用
  • 過長函式 / 過大類別 → 拆小(工具-提煉函式)
  • 過長參數列 → 包成物件
  • 發散式變化(一個類別因多種原因而改)/ 霰彈式修改(一個改動要動很多處)→ 重新歸位職責
  • 依戀情結(方法愛用別的類別資料)→ 搬移函式
  • 資料泥團 / 基本型別偏執 / 過度耦合的訊息鏈

★ 分診:先處理哪一種?

聞到很多味道時,依「擴散速度 × 修改頻率」排序:

優先味道為什麼先處理
🔴 高重複程式碼、霰彈式修改每次改需求都要改好幾處,漏改就是 bug。成本會隨時間線性增加
🔴 高神秘命名最便宜(工具一鍵改名)、效果最立即;而且改名本身常揭露其他問題
🟡 中過長函式、發散式變化、依戀情結讓改動變難,但不會自己擴散;在要改那一塊時順手處理
🟢 低冗員元素、誇誇其談通用性讀起來煩,但不會造成 bug
⚪ 放著在不會再改的程式碼裡的任何味道重構的回報來自「之後的修改變便宜」;不改的程式碼沒有回報

最後一列最重要:分診的第一個問題永遠是「這段程式碼接下來會被改嗎?」用 git log --since=6.months --format= --name-only | sort | uniq -c | sort -rn | head 找出最常被改的檔案,味道 × 修改頻率高的才是真正的熱點。

★ 讓工具幫你聞:golangci-lint 的對應

以下 linter 名稱依 golangci-lint 官方清單(2026-09-25 查證):

味道linter抓什麼
過長函式funlen函式行數/敘述數超過門檻
過長函式(複雜度)gocyclo、gocognit、nestif圈複雜度、認知複雜度、if 巢狀深度
重複程式碼dupl語法樹層級的重複片段
基本型別偏執(魔術值)goconst、mnd重複出現的字串字面量、裸數字
冗員參數unparam永遠傳同一個值、或從不使用的參數
全域資料gochecknoglobals套件層級變數

用法建議:門檻不要一次設嚴。先用現況的第 90 百分位當門檻,只擋「新增的更糟的程式碼」,再逐步收緊(這個做法見 工具-破窗理論與軟體熵 的棘輪)。

TS 對應:ESLint 的 complexity、max-depth、max-lines-per-function、max-params 規則;重複偵測可用 jscpd。

★ 工具抓不到的味道

linter 只能看單一檔案的語法。跨檔案、跨時間的味道只能靠人:

  • 霰彈式修改:看 PR 的檔案清單——一個小需求動了 12 個檔案,就是它。
  • 發散式變化:git log 看同一個檔案被多少種不相關的需求改過。
  • 依戀情結:方法裡 other.X、other.Y、other.Z 比 s. 還多。

🧪 我實際套用的紀錄

  • 2026-07-14:(待填)

⚠️ 注意

  • 聞到味道不代表一定要立刻改;權衡成本與風險,並確保有測試。

  • 把 linter 的數字當成目標,程式碼會變醜:為了把 funlen 壓到 40 行而硬切出只被呼叫一次、名字說不清的函式,是把「過長函式」換成了「過度間接」。linter 是提醒你去看一眼,不是裁決。

🔗 相關工具

  • 工具-小步重構 —— 聞到味道之後的行動節奏,一次改一小塊
  • 工具-提煉函式 —— 對付最常見那幾種味道(過長函式、重複程式碼)的主力手法