缺陷抓得再快,code review 還是得有人問這筆改動該不該存在

https://www.adaptivecapacitylabs.com/2026/08/24/there-is-more-to-code-review-than-automatable-detection/

2026 年 8 月 24 日,John Allspaw 在 Adaptive Capacity Labs 的網站上寫了一篇,整段引用了一篇論文的摘要。論文標題是 The End of Code Review: Coding Agents Supersede Human Inspection,6 月 11 日以第一版掛上 arXiv(2606.13175),作者是瑞典皇家理工學院 KTH 的 Martin Monperrus。Allspaw 連結這篇、行文稱「這篇文章」「作者」「這篇論文」。Monperrus 這個名字在他連結的論文頁上。

摘要把 code review 寫成軟體開發的主要品質關卡:從 Fagan 1976 年把 code inspection 形式化算起,五十年來、各種規模的組織,合併前由人看過同事的改動。Coding agent 在摘要裡是以大型語言模型為底、能讀、能寫、能測、能修軟體的自主系統。論文主張,agent 已經跨過一道能力門檻,傳統的人類 code review 不再是品質管線裡必要的一環。撐住這句的是兩個主張。Code review 講出來的每個目標,agent 都能以更低成本、更高吞吐做到。Agent 寫 code、人仍當強制審查者,這種接法是死胡同:給不出有意義的保證,也無法跟著 AI 輔助的吞吐擴張。

Allspaw 認為文章結構清楚,不常讀論文的工程師也看得懂。論證站在一個他稱為 substitution myth 的框架上。這個框架先把人的貢獻拆成幾項可度量的功能,再證明機器每項都做得到,然後宣布人可以換掉。

Monperrus 這篇自己寫明是立場論文,綜合既有的能力證據,沒有另做一篇新的實證。它把 peer code review 拆成四項功能:抓缺陷(defect detection)、風格與慣例的檢查(style enforcement)、知識傳遞(knowledge transfer)、讓相關的人知道發生了什麼改動(awareness)。論文把這四項跟 Bacchelli 與 Bird 對實地審查的整理對在一起,接著主張 agent 每一項都做得到,於是有能力換掉人類 reviewer。論文引言把這句說得更直:傳統的人類 code review 已經多餘,品質管線應改由 agent 做驗證。引言裡還有一句 Allspaw 沒有拿來當靶子的話:對常規改動,強制人審的成本效益已經翻轉。他的文章沒有引用那些能力數字。他抓的是拆法。拆完之後,每一項都被當成機器可以單獨做完、也量得出來的功能。他認為審查裡要緊的事,常常拆不成這種可勾選的功能。Allspaw 把爭點放在這個推論:做得到每一項,就換得掉 reviewer。推論要成立,審查必須等於四項的加總。他認為不等於。缺的判斷也拆不成清單上的下一項。

說一句看不懂,就是這次審查的發現

有經驗的工程師讀完一份 diff,說 “I don’t understand this.” Allspaw 寫,這句困惑就是發現。它可能是 code 太複雜,可能是抽象不對,也可能是意圖沒講清楚。三種讀法都落在這筆改動上:一個夠格的人讀不下去。

大型語言模型處理得了這份 code,也能寫出一段看起來通順的說明。處理得了,跟給得出另一種訊號,是兩件事。後者是一個夠格的人正當地看不懂。模型這邊的「看懂」是把內容處理完。人這邊的看不懂是一種阻力,阻力本身帶資訊。論文把可理解性放在比較靠近風格的那一側。可理解性不是風格問題,是人試著讀懂這份產物時才出現的性質:人卡住或跟上,發生在這次互動裡。風格檢查多勾一格,替代不了有人在讀、而且讀不下去。

看不懂一旦被收進風格,它就不再被當成這筆改動的發現。Allspaw 把方向倒過來。合法的人類不解,是審查該留下的訊號。模型的處理能力蓋不住它,因為那個訊號要有一個會不解的人,才出得來。

對不對之前,先問這筆該不該存在

審查裡會出現這樣的句子。「這應該拆成兩個 PR 嗎?」「這解的是症狀,不是問題。」問的是意圖、範圍、這筆改動適不適當。這些都排在「code 正不正確」前面。範圍問的是這份 diff 是不是把兩件決策綁在一起。症狀問的是,眼前這個修法過了,原來的問題還在不在。兩問都還在爭這筆改動該以什麼形態存在,甚至該不該存在。

論文的框架假設送審的改動是必要的,也假設審查主要在做驗證。碰過正式環境(production)的人知道,code review 常常是最後一次、有時是唯一一次,還能期待有人出聲問:這筆到底該不該改。

