01tech workshop · 日期待定

一個模型不夠

從 PR 到 spec 的多視角 review。

review 的盲點來自視角。
換更強的模型不保證補齊;要改的是餵給 reviewer 的材料與立場。

今天講的是流程,不現場操作。例子來自近一個月自己團隊的 review 紀錄,已去敏:不出現 repo、PR 號、作者與業務名詞。

02開場

同一份報告裡的兩條 finding

一份大 PR 的 review 報告,幾十條 finding。挑兩條。

第一條 · 四席同題 · Must Fix

登入後的落點少了一道權限判斷,某類使用者登入直接看到 404。

四個視角都抓到。交叉驗證時去 repo 裡找同型的判斷,別處都有、只有這裡缺,所以成立。

第二條 · 初審 → 複查推翻 → 重驗翻回 · Nice to Have

事件本身很小:一條測試是綠的,但它走的不是名字說要測的那條路,等於沒測到東西。

重點在過程:初審提出 → 複查判 REFUTED(被反證推翻) → 重驗翻回 CONFIRMED(成立)。複查者拿的環境跟被測程式不同,結論就反了。

1 · 初審

這條測試實際走的是另一個分支,測不到它想測的東西

MEDIUM
→
2 · 複查

在一般環境試,判斷式成立,說測試沒錯

REFUTED
→
3 · 重驗

用測試實際跑的環境重跑一次,log 證明走錯分支。複查者的環境跟被測程式不同

CONFIRMED · 降 LOW

哪條更能說明 review 該怎麼設計?

第二條。驗證者也會錯。今天講的流程,一半在防 reviewer 錯,另一半在防「驗 reviewer 的人」錯。

Part APR review
03視角差是設計出來的

一席餵滿,一席刻意餓著

想要不同視角,就餵不同的材料、給不同的立場。換更強的模型不保證做到。

CC 席 · 餵滿

主動塞給它:spec 全文、這次改動會影響到誰(用依賴關係算出來)、repo 的慣例文件、共同 checklist。有關聯的後端 PR 時,連那份 diff 也一起給。

Codex 席 · 刻意餓著

不給 spec、不給慣例、不給 checklist,只給 diff 和它原生的 review 規則。流程明文禁止多塞一句提示。

它仍然可以讀程式、跑測試。差別在沒人告訴它什麼是「本次不管」,所以它會問出我們習以為常而沒想到的事。


兩件事會發生

  1. 多給的材料會帶來獨有發現。前端這次接的是一個還沒合併的後端改動,前端先上線整欄會空白。只看 diff 的席看不到後端狀態,抓不到;拿到後端 diff 的席抓到了。報告會標這是 lone finding(只有一席抓到),並寫清楚「另一席材料上看不到,合理」。

  2. 不同材料下各自命中,才叫共識。兩席材料不同、立場不同,卻指到同一段程式、同一個根因,這條的可信度就高。誠實講:在我們的紀錄裡,餓著的那席很少獨自抓到別人沒抓到的,它的貢獻是獨立命中、形成共識。

04選席

三個常設視角、兩個條件視角,每次選

不是每個 PR 都全開。流程先列出席位和推薦,你當次選。推薦不等於必跑,沒回覆不等於接受推薦。

常設 · CC 主審

拿到全部材料。依語言選 reviewer。PR 大就切塊,一塊一個 reviewer。

常設 · Codex 中性

只看 diff,用它原生的 review 規則。新鮮的眼睛。

常設 · Codex 對抗

同一個 Codex、換成紅隊立場:「假設這個 PR 會炸,去找哪裡炸。」

條件 · security

PR 碰到登入授權、請求參數、新的外部 API 時才開。

條件 · spec 對照

repo 有寫成 MUST/SHALL 的正式條款,而且對得上這次改的流程才開。口語規格對不上,這席會 SKIPPED。

實際上怎麼選

小 hotfix 三個常設席就夠。大 PR 主審切塊、加 security、兩個 Codex 都開。純後端 PR 可以不開對抗席。看的是這次改動碰到什麼,不是固定套餐。

沒完成的席留 FAILED,不冒充完成、不偷換成別的模型。重試成功就記成功,首次失敗也留著。沒選的席不算未完成,也不算完成。

前後端配對的 PR 可以一起審:各自 worktree、一次選席、一份報告。分開審時 reviewer 看不到對面的 diff,契約對不上判不出來。

05找的不准自己驗

初審會錯,複查也會錯

CC 驗 Codex 的 finding,Codex 反過來驗 CC 的。兩邊都被驗。多席都抓到的條目走輕量查證;只有一席抓到的,要多答一題「另一席為什麼漏」。

