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 i18nkey 存在性檢查:掃 key literal 對本地 i18n json低
L4eslint-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 的交換條件。