Matt Pocock AI 工作流深度導讀

寫出《Clean Code》的人,現在幾乎不看程式碼了

Robert C. Martin(Uncle Bob)與 Matt Pocock 的直播對談:把 agent 放出去工作,然後盡量不去看它寫了什麼——這句話背後,是一整套用 CRAP 分數、變異測試、深模組概念重新武裝起來的檢查機制。

Robert C. Martin,江湖人稱 Uncle Bob,寫了那本幾乎每個工程師書架上都有、也最常被引用的《Clean Code》。他花了大半輩子告訴人們該怎麼把程式碼寫乾淨、怎麼看懂一段爛程式碼在爛在哪裡。這樣一個人,理論上應該是最反對「讓 AI 亂寫程式碼」的人。

但他現在的做法是:把 agent 放出去工作,然後盡量不去看它寫了什麼。

「我會很努力讓自己走到一個地步,完全不用去看那些程式碼。」他在與 Matt Pocock 的直播對談裡這麼說。這不是躺平,而是一個精心設計過的結論——他花了大半年時間,才想清楚「不看程式碼」這件事該怎麼安全地做到。這篇對談的價值,就在於拆開這個結論背後的推理過程,而不只是結論本身。

從「清理狗屎」開始的覺悟

Uncle Bob 第一次認真用 agent 是去年十二月,聖誕節前後。他用的是早期版本的 Grok,讓它接手一個他手上的專案。結果很快就出現一個熟悉的模式:agent 寫得很快,但每次動手都留下一堆爛攤子——他形容為「dog do」(狗屎)。他不清理,直接叫它做下一件事,爛攤子就疊得更高,agent 開始變慢、開始卡住、開始「一邊修一邊又弄壞別的東西」,陷入原地打轉的迴圈。有一次某個 agent 甚至直接放棄,用它自己的方式表達了「我真的搞不定這個了」。

這個現象本身不是重點,Uncle Bob 真正在意的是接下來這句話:程式碼爛到一定門檻,連 agent 都會處理不了,開始跟人類一樣卡關、一樣掙扎。差別可能只在門檻高低,不在於會不會發生。也就是說,「乾淨的程式碼很重要」這件事,並不是人類獨有的偏好,而是任何要處理複雜系統的智能體都躲不開的物理限制。

有意思的轉折在這裡:這個發現讓他想起了兩個他二十多年前就知道、但當年判了死刑的想法。

一個叫 CRAP 分數(沒錯,就是這麼直白的縮寫)——把程式碼的測試覆蓋率跟循環複雜度(cyclomatic complexity,一個函式裡有多少條執行路徑)用公式混在一起,算出一個分數代表這段程式碼有多爛。二〇〇〇年代初他真的跑過一次,抓出一堆爛函式,但光是要親自把它們一個個修好、重寫測試,工作量大到他做不下去,只好擱置。

另一個叫變異測試(mutation testing)——寫一個小程式跑過你的原始碼,把負號改正號、把小於改大於、把等於改不等於,每改一處就跑一次完整測試,如果測試沒有因此失敗,代表這處改動沒有被任何測試偵測到,這叫「倖存的突變」,必須被殺死。他也跑過,一次要跑整晚,因為測試套件本身要跑四分鐘,要重複跑好幾百次。同樣的問題:太慢,塞不進正常的開發流程。

這兩個想法二十年前都是「好主意,但不實用」。今天的差別不是想法變了,是誰在做這件苦工變了。Agent 跑得快、而且不介意工作有多枯燥重複。你叫它把 CRAP 分析跑一遍再清乾淨,它就去清;叫它跑變異測試,原本要一整晚的工作縮到三十分鐘,然後把測試漏洞一個個補上。Uncle Bob 說得很直接:這些舊工具本來就等在那裡,只是在等一個不會喊累的執行者。

兩條路線的分岔:你是在說服它,還是在檢查它

對談裡最尖銳的一個對照,是 Matt Pocock 主動點出來的。大多數人遇到 agent 亂寫程式碼,反應是往 CLAUDE.mdagents.md 裡狂塞規則——每發現一個問題就多寫一條指示,越寫越長,最後變成一份五到十頁的「你應該怎麼寫乾淨程式碼」說明書。Matt 把這種做法稱為「steering」(引導),對照 Uncle Bob 用的「deterministic tools」(確定性工具)——一個是靠文字說服模型,一個是靠外部程式強制檢查。