審查若把改動當成既成事實,剩下的座位只夠找錯、對風格、對測試。必要性沒有座位。Agent 可以很會驗證一份已經決定要進的 diff。驗證問的是:照眼前這份改動,實作有沒有偏。必要性問的是:這筆值不值得合併。第二問不在那四項功能裡。四項都做完,第二問仍然可以沒被問過。

論文摘要把「agent 寫、人仍每一筆必看」稱為死胡同。論文正文接著寫得更具體:agent 生出 code 之後,人審常常變成橡皮圖章;人的產能跟不上 AI 的吞吐,審查佇列變成卡住管線的地方。Allspaw 引用的摘要裡已經有死胡同這個判斷。他沒有因此同意人審可以從管線拿掉。他指出框架先把改動當成必要,把審查收成驗證。人還坐在審查席上,被分到的工作只剩對不對。該不該存在,不在問題裡。

該在卻不在,diff 不會自己標出來

人可以注意到一件事:API 契約改了,錯誤處理沒改。缺的那一塊不會以新增行的樣子出現在 diff 裡。審查者心裡有一份「按這個改動,這裡應該有東西」的預期,然後發現預期落空。Allspaw 把這個能力寫成:認得出預期該在、實際上不在。

論文沒有談這個。他認為特別該談,因為看不見缺席(absence blindness)正是大型語言模型常常很弱的一類失敗。Agent 審在場的內容。有這塊經驗的工程師,很容易看見不在場的內容。

四項功能裡的「抓缺陷」,預設缺陷是看得到的偏差:寫錯的邏輯、不合風格的寫法、不該出現的資安問題。缺席常常沒有偏差可標,因為那一行沒被寫出來。清單對著在場的行比對,缺的那一塊不會被任何一項功能報出來。審查若被定義成偵測在場的問題,這一類從定義裡就消失了。Agent 把四項都做完,做完的報告仍然可以對一份少了錯誤處理的改動給過。

預期從哪裡來,他沒有另寫一份清單。契約一改,呼叫端通常還要處理新的失敗,這是熟悉這塊的人帶進來的。Agent 比對已經寫出來的行。人多比對一層:這種改動通常還會帶上、這次卻沒有的東西。

誰寫的,會改你盯多緊

Peer review 的人帶著一種校準過的注意力(calibrated attention):依過去跟作者共事的經驗,決定這次盯多緊。作者常常就是同事。資歷較淺的工程師第一次提交進支付模組,得到的注意力,跟一位資深、他稱為 grey beard 的工程師做例行重構,不會一樣。審查者把當下的誰、改什麼、什麼時候、在哪裡,對上自己知道風險落在哪。支付模組本身危險。第一次交這塊的人,危險再加一層,因為審查者還沒有這個人在這塊上的紀錄。老手的例行重構仍要看,盯的密度來自另一種經驗:這個人過去怎麼處理類似的改動。

論文把 diff 當成等價輸入。同一套檢查、同一種簽法、同一種說明。自動檢查這樣做說得通,測試不關心這是誰的第一次。人審若也把輸入等價化,資淺者第一次動錢流,跟老手改自己寫過很多遍的模組,會走同一種盯法。風險感是校準過的;把 diff 當等價輸入,給不出「這個人過去交過什麼」這層經驗。

摘要替代不了兩邊一起把理解做出來

論文把知識傳遞收成資訊投遞,agent 產生解釋就好。Allspaw 寫,code review 裡的討論是一種共同的認知活動(joint cognitive activity)。審查的人學到作者怎麼下手。作者從審查者的問題裡,學到自己沒講清楚的地方。討論結束時有一份共同理解,開始之前兩邊都沒有。那份理解要等討論發生才有。摘要若在對話之前產生,手上只有對話還沒發生的那一側。

他用 coactive 叫這種工作:兩邊一起做。單向傳送是作者把已知的東西交出去,接收端收下。審查裡常發生的是兩邊都還不知道最終那份理解長什麼樣。審查者的問題可以逼出意圖,作者的回答可以改掉審查者原本的讀法,兩邊的心智模型都動了。Agent 的摘要可以寫得很完整,這個過程還沒開始。

「讓別人知道」(awareness)在四項功能裡是單獨的一項,論文認為 agent 同樣做得到。Allspaw 沒有另開一節駁它。他寫到的「知道」,出現在版本庫外面的脈絡裡。那份知道常常不在這份 diff 裡,一份說明也投遞不完。

版本庫外面的一句話,會改這筆 diff 怎麼讀

審查時會出現 diff 裡沒有的句子。「這個服務上星期二剛出過事。」「下游那個消費端的團隊,快要把這個介面廢掉。」「法務跟我們說,這個欄位不要再寫進 log。」

