一個模型不夠
從 PR 到 spec 的多視角 review。
review 的盲點來自視角。
換更強的模型不保證補齊;要改的是餵給 reviewer 的材料與立場。
今天講的是流程,不現場操作。例子來自近一個月自己團隊的 review 紀錄,已去敏:不出現 repo、PR 號、作者與業務名詞。
同一份報告裡的兩條 finding
一份大 PR 的 review 報告,幾十條 finding。挑兩條。
登入後的落點少了一道權限判斷,某類使用者登入直接看到 404。
四個視角都抓到。交叉驗證時去 repo 裡找同型的判斷,別處都有、只有這裡缺,所以成立。
事件本身很小:一條測試是綠的,但它走的不是名字說要測的那條路,等於沒測到東西。
重點在過程:初審提出 → 複查判 REFUTED(被反證推翻) → 重驗翻回 CONFIRMED(成立)。複查者拿的環境跟被測程式不同,結論就反了。
這條測試實際走的是另一個分支,測不到它想測的東西
在一般環境試,判斷式成立,說測試沒錯
用測試實際跑的環境重跑一次,log 證明走錯分支。複查者的環境跟被測程式不同
哪條更能說明 review 該怎麼設計?
第二條。驗證者也會錯。今天講的流程,一半在防 reviewer 錯,另一半在防「驗 reviewer 的人」錯。
一席餵滿,一席刻意餓著
想要不同視角,就餵不同的材料、給不同的立場。換更強的模型不保證做到。
主動塞給它:spec 全文、這次改動會影響到誰(用依賴關係算出來)、repo 的慣例文件、共同 checklist。有關聯的後端 PR 時,連那份 diff 也一起給。
不給 spec、不給慣例、不給 checklist,只給 diff 和它原生的 review 規則。流程明文禁止多塞一句提示。
它仍然可以讀程式、跑測試。差別在沒人告訴它什麼是「本次不管」,所以它會問出我們習以為常而沒想到的事。
兩件事會發生
多給的材料會帶來獨有發現。前端這次接的是一個還沒合併的後端改動,前端先上線整欄會空白。只看 diff 的席看不到後端狀態,抓不到;拿到後端 diff 的席抓到了。報告會標這是 lone finding(只有一席抓到),並寫清楚「另一席材料上看不到,合理」。
不同材料下各自命中,才叫共識。兩席材料不同、立場不同,卻指到同一段程式、同一個根因,這條的可信度就高。誠實講:在我們的紀錄裡,餓著的那席很少獨自抓到別人沒抓到的,它的貢獻是獨立命中、形成共識。
三個常設視角、兩個條件視角,每次選
不是每個 PR 都全開。流程先列出席位和推薦,你當次選。推薦不等於必跑,沒回覆不等於接受推薦。
拿到全部材料。依語言選 reviewer。PR 大就切塊,一塊一個 reviewer。
只看 diff,用它原生的 review 規則。新鮮的眼睛。
同一個 Codex、換成紅隊立場:「假設這個 PR 會炸,去找哪裡炸。」
PR 碰到登入授權、請求參數、新的外部 API 時才開。
repo 有寫成 MUST/SHALL 的正式條款,而且對得上這次改的流程才開。口語規格對不上,這席會 SKIPPED。
實際上怎麼選
小 hotfix 三個常設席就夠。大 PR 主審切塊、加 security、兩個 Codex 都開。純後端 PR 可以不開對抗席。看的是這次改動碰到什麼,不是固定套餐。
沒完成的席留 FAILED,不冒充完成、不偷換成別的模型。重試成功就記成功,首次失敗也留著。沒選的席不算未完成,也不算完成。
前後端配對的 PR 可以一起審:各自 worktree、一次選席、一份報告。分開審時 reviewer 看不到對面的 diff,契約對不上判不出來。
初審會錯,複查也會錯
CC 驗 Codex 的 finding,Codex 反過來驗 CC 的。兩邊都被驗。多席都抓到的條目走輕量查證;只有一席抓到的,要多答一題「另一席為什麼漏」。
複查者依序問四個問題
真的觸發得出來嗎?順著程式路徑走,能不能構造一個實際輸入讓它發生?構造不出來就只是「理論上可能」,不算成立。
發生了會怎樣?「多印一個錯誤」「只有開發環境會遇到」算 LOW;能跑任意程式、資料外洩、權限提升、資料寫壞才算 HIGH 以上。初審常把 LOW 抓成 HIGH,這題壓回來。
同樣寫法別處早就有,為什麼只有這條會炸?去整個 codebase 找同一種寫法。已經存在多處、跑很久沒事,就要答出這條的實質差別:多了使用者可控的輸入、跨了信任邊界、範圍變了。答不出就 REFUTED。
這題最會殺誤報,也最容易被濫用:它分得出「新增的回歸」和「早就在的風險」,分不出「沒問題」。既有程式也可能一起錯,REFUTED 時要註明這點。
別的地方擋掉了嗎?中介層、資料庫約束、framework 預設、上游守衛。有就降級,並附上擋掉它的檔案與行號。
任何一題不過,複查者要附反證,不能只寫「我不同意」。REFUTED 不是刪除,是多了一份反證。
兩種錯法
開場第二條。複查者在一般環境驗,被測程式跑在另一種環境,同一個判斷式在兩邊結果相反。驗證環境跟被測程式不同,結論就反了。
初審說「這段設定要到某個時間點才載入,所以這個檢查永遠跳過」。複查判它前提錯。最後用一小段探針程式實測,證明設定在 import 當下就載好了。初審的前提沒查證,整條就不成立。
三道降級閘
報告按最終建議排序:Must Fix、Should Fix、Nice to Have、參考用。不按 severity(模型給的嚴重度)排。CRITICAL 被推翻一樣沉底。三道閘把「聽起來嚴重」壓回「查證過的嚴重」。
Must Fix 要兩個條件同時成立。一,寫得出使用者看得到的重現步驟。二,不修就會壞掉會出貨的東西。死測試、死設定、文件不符通常不單獨構成 Must Fix,依影響列 Should 或 Nice。多席共識也不豁免。
假設性措辭最高只到 Should Fix。「若有人繞過」「假設 API 回 X」這類句子撐不起 Must。例外是不必舉證就該報的類型:硬編的密鑰、SQL 字串拼接、eval 使用者輸入。
嚴重度不能建立在沒查證的前提上。把沒查證過的論據拿掉,等級會不會降?會降就用拿掉後的等級。
為什麼獨立成一條:沒查證的那段往往正是把嚴重度撐高的那段,而嚴重度決定哪幾條會貼給作者。不擋在這裡,最沒根據的 finding 會被系統性選出來送出去。
閘的結果長什麼樣:四份報告合計 138 條 finding,會擋 merge 的 Must Fix 只有 4 條。作者打開報告先看那 4 條。其餘多數是已確認但不擋發布的測試缺口、文件過期,各自保留驗證狀態。最終建議是處理優先序,不是真偽。
第三道閘的來由:某一輪貼給作者的前兩條,都是靠沒查證的論據升上去的。拿掉那段,一條降 Nice to Have、一條不成立。同一輪完整驗證過的幾條全對、全落 Nice to Have,有一條根本沒貼。沒有這道閘,送出去的剛好是最沒根據的。
等級之外還有一欄:處置
每條 finding 除了等級和最終建議,還標一個處置,三選一。auto-fix:修法明確又局部,可以直接修。ask-user:要人決定怎麼修,或不確定該誰負責;拿不準時預設這個。no-op:純觀察,不用動。
auto-fix 是標籤,不是動作。流程明文寫:沒有使用者另外下令,不改 code、不 commit、不 push、不動 PR。標了 auto-fix 的條目會附一段可以直接貼到 PR 的留言,但貼不貼也是人決定,預設不貼。
第二輪對帳只追 auto-fix 和 ask-user 的條目。no-op 不算未結案。
能用程式檢查的,就不留給模型自述
不信 agent 說「都看了」。每一件能機械核對的事,都由程式算、程式寫進報告。
審的是哪一版。程式核對 review 副本的 commit 跟 PR 最新 commit 一樣,才准寫「已審 SHA」。不一樣就標 unverified。
每個檔都有交代。改動檔清單由程式建。每個檔只能是四種狀態之一:有 finding、看過沒問題、刻意跳過、漏掉。四個數字加起來要等於總檔數,漏掉那格要是 0。刻意跳過的檔(例如自動生成檔)要有跳過的理由。
行號重新對位。finding 附一段逐字程式碼,由程式重新找出它在哪一行。模型報的行號常會偏。
兩層報告。完整證據是正本,給人看的主報告由程式從正本投影出來。模型不准重寫或摘要主報告。
發布前有人再看一次。派一個沒參與審查的唯讀 agent,照固定清單挑報告的缺口。它是提醒性質:失敗只註記、不擋發布。
第二輪不是重來。前一輪每條 finding 有固定編號,這輪對帳:修了、還開著、已過時。前一輪內容不餵給新 reviewer,避免先入為主。誠實揭露:我們目前都是首輪,這機制還沒實際用到。
# Code Review 報告 · 已審 SHA ●●●●●●● 變更: N 檔 (源碼/測試/文件/生成檔各幾個) 輸入綁定: verified (副本 commit == PR 最新 commit) 前輪對帳: N-A(沒有前一輪) 選了哪幾席: CC 主審 × N 塊;security;Codex 中性 作者/繼承: N authored / M inherited 覆蓋: 有 finding a / 沒問題 b / 刻意跳過 c / 漏掉 0 行號對位: 精確 x / 模糊 y / 失敗 0 影響範圍: 已算;共用元件有 N 個使用端;已餵給每個 reviewer spec 對照: SKIPPED(沒有正式條款) 各席狀態: 每席一個 PASS/FAILED 發布前稽查: 幾條 PASS;哪幾條 FAIL → 修了沒重跑就寫「修正後未重派」
前半場收:PR 階段的三句
視角要設計:一席餵滿、一席餓著。
找的不准自己驗;初審與複查都會被驗。
拍板靠閘,不靠模型的語氣。
這三句搬到 spec 階段一樣成立,而且更便宜。
spec 階段抓到的,在寫 code 前就能改
PR 階段抓到缺陷,修法是改 code。spec 階段能更早核對兩件事:做的是不是使用者要的、值不值得做。PR 階段也能判範圍,但那時 code 已經寫了。
分五級,賭多大開多重
- 1唱反調自審
在原對話裡問自己,不派 agent改動小、已經很懂這塊 - 2一個 critic
沒看過對話的新 agent跨多檔、新模組、架構沒把握 - 3多個角度平行審,再彙整動核心架構、碰生產資料
- 4找外部模型賭很大的關鍵決策才手動升
- YYAGNI 定價
獨立跑,不與 1 到 4 疊加spec 是模型代寫、要審「值不值得」
三個問題,不派 agent 的地板
直接在原對話自問。只要問對三個問題。
這份 spec 我做了哪 3 個你可能不同意的決策?各附「我選它的理由」和「反方理由」。
我對關鍵變數、範圍、依賴假設了什麼?哪些沒跟你確認過?
哪裡偷工、含糊帶過、留了沒解的問題?
紀律:自己能答的先答完、一次列完,只把真要使用者裁的留下來問。不軟化、不先幫自己辯護。
新 agent 的價值,在它沒被作者的結論污染
派出去的 critic 沒看過我們的對話。這是它的價值,四條守則都在保護這件事。
不夾帶自己的判斷。只給 spec 全文和需求約束,不給自己的評估。prompt 裡一句「我認為這段沒問題」,critic 就會傾向同意。
意圖自己審、架構才外派。「這份 spec 有沒有偏離使用者真正要的」要靠整段對話才判得出來,新 agent 看不到。這題留在原對話做。
finding 對回使用者的理解。使用者說「這個假設不對」,就整批重評。標「因理解修正而撤銷」是合法結果,不是忽略。不硬推原本的解法。
每輪至少一路交兩張表。自洽表和引證表,寫進既有 critic 的輸出格式,不另派 agent。
讓兩個 critic 故意對立
多個角度不是多幾個同樣的人。價值在兩個對立角度的對撞處。
預設懷疑。找過度設計、漏掉的需求、更簡單的替代。抓「該砍什麼」。
簡化派說 X 可以刪,強度派把 X 列為防禦。這種條目標 contested(有爭議)、單獨一段呈現、由人決策,流程不替它判。
假設這份 spec 在某些情境會裂。找邊界情況、隱含假設、回滾、並發、部分失敗。明文「不找過度設計,那是另一支的活」。
另兩個角度
跟 repo 既有決策一致嗎:讀決策紀錄、術語表,看用詞與已決定的事有沒有衝突。
改了之後舊的還能用嗎:已上線的介面改前改後、每個使用端怎麼遷移、哪些要分兩步切換而不是一次換。
要跳過某個角度,要有機械證據
跳過相容性角度的唯一合法理由:grep spec 找不到任何對外介面的字樣,而且 spec 自己說是純內部重構。兩個都要。要跳過的清單先亮給使用者拍板,報告第一段固定寫「本輪跳過了哪些角度、為什麼」,沒跳過就寫「無」。
審「值不值得」,不是「對不對」
YAGNI 是 You Aren't Gonna Need It,你不會需要它。找洞型的 review 對過度設計的 spec 是火上加油:每補一個洞就多一段 code。這一軸方向相反,reviewer 的預設答案是「不做」。
三個檢查
每條需求從哪來。分三類:使用者原話、量測證據、模型自己推導的。前兩類受保護不砍。第三類逐條要答「砍掉會發生什麼具體損失」,答不出就降級或砍。
這問題真的發生過嗎。過去實際發生幾次、每次損失多少?沒有數字就標「未定價」。未定價的問題,解法以最簡版為上限。
先寫最簡版。reviewer 先寫一頁「最少要做什麼才能滿足使用者那句話」,再逐條列 spec 超出最簡版的部分。舉證責任在 spec 這邊,不在砍的那邊。
反方下限:一席如果一條都沒砍,必須逐條證明每條模型推導的需求砍掉會有損失。做不到就當輸出不合格、重跑。這是防 reviewer 蓋章。
spec 階段的「驗證者也會錯」
實際跑起來常見的形狀:一席主張砍某段,理由是「這是模型推導的」。另一席翻出使用者原話,證明那段是使用者自己要的。砍的那席把使用者原話誤分類成模型推導,就是 spec 階段的複查錯。
這種爭議不歸流程判。兩邊證據並陳,上呈給人拍板。要做到這件事,派工時就得把使用者的原話和被選的選項成對附上,否則 critic 沒東西可對。
先分類完才開口
critic 的原始報告不能直接倒給人。先把每條分進四類,遇到第一個符合的就停。
題目誤讀:critic 誤解了 spec 某段。補 spec 說明、該條重審。不直接丟掉,也不照單全收。
直接能改:不動範圍、行為、風險。直接改 spec,記下依據哪條既有決策。
取捨:兩個合理的標準對撞。留給人裁。
雜訊:反問「餵什麼材料能防這個誤報」,答得出就補進派工材料,下輪不再誤報。
剩下的取捨彙總成一張拍板表:一列一題、每列 a/b/c、推薦放第一、選了會怎樣先講。不逐條停下來問「要不要理它」。
結論強度不變:critic 的嚴重度與措辭原樣傳達,不軟化、不在旁邊加自己判對錯。
- 視角要設計材料
想要不同視角就餵不同材料、給不同立場;換更強的模型不保證補齊。PR 階段是餵滿 vs 餓著;spec 階段是簡化 vs 強度。
- 驗證者也要被驗REFUTED ≠ 錯
先看它拿什麼環境、什麼前提驗。報告保留雙方證據,人拍板。
- 最便宜的 review 在最前面3 問
spec 階段三個問題不派 agent;YAGNI 砍掉的每一條,都是不用寫的 code。
自評三題
只量熟悉度,不排名、不收集。
你上一個 PR 的 review 有幾個視角?餵給它們的材料一樣嗎?
你收到 finding 時,會問「這條的前提有查證過嗎」嗎?
你上一份 spec 裡,哪幾條是模型推導的?砍掉會損失什麼?
選完三題,下面會出現一句建議。