Uncle Bob 一開始也走了 steering 這條路,而且踩到了同一個坑:模型對這些規則的態度,就像神鬼奇航裡那句台詞——規則比較像是「參考方針」,不是真的會被遵守的規矩。這背後有個技術原因,叫「lost in the middle」(迷失在中段):隨著上下文視窗塞進越來越多文字,只有最開頭跟最結尾的內容會被模型認真對待,中段的東西存在感會急速下降。你寫在文件開頭前三句可能還有效,但第五十句、第八十句基本上等於沒寫。更麻煩的是,如果開頭那段本身太長,它自己也會被往後推進中段,一樣被稀釋掉。

Matt 補了一個他從 Dex Hawthorne 那裡聽來的說法呼應這個現象:上下文視窗有個「聰明區」跟「笨區」——前十五萬個 token 左右模型還算銳利,再往後,transformer 內部的注意力機制被拉得太開,訊噪比直直落,「就像每個 token 都在一個越來越擠的房間裡喊叫,你聽不到訊號,只聽到噪音」。

確定性工具不會被這個問題稀釋,因為它們根本不活在上下文視窗裡——它們是外部程式,跑完直接給結果,不會被「中段遺忘」影響。所以 Uncle Bob 現在的做法是反過來:把最初的提示詞盡量壓到最短,只留下真正重要、必須被記住的東西,把其他所有要求都改成事後執行的檢查工具,強迫 agent 進一個迴圈——「你必須不斷修改程式碼,直到這個工具說可以為止」。

這裡有個現實的代價要付:確定性工具跑得越多,agent 就被拖得越慢。兩人都同意,這個代價遲早會遇到一個臨界點——如果檢查多到讓 agent 比人類還慢,那整件事就沒意義了。Uncle Bob 目前觀察到的空間,是還能維持人類生產力的兩到四倍,而且品質遠高於人類會做到的程度。他形容這是「早期犧牲生產力,換取後面的生產力」,本質上是對自己程式碼庫的一種投資。

一條 agent 接力線:寫壞、清乾淨、往死裡測、最後驗收

Uncle Bob 現在真正在跑的東西,不是一個萬能 agent,而是一條分工明確的接力線,每個角色只做一件事:

specifier——把人寫的需求文件轉成 Gherkin(Given/When/Then 格式的高階驗收測試)跟一份 QA 流程,而且刻意站在使用者視角寫:「你是一個人,你在用這個系統的介面操作,你必須證明這個系統真的能動。」

coder——照著這份規格寫單元測試跟實作,順便把 Gherkin 跑通。Uncle Bob 講得很坦白:這個角色的產出到這一步一定是一團糟,它的任務只是「先讓它動起來」,不用漂亮。

cleaner——接手這團亂,跑 CRAP 分析跟一般性的程式碼審查,把 coder 留下的爛攤子清乾淨。

hardener——負責跑變異測試,而且「毫不留情」。它的目標是百分之百覆蓋率,每一個等號、每一個小於符號都要被測到。

QA agent——把之前寫好的 QA 文件變成一段可執行的腳本,實際操作系統,產出一個確定性的驗收結果。

Agent 接力線五階段:specifier、coder、cleaner、hardener、QA agent 依序交棒
每個角色只做一件事,做完就結束,下一個 agent 帶著乾淨的上下文重新開始。

這條線走完一次的代價,是把單一 agent 五分鐘、結果堪憂的產出,拉長到大約一小時。聽起來變慢了,但 Uncle Bob 算的帳是:同樣的工作人類大概要花半天,所以整體算下來還是有四到五倍的生產力提升,而且品質是人類自己做絕對達不到的水準。

為什麼要拆成多個角色而不是一個 agent 從頭做到尾?兩個理由。第一,可以平行跑——他一台筆電就能同時撐好幾個 coder 同時工作。第二,也是更關鍵的一點:把 agent 的任務切得夠窄,上下文視窗裡塞的東西就少,「lost in the middle」的問題自然變小,你甚至可以在開頭多塞幾條規則,它們還撐得住。他還會讓每個 agent「生下來、做完任務、死掉」,下一個接手的 agent 帶著乾淨的上下文視窗重新開始,不會被前一階段的雜訊污染。

代價是啟動成本——每個 agent 光是啟動、重新理解自己的上下文,就要十幾秒,而且分工越細,溝通開銷就越大。但兩人算下來,這個開銷仍然遠遠划算過人類自己做全部工作。