複查者依序問四個問題

  1. 真的觸發得出來嗎?順著程式路徑走,能不能構造一個實際輸入讓它發生?構造不出來就只是「理論上可能」,不算成立。

  2. 發生了會怎樣?「多印一個錯誤」「只有開發環境會遇到」算 LOW;能跑任意程式、資料外洩、權限提升、資料寫壞才算 HIGH 以上。初審常把 LOW 抓成 HIGH,這題壓回來。

  3. 同樣寫法別處早就有,為什麼只有這條會炸?去整個 codebase 找同一種寫法。已經存在多處、跑很久沒事,就要答出這條的實質差別:多了使用者可控的輸入、跨了信任邊界、範圍變了。答不出就 REFUTED。

    這題最會殺誤報,也最容易被濫用:它分得出「新增的回歸」和「早就在的風險」,分不出「沒問題」。既有程式也可能一起錯,REFUTED 時要註明這點。

  4. 別的地方擋掉了嗎?中介層、資料庫約束、framework 預設、上游守衛。有就降級,並附上擋掉它的檔案與行號。

任何一題不過,複查者要附反證,不能只寫「我不同意」。REFUTED 不是刪除,是多了一份反證。


兩種錯法

複查錯

開場第二條。複查者在一般環境驗,被測程式跑在另一種環境,同一個判斷式在兩邊結果相反。驗證環境跟被測程式不同,結論就反了。

初審錯

初審說「這段設定要到某個時間點才載入,所以這個檢查永遠跳過」。複查判它前提錯。最後用一小段探針程式實測,證明設定在 import 當下就載好了。初審的前提沒查證,整條就不成立。

06拍板不是模型說了算

三道降級閘

報告按最終建議排序:Must Fix、Should Fix、Nice to Have、參考用。不按 severity(模型給的嚴重度)排。CRITICAL 被推翻一樣沉底。三道閘把「聽起來嚴重」壓回「查證過的嚴重」。

  1. Must Fix 要兩個條件同時成立。一,寫得出使用者看得到的重現步驟。二,不修就會壞掉會出貨的東西。死測試、死設定、文件不符通常不單獨構成 Must Fix,依影響列 Should 或 Nice。多席共識也不豁免。

  2. 假設性措辭最高只到 Should Fix。「若有人繞過」「假設 API 回 X」這類句子撐不起 Must。例外是不必舉證就該報的類型:硬編的密鑰、SQL 字串拼接、eval 使用者輸入。

  3. 嚴重度不能建立在沒查證的前提上。把沒查證過的論據拿掉,等級會不會降?會降就用拿掉後的等級。

    為什麼獨立成一條:沒查證的那段往往正是把嚴重度撐高的那段,而嚴重度決定哪幾條會貼給作者。不擋在這裡,最沒根據的 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 不算未結案。

07LLM 不可信的部分

能用程式檢查的,就不留給模型自述

不信 agent 說「都看了」。每一件能機械核對的事,都由程式算、程式寫進報告。

  1. 審的是哪一版。程式核對 review 副本的 commit 跟 PR 最新 commit 一樣,才准寫「已審 SHA」。不一樣就標 unverified。

  2. 每個檔都有交代。改動檔清單由程式建。每個檔只能是四種狀態之一:有 finding、看過沒問題、刻意跳過、漏掉。四個數字加起來要等於總檔數,漏掉那格要是 0。刻意跳過的檔(例如自動生成檔)要有跳過的理由。

  3. 行號重新對位。finding 附一段逐字程式碼,由程式重新找出它在哪一行。模型報的行號常會偏。

  4. 兩層報告。完整證據是正本,給人看的主報告由程式從正本投影出來。模型不准重寫或摘要主報告。

  5. 發布前有人再看一次。派一個沒參與審查的唯讀 agent,照固定清單挑報告的缺口。它是提醒性質:失敗只註記、不擋發布。

  6. 第二輪不是重來。前一輪每條 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 階段一樣成立,而且更便宜。

Part Bspec review
08為什麼往前搬

spec 階段抓到的,在寫 code 前就能改

PR 階段抓到缺陷,修法是改 code。spec 階段能更早核對兩件事:做的是不是使用者要的、值不值得做。PR 階段也能判範圍,但那時 code 已經寫了。

分五級,賭多大開多重

  1. 1唱反調自審
    在原對話裡問自己,不派 agent
    改動小、已經很懂這塊
  2. 2一個 critic
    沒看過對話的新 agent
    跨多檔、新模組、架構沒把握
  3. 3多個角度平行審,再彙整動核心架構、碰生產資料
  4. 4找外部模型賭很大的關鍵決策才手動升
  5. YYAGNI 定價
    獨立跑,不與 1 到 4 疊加
    spec 是模型代寫、要審「值不值得」
09第 1 級

三個問題,不派 agent 的地板

直接在原對話自問。只要問對三個問題。

  1. 這份 spec 我做了哪 3 個你可能不同意的決策?各附「我選它的理由」和「反方理由」。

  2. 我對關鍵變數、範圍、依賴假設了什麼?哪些沒跟你確認過?

  3. 哪裡偷工、含糊帶過、留了沒解的問題?

紀律:自己能答的先答完、一次列完,只把真要使用者裁的留下來問。不軟化、不先幫自己辯護。

10派 critic 的守則

新 agent 的價值,在它沒被作者的結論污染

