註解與清理:寫為什麼,刪掉噪音
什麼註解該留、什麼該刪、AI 產生的註解為什麼特別多噪音,以及一份可以直接用的清理流程。
1. 註解寫「為什麼」,不寫「做什麼」
程式碼已經說了它在做什麼。註解要說的是讀者從程式碼看不出來的事。
// ✗ 噪音
// 設定 loading 為 true
setLoading(true);
// ✗ 噪音
// 遍歷所有項目
items.forEach(...);
// ✓ 有價值:解釋一個不明顯的決定
// 這裡用 replaceState 而不是 pushState:打字篩選會觸發很多次,
// 用 push 的話使用者要按 20 下上一頁才離得開這一頁。
goto(url, { replaceState: true });
// ✓ 有價值:記錄一個外部限制
// Safari 17 以前 backdrop-filter 在 position: sticky 上會失效,
// 所以這裡多包一層。移除前請先在 Safari 實測。
2. 值得留下的五種註解
| 類型 | 例子 |
|---|---|
| 為什麼是這個做法 | 「用 cookie 不用 localStorage,因為伺服器要在渲染前知道主題」 |
| 外部限制 | 「上游 API 的分頁從 1 開始,不是 0」 |
| 踩過的坑 | 「:where() 是必要的,否則主題會被預設值蓋掉」 |
| 不明顯的順序依賴 | 「這行必須在 hydration 之後,否則 SSR 會不一致」 |
| 刻意的取捨 | 「這裡故意不做快取:資料每次都要最新」 |
3. 該刪的六種註解
- 翻譯程式碼的:
// 加一配count++ - 過期的:講的是三個版本前的行為
- 被註解掉的程式碼:git 記得,你不需要記得
- 自動產生的樣板:
/** * @param x - x */ - 裝飾性分隔線:
// ==========(除非真的在分區,而且一致) - 道歉:
// 這裡很醜,之後再改—— 要嘛現在改,要嘛寫清楚為什麼現在不能改
4. AI 產生的註解為什麼特別多噪音
模型會逐行解釋,因為那讓輸出看起來完整。結果是每行程式碼配一行中文翻譯,讀起來像教科書,實際資訊量是零 —— 而且它會很快過期,因為之後改程式碼的人不會同步改註解。
給 agent 的規則,可以直接貼:
註解規則:
- 不要逐行解釋程式碼在做什麼
- 只在「讀者看不出為什麼要這樣寫」的地方加註解
- 不要加 JSDoc 樣板,除非那是公開 API
- 不要留被註解掉的程式碼
- 一個檔案的註解密度不要超過 15%
5. 清理流程
一次做一種,不要混在一起。每一步都是獨立的 commit,這樣壞掉的時候找得回來。
第一輪:刪除
# 被註解掉的程式碼
grep -rn "^\s*// *\(const\|let\|function\|if\|return\|import\)" src/
# TODO / FIXME 盤點
grep -rn "TODO\|FIXME\|XXX\|HACK" src/
# 沒用到的檔案
npx knip
TODO 的處理原則:要嘛現在做,要嘛開 issue 然後把編號寫進註解。沒有編號的 TODO 是永遠不會做的 TODO。
第二輪:命名
- 布林用
is/has/should開頭 - 函式用動詞開頭
- 不要縮寫(
usrbtncfg省下的字元不值得) - 不要
datainfomanagerhelperutils這種沒有資訊的名字
第三輪:結構
- 一個函式一件事
- 提早 return,減少巢狀
- 把 magic number 變成有名字的常數
第四輪:型別
- 消滅
any - 消滅用來閉嘴的
as - 布林爆炸換成 union
6. 讓 AI 重寫註解
請重新處理 <檔案> 的註解,規則如下:
1. 刪掉所有只是在翻譯程式碼的註解
2. 刪掉所有被註解掉的程式碼
3. 對於「為什麼這樣寫不明顯」的地方,補上一句說明為什麼
4. 保留所有記錄外部限制與踩坑經驗的註解
5. 不要改任何實際的程式碼行為
6. 改完告訴我你刪了幾行、加了幾行
如果某個地方你自己也看不出來為什麼要這樣寫,不要猜,列出來問我。
最後一句很重要。模型猜出來的「為什麼」如果是錯的,比沒有註解更糟 —— 它會誤導下一個人。
7. 檢查清單
- 沒有逐行翻譯程式碼的註解
- 沒有被註解掉的程式碼
- 每個 TODO 都有編號或負責人
- 註解講的是為什麼,不是做什麼
- 沒有過期的註解
- 公開 API 有說明,內部函式不需要樣板
- 命名沒有無意義的縮寫