Matt 在這裡補了一個他自己觀察到、Uncle Bob 立刻認同的概念:軌跡(trajectory)。一個上下文視窗一旦被帶往某個方向,後面所有動作都會傾向延續那個方向,不管你後續給多少新指示。他舉的例子很生活化:你正跟一個模型聊怎麼泡咖啡,旁邊有人經過聊起昨晚的肥皂劇,這句話混進了上下文——不是你放進去的,但從那一刻起,所有關於咖啡的回覆都會沾上肥皂劇的味道,模型分不出哪句話該被當真、哪句不該。這正是為什麼「讓 agent 做完一件事就死掉」有意義:清空上下文視窗,是唯一真正清除軌跡的方法。implementer 的軌跡不用太乾淨,但如果你要它同時兼顧「先跑通」和「百分之百覆蓋率」,這兩個目標會互相污染彼此的軌跡。

深模組:讓 agent 不用看穿整棟房子也能修水管

在聊架構時,Matt 主動把話題帶到 John Ousterhout(《A Philosophy of Software Design》作者)的「深模組」(deep module)概念上——一個只有窄接口、但內部藏了大量細節的模組,對比「淺模組」那種接口寬、裡面卻空空如也的設計。Matt 的直覺是:這個概念對 agent 特別受用,因為模型可以只讀接口就動手,不需要理解接口背後藏了什麼。

深模組窄接口藏大量細節,agent 只讀接口就能動手;淺模組接口寬內部卻空,agent 容易迷失
接口小、細節藏裡面,agent 讀接口加測試就能動手;接口大、裡面卻空,agent 容易迷失方向。

Uncle Bob 完全認同,而且補了一層人類跟 agent 共通的解釋:模組化這件事之所以重要,不是因為某種抽象的工程美學,而是因為人腦本來就用「分隔思考」在理解複雜系統——模型某種程度上也是。差別可能只是門檻不同,他還不確定。但兩者的共通點是:如果一個模組被塞進什麼都有、什麼都不相干的東西,不管是人還是 agent,都會迷失在裡面,不知道自己在幹嘛。乾淨的接口、清楚的邊界,讓 agent 可以只讀接口名稱、只讀測試,就大致猜到系統在做什麼——不必往下讀完整個實作。這既是優點,也是風險:只要底下的程式碼夠一致,這個「不用全讀」的假設就成立;一旦不一致,agent 會被自己的假設坑到。

為了不用每次都靠人工去問「這個模組跟那個模組是什麼關係」,Uncle Bob 讓他的 agent 幫他做了一個架構檢視工具——螢幕上跳出一張類似 UML 的圖,顯示模組結構跟依賴方向,可以點進去看子模組、再點進去直接把程式碼叫出來看。另外還有一個更硬性的工具:一份定義「哪個模組可以依賴哪個、不可以依賴哪個」的規格檔,agent 不能違反,違反了就得自己想辦法解決——通常是反轉依賴方向、插入一個介面,或是把模組拆成兩半。這條規則不是寫在提示詞裡拜託模型遵守,而是變成一個跑在最後的檢查器,違反就過不了關。

舊書架上被丟掉的東西,不一定該丟

對談後半段,Matt 問了一個他自稱「有點有壓力」的問題:《Clean Code》裡教的東西,有沒有哪些現在該修正、甚至該丟掉?

Uncle Bob 的答案分成兩層,而且分得相當清楚。

第一層是門檻,這個可以調。人類的短期記憶容量有限,所以他過去會把函式的 CRAP 分數壓在 4 以下才算合格。但 agent 的短期記憶又大又精準,所以他現在把門檻放寬到 6,還在考慮往 8 推——他還在找 agent 真正撐得住的上限在哪裡,坦承這條線不好找。

CRAP 分數門檻從人類標準的 4 放寬到 agent 標準的 6,還在測試能否推到 8
人類短期記憶有限,門檻壓在 4;agent 記憶更大更精準,門檻放寬到 6,上限還沒找到。

第二層是紀律,這個他認為不該硬套。他過去最看重的紀律之一是測試驅動開發(TDD):寫一行測試、寫一行程式碼、再寫下一行測試。但他現在的立場很明確:「我不會、也不打算把這件事強加在 agent 身上。」原因不是 TDD 不好,是它本來就是為了配合人類的短期記憶限制而設計的一套工作方法——寫完一行測試,你剛好還記得要寫什麼程式碼讓它過;寫完程式碼,你可以去倒杯咖啡再回來。agent 不需要這種節奏。即使他明確要求 agent 用嚴格的 TDD 節奏做事,它們最後總是自然滑回「寫完一個函式、補上這個函式的測試、再寫下一個函式」的模式——跟 Ousterhout 的做法比較接近,而不是嚴格 TDD 的逐行交替。他觀察到這個現象一再重複,索性就順著它,不再逼 agent 學人類的節奏。