派出去的 critic 沒看過我們的對話。這是它的價值,四條守則都在保護這件事。

  1. 不夾帶自己的判斷。只給 spec 全文和需求約束,不給自己的評估。prompt 裡一句「我認為這段沒問題」,critic 就會傾向同意。

  2. 意圖自己審、架構才外派。「這份 spec 有沒有偏離使用者真正要的」要靠整段對話才判得出來,新 agent 看不到。這題留在原對話做。

  3. finding 對回使用者的理解。使用者說「這個假設不對」,就整批重評。標「因理解修正而撤銷」是合法結果,不是忽略。不硬推原本的解法。

  4. 每輪至少一路交兩張表。自洽表和引證表,寫進既有 critic 的輸出格式,不另派 agent。

11第 3 級

讓兩個 critic 故意對立

多個角度不是多幾個同樣的人。價值在兩個對立角度的對撞處。

簡化派

預設懷疑。找過度設計、漏掉的需求、更簡單的替代。抓「該砍什麼」。

⇄
對撞

簡化派說 X 可以刪,強度派把 X 列為防禦。這種條目標 contested(有爭議)、單獨一段呈現、由人決策,流程不替它判。

⇄
強度派

假設這份 spec 在某些情境會裂。找邊界情況、隱含假設、回滾、並發、部分失敗。明文「不找過度設計,那是另一支的活」。

另兩個角度

  1. 跟 repo 既有決策一致嗎:讀決策紀錄、術語表,看用詞與已決定的事有沒有衝突。

  2. 改了之後舊的還能用嗎:已上線的介面改前改後、每個使用端怎麼遷移、哪些要分兩步切換而不是一次換。

要跳過某個角度,要有機械證據

跳過相容性角度的唯一合法理由:grep spec 找不到任何對外介面的字樣,而且 spec 自己說是純內部重構。兩個都要。要跳過的清單先亮給使用者拍板,報告第一段固定寫「本輪跳過了哪些角度、為什麼」,沒跳過就寫「無」。

12YAGNI 軸

審「值不值得」,不是「對不對」

YAGNI 是 You Aren't Gonna Need It,你不會需要它。找洞型的 review 對過度設計的 spec 是火上加油:每補一個洞就多一段 code。這一軸方向相反,reviewer 的預設答案是「不做」。

三個檢查

  1. 每條需求從哪來。分三類:使用者原話、量測證據、模型自己推導的。前兩類受保護不砍。第三類逐條要答「砍掉會發生什麼具體損失」,答不出就降級或砍。

  2. 這問題真的發生過嗎。過去實際發生幾次、每次損失多少?沒有數字就標「未定價」。未定價的問題,解法以最簡版為上限。

  3. 先寫最簡版。reviewer 先寫一頁「最少要做什麼才能滿足使用者那句話」,再逐條列 spec 超出最簡版的部分。舉證責任在 spec 這邊,不在砍的那邊。

反方下限:一席如果一條都沒砍,必須逐條證明每條模型推導的需求砍掉會有損失。做不到就當輸出不合格、重跑。這是防 reviewer 蓋章。


spec 階段的「驗證者也會錯」

實際跑起來常見的形狀:一席主張砍某段,理由是「這是模型推導的」。另一席翻出使用者原話,證明那段是使用者自己要的。砍的那席把使用者原話誤分類成模型推導,就是 spec 階段的複查錯。

這種爭議不歸流程判。兩邊證據並陳,上呈給人拍板。要做到這件事,派工時就得把使用者的原話和被選的選項成對附上,否則 critic 沒東西可對。

13呈現紀律

先分類完才開口

critic 的原始報告不能直接倒給人。先把每條分進四類,遇到第一個符合的就停。

  1. 題目誤讀:critic 誤解了 spec 某段。補 spec 說明、該條重審。不直接丟掉,也不照單全收。

  2. 直接能改:不動範圍、行為、風險。直接改 spec,記下依據哪條既有決策。

  3. 取捨:兩個合理的標準對撞。留給人裁。

  4. 雜訊:反問「餵什麼材料能防這個誤報」,答得出就補進派工材料,下輪不再誤報。

剩下的取捨彙總成一張拍板表:一列一題、每列 a/b/c、推薦放第一、選了會怎樣先講。不逐條停下來問「要不要理它」。

結論強度不變:critic 的嚴重度與措辭原樣傳達,不軟化、不在旁邊加自己判對錯。

14帶走的三件事
  1. 視角要設計材料

    想要不同視角就餵不同材料、給不同立場;換更強的模型不保證補齊。PR 階段是餵滿 vs 餓著;spec 階段是簡化 vs 強度。

  2. 驗證者也要被驗REFUTED ≠ 錯

    先看它拿什麼環境、什麼前提驗。報告保留雙方證據,人拍板。

  3. 最便宜的 review 在最前面3 問

    spec 階段三個問題不派 agent;YAGNI 砍掉的每一條,都是不用寫的 code。


自評三題

只量熟悉度,不排名、不收集。

你上一個 PR 的 review 有幾個視角?餵給它們的材料一樣嗎?

你收到 finding 時,會問「這條的前提有查證過嗎」嗎?

你上一份 spec 裡,哪幾條是模型推導的?砍掉會損失什麼?

選完三題,下面會出現一句建議。