Code review 回饋要按處理成本分級,能機械化的交給 CI
Reviewer 給的 severity 標籤(Warning / Nit)說的是嚴重度,不是處理方式。真正該分的是處理成本:有些回饋改 code 就好,有些根本不該由人來講。
一輪 23 條人類 review(cupcake !2030 17 條、souffle 7 條)按處理成本分完,結果是約一半可以用確定性檢查收掉。
五級
| 級 | 定義 | 典型例子 |
|---|---|---|
| L1 Blocker | 合進去會壞在別人身上 | DTO 收 en_us,契約仍寫 en-US,前端照契約送會 400 |
| L2 Correctness | 行為錯,範圍有限 | decimal string 轉 float 寫進 Excel,精度掉了導致未修改的列被判定有變更 |
| L3 Spec 與部署前置 | code 沒錯,功能不完整 | i18n key 不在本地 json,使用者看到原文 key |
| L4 測試在說謊 | 測試過了但什麼都沒驗證 | mock 定義了沒斷言;expect(CONST).toBe('ALL') 永遠過 |
| L5 Nit | 改了較好,不改不會壞 | 別名常數、log 完又 rethrow、型別複本 |
三個反直覺的地方
L1 與 L3 的正確處理不是改 code。 L1 是契約漂移,要等上游契約合併或推動它先合;L3 是部署前置條件,屬於 checklist。把這兩類當成 code 問題在改,會繞很多圈。
L4 要單獨成一級,而且要最先修。 它不是測試品質問題,是上面四級都無法被擋住的原因——CI 全綠不代表沒事,只代表測試沒在看。
L5 最多但最便宜。 23 條裡 11 條是 L5。它佔掉 reviewer 的注意力,而它正好是 linter 最擅長的那一類。
哪些能機械化
| 收掉的級 | 工具 | 成本 |
|---|---|---|
| L1 | 契約一致性 script:assert DTO 的 enum 與欄位名是契約 YAML(某個 pinned SHA)的子集 | 中,報酬最高 |
| L3 i18n | key 存在性檢查:掃 key literal 對本地 i18n json | 低 |
| L4 | eslint-plugin-vitest / -jest 的 expect-expect、no-standalone-expect、no-conditional-expect;自訂規則禁止測試檔用 as 蓋掉必填 prop、禁止 vi.mock data hook | 最低,先做這個 |
| L5 重複定義 | jscpd 抓複製貼上、knip 抓重複與未使用的 export | 低 |
| L5 未使用 | no-unused-vars + CI 跑 tsc --noEmit | 極低 |
不必為此蓋新基礎建設:一個已經在跑 lint + type-check + 自訂 script 的 CI job,就是這些檢查的位置。
收不掉的
行為型的 L2(快取跨 remount 殘留、revalidate 後狀態不一致)要靠真 hook + MSW 的測試,不是 linter。L3 的 spec 符合度要從規格衍生驗收測試。L5 裡「這個抽象過頭了」是判斷題。
這是 規格檢查要機械化進 CI,不能靠紀律 在 review 層的同一招:靠紀律的檢查遲早會漏,變紅的才不會。分級的另一半是決定哪些要親自讀,見 把 codebase 想成一棵樹:trunk 與 leaf 的分界是有沒有被隔開 與 PR 要自帶證據,證據是少讀 code 的交換條件。