他把這句話講得很直接:「把人類的價值觀套用在 agent 身上不算錯,但門檻可能需要調整。把人類的紀律、行為模式套用在 agent 身上,才是真正不明智的地方。」

Spec-driven development 這場戲,七〇年代演過一次了

這段是整場對談裡兩人配合最默契的地方。Matt 先拋出觀察:業界現在很流行「先把規格寫到極致完美,再交給 agent 去實作」——把一份 spec 丟進七個不同的 agent 裡反覆推敲,直到得出一份精雕細琢的計畫,再拿去實作。Uncle Bob 一聽就接話:agent 特別愛寫這種計畫,而且會寫得極其華麗、鉅細靡遺——然後到了實作階段就整個崩掉。

Uncle Bob 直言,這整套邏輯他親自試過,而且屢試不爽地失敗:規劃了半天,agent 一跑起來,人類才發現原本的計畫漏想了什麼、agent 沒有你以為的那麼聰明,結果只能喊停、回頭改計畫、再重跑一次。他把這個模式跟七〇年代的軟體業直接畫上等號——那正是瀑布式開發被推崇、又被 Agile 革命推翻的那段歷史。「這是一個非常古老的誘惑,」他說,前期規劃投入越多,交出來的東西跟計畫本身的落差反而越大。

他講了一個他過去做 Agile 演講常用的比喻:如果蓋一棟房子,每改一次設計的成本只要一塊美金——包括打地基、蓋屋頂在內的任何一次修改——你會怎麼蓋?是花大錢請建築師畫出完美藍圖,再花一塊錢請包商照圖蓋好;還是走到工地現場,跟包商說「地基做這個形狀」,看了說「不對,改一下」,「廚房放這裡,客廳放那裡,兩塊錢」,再改一次「動線很爛,把樓梯挪一下」?顯然是後者更划算。他的判斷是:今天用 agent 做軟體,修改的成本已經被壓到接近於零——「也許是兩塊,也許是五塊,但這個成本會低到我認為我們能達到的極限」——那前期規劃到極致的動機也就跟著消失了。

Matt 對「spec-driven development」這個標籤本身有意見:這個詞的定義太浮動,幾乎任何跟 agent 溝通的方式都能被貼上這個標籤,連你以前跟同事說「你去把 header 那個載入問題修一下」都可以算是一種規格。他認為真正該問的問題不是「有沒有先寫規格」,而是「你會不會把這份規格留下來、之後還會回頭去看它」。

Uncle Bob 的答案很乾脆:不會。他現在寫的規格是「短命的」——會變、會被他隨手調整、用完就丟。這裡沒有相當於過去「原始碼即最終規格」的東西存在了,因為原始碼現在不是人類寫的。他現在真正在做的,是反過來:先看 agent 產出的最終結果,把那個結果本身當成規格。他有一整套自己用的工具——CRAP 分析工具(支援 Clojure、Java、Go)、變異測試工具、agent 工作框架——但他特別提醒聽眾不要直接拿去用:「我是為我自己寫的。」他建議的做法是讓自己的 agent 去讀這些工具、理解它們的精神,再幫你重新造一套符合你自己需求的版本——這是一種「規格的本質被萃取、再客製化」的路徑,比複製別人現成的工具更務實。

這段對談裡藏著一個很精準的不對稱觀察,是 Matt 提出、Uncle Bob 立刻接住的:如果你把一份龐大的規格文件丟給人類同事,命中率大概只有百分之五到二十,大家根本不會全部讀完;但如果你把同樣的東西丟給 agent,它真的會逐字讀完。反過來,agent 產出的東西,人類幾乎不會真的全部讀——「這太諷刺了,」Uncle Bob 笑說,「這事完全不對稱。agent 期待我們讀完它們寫的每一樣東西,而我們根本不會。」

戰術與戰略:當新人再也沒有「打雜」可以練

對談收尾前,Matt 問了一個他認為很多觀眾真正想從 Uncle Bob 這裡得到答案的問題。他借用 Ousterhout 書裡「戰術型程式設計」跟「戰略型程式設計」的框架——前者像戰場上的士官,負責打眼前這一仗;後者像將軍,負責決定整場戰爭該怎麼打。兩人都同意:agent 現在極擅長戰術,極不擅長戰略。問題是:當 AI 把所有戰術層級的工作都吃掉了,一個剛入行的新人,要怎麼學會戰略層級的判斷?過去這種判斷靠的正是多年埋頭寫戰術程式碼累積出來的直覺。

Uncle Bob 坦承自己沒有完美答案,但給出一條具體路徑。第一步,不管在學校還是別的地方,新人還是得先實際寫程式碼——寫個一年左右,才會知道 agent 到底在跟什麼東西打交道。第二步是他講得最直白、也最挑戰現有職場邏輯的一段:當這個新人真的進了一家大量使用 agent 的公司,公司應該把他當成一個 agent 來對待——分配跟 agent 一樣的任務,套用跟 agent 一樣的確定性檢查工具,讓他撐過好幾個月「極度沒有生產力、但學到很多東西」的階段。走完這段煉獄,他才有資格被信任去指揮一個屬於自己的 agent。

Uncle Bob 把這個邏輯延伸回他十年前就講過的一個比喻:如果你從沒寫過組合語言,應該花一個週末去寫,才會知道 Java 底層到底在發生什麼事——不然你活在一個幻想世界裡,不知道有些「魔法」其實是可以被理解的。他認為這個道理現在同樣成立,只是這條學習路徑變得更長:從二進位、組合語言,到 C 這類底層語言,再到 Python 這類高階語言,然後是跟 agent 協作、使用確定性工具,最終才能在監督下戰略性地指揮一個 agent。

至於怎麼學會「識別 agent 正在掙扎」這種直覺——去年十二月他一眼就看出 agent 陷入死結,靠的不是看程式碼本身,而是看見它在原地打轉的那種掙扎狀態,而他認得這種掙扎,因為他自己走過。這種直覺沒有捷徑,但他給了一條具體的路:去讀那些沒人在讀的舊書——Tom DeMarco、Ed Yourdon,還有《The Pragmatic Programmer》這類七〇、八〇年代寫成的書。內容要過濾掉一些過時的技術細節,但這些書記錄的正是「軟體怎麼失控、怎麼被組織」這件事最早被系統性想清楚的年代。Matt 順勢接了一句他覺得很妙的收束:與其只是讀,不如親身當一段時間的「agent 的 subagent」——被 agent 指揮的人類,親自感受一次那種被高度約束、被反覆檢查的工作狀態。

為什麼基礎工程學不會過時

對談最後,Matt 直接問了那句最直白的問題:軟體工程的基礎功,還重要嗎?那些說「基礎不重要了」的人,錯在哪裡?

Uncle Bob 的回答援引了他記憶中 Dijkstra 說過的一句話(他自己也承認可能記得不完全準確):軟體是人類嘗試過最複雜的東西,比任何其他人類做過的事情都複雜。而工程基礎的存在,本質上就是一套把這種複雜度組織成可以被理解的形式的方法——不只是給人類用的,也是給模型用的,因為模型終究是照著人類的思維方式訓練出來的。這句話把整場對談的邏輯收攏了起來:CRAP 分數、變異測試、深模組、確定性檢查,這些工具之所以在 agent 時代重新有用,不是因為它們被重新發明了,而是因為它們解決的問題——複雜度失控——從來沒有消失,只是換了一個更快、更不喊累的執行者。

那些認為基礎不重要的人,他說,會用自己的方式學到教訓,而且可能不需要太久——「agent 現在已經很強了,可能會比我以為的更晚才撞上那堵牆,但我看過他們撞上去,我知道那堵牆真實存在,我不想再撞一次。」

Matt 在收尾時補了一個歷史類比,呼應了整場對談的基調:軟體的抽象層一路往上疊——從二進位到組合語言,到編譯器,現在到模型這一層——每一次往上跳,底層的人都會喊「這下完了,大家都要失業了」或「這下簡單到連五歲小孩都能寫程式了」。這句話兩千多年前柏拉圖就對「文字」這個新發明說過類似的話,擔心它會讓人變笨。每一次,恐慌的內容都差不多,而每一次,被丟掉的東西,最後都變成一年後又得從地上撿回來、吹掉灰塵、重新想起為什麼當初留著它的東西。