這些是審查者帶在身上的話。上星期二的事故改的是風險背景,同一筆改動跟平常那天不一樣。下游即將廢掉的介面,改的是這筆還值不值得做。法務那句話,改的是某一行 log 還准不准留。三句都碰到「code 正不正確」以外的判斷。

人掌握的脈絡比自己意識到的多,真的碰上時又認得出來。組織現在什麼狀態、最近發生過什麼、測試和文件和版本控制都沒寫下的非正式約定,都會影響眼下這筆 code 該怎麼看。這種事常見到幾乎隱形。論文假設 codebase(這份程式碼庫)就是完整脈絡。Allspaw 寫:從來不是。

審查當下把這些句子跟這份 diff 接上的,是已經知道這些事、又被要求對這筆改動負責的人。程式碼庫再齊,也只齊到有人把它寫進去的程度。沒寫進去的那部分,會改這筆怎麼判。

簽名會痛,審查的方式才會不一樣

論文把人的責任當成合規物件:為了法律或其他規則,需要一個具名的人(named human)。Allspaw 認為這樣不夠。你知道自己個人要為核准這筆改動負責,審查就會被這個事實塑形。他稱為 skin in the game。Agent 在 pull request 上簽過,不承擔後果,也沒有一種激勵結構逼它認真估這一筆。

論文的討論有把倫理放進來。落到的建議是:倫理審視放在需求工程(requirements engineering)和上線後的監控(post-deployment monitoring);強制的 PR 審查不承載這件事。Agent 審查像其他自動檢查一樣被治理,不確定或高風險再升級給人。Allspaw 把這個轉向稱為 hand-waving,是把眼下的問題往後踢。

論文建議的調整是:多數 commit 由 agent 做有結構的簽核;人的核准留給高風險改動、新的架構,以及法規要求具名負責的路徑。這份分級看的是改動種類。Allspaw 寫的那種注意力,看的是誰交的。論文在拆功能時把 diff 當成等價輸入;後面的分級把高風險挑出來給人,沒有把「這個人過去交過什麼」放回每一筆的盯法裡。

他沒有逐條拆這份建議。他抓的是責任被當成表格上的名字之後的後果。名字在表格上,不會改變人怎麼讀 diff。會改變讀法的,是這筆如果出事,算在簽名的那個人身上。沒有後果的簽核,沒有這個壓力。

課責若只剩合規物件上的具名,或只出現在受規管路徑,日常合併上別人期待的那種社會課責就蓋不住。

四項各做一遍,跨在中間的那層從第一步就沒了

Allspaw 對這篇文章最根本的異議,是它把 code review 主體放在偵測上:找缺陷、找風格違規、找資安問題,然後假設偵測得更快、更便宜,一律更好。

Code review 同時是協調、意義建構(sensemaking)、治理(governance)。協調是相關的人要不要在這筆改動上對齊。意義建構是把 diff 讀成:在現在這個組織裡,這筆到底在做什麼。治理是誰有權放行,放了之後誰承擔。三件事發生在同一次閱讀裡。人可以看不懂這筆抽象,看見錯誤處理沒跟著契約走,想起上星期二的事故和法務那句話,也知道這是誰第一次動支付、核准算在自己頭上。這些判斷會互相改寫。拆開成交接的四份產出之後,那個互相改寫的動作不在任何一份裡。

Substitution myth 在他筆下常走同一條路。先把人的貢獻拆成可度量的功能。再證明機器能用它自己的可度量功能,把這些貢獻複製出來。然後宣布人多餘。這條路常常在同一個點垮掉。真正要緊的貢獻,是跨功能怎麼接在一起。人能對沒有事先計畫的情境和脈絡改自己的做法,並且承擔別人期待的那種社會課責。

沒有事先計畫,指的是審查進行到一半,輸入變了。上星期二的事故不會出現在四項功能的定義裡,它出現在審查者打開 diff 的那一天。人改的是自己怎麼看這筆,功能清單還是原來那四項。簽名會痛也不會變成第五項可勾的功能,它改變的是前四項要看得多認真。臨時的法務約束同樣不在清單上。拆解的第一步沒有把這些算進去。要緊的是跨功能怎麼接在一起,不是把人的貢獻再拆細一點;他認為原文也沒有把這層算進去。

讓 agent 寫 code 的團隊若照這個分法排管線,找缺陷、對風格、掃資安可以留在自動檢查裡。人留在合併前那個要不要放行的決定上:這筆改動該不該存在、缺了什麼、版本庫外面哪一句話會改讀法、核准算在誰頭上。四項功能各做一遍,得不出這個決定可以刪。