Bob 大叔:AI 程式碼我完全不看
Robert C. Martin |AI 程式設計 |AI Agent |程式碼整潔之道 |Clean Code |變異測試 |測試驅動開發 TDD
開場
「我完全不看 agent 寫出來的任何程式碼」——這句話如果是從一個剛畢業的程式設計師嘴裡說出來,我們可能會覺得他不太靠譜。但說這話的人是羅伯特・馬丁(Robert C. Martin),全球開發者更熟悉的稱呼是 Bob 大叔。他寫程式超過 50 年,著有《程式碼整潔之道》(Clean Code),是軟體工程領域被引用次數最多的作者之一——一個把程式碼品質看得比什麼都重要的人,現在卻說他不看 AI 寫的程式碼。
Bob 大叔最近和知名 TypeScript 教育家 馬特・波克克(Matt Pocock)做了一場直播對談,聊的是 AI 時代的軟體基礎,以及他這幾個月和 AI agent 一起寫程式碼的真實經歷。整場對談不只是在聊 AI 怎麼寫程式碼,更核心的問題是:當程式碼的生成速度被大幅提升之後,程式設計師到底應該把時間花在哪裡?
Bob 大叔其人
- 1964 年,12 歲的他寫下第一個程式,用的是母親送的生日禮物——一臺小型類比電腦,需要把白色小管子套在插樁上來程式設計。在他看來,那本質上是一個三位元的接線狀態機,但對 12 歲的他來說已經足夠著迷。
- 後來父親給了他幾本程式語言的書,包括 Fortran、COBOL、PL/I,他全讀完了,只是當時沒有機器可以執行程式,只能把程式寫在紙上,在腦子裡模擬執行。
- 16 歲找到一份可以寫程式碼的臨時工作,18 歲找到第一份正式的程式設計師工作,從此一直做到今天。
對談還有一個有趣的開場:Bob 大叔是穿著浴袍出現的。這其實是他個人形象的一部分,源自他所謂的「浴袍吐槽」——大約兩年前,他早上 6 點穿著浴袍坐在前廊,開始琢磨 Silver Bullet(銀彈)這件事,越想越覺得不對勁,掏出手機發了一通牢騷,沒想到反響不錯,後來就陸續做了幾次,用這種半開玩笑的方式表達對技術問題的看法。
AI 程式設計的初體驗
去年聖誕節前後,Bob 大叔開始認真嘗試 AI 程式設計工具,最早試的是 ChatGPT、Grok 這些,一開始其實沒覺得有多驚豔。後來他找了一個 agent 讓它幫忙寫程式碼,寫得不怎麼樣,但至少確實寫出來了。當時他正忙著一個專案,就讓 agent 參與了工作,不過那個階段他一直在幫 agent 擦屁股,因為它經常把事情搞得一團糟——速度確實快,但總是會留下隱患。
他的感受很矛盾:AI 挺有意思,因為確實很快;但也很讓人沮喪,因為反而讓自己的效率變慢了。
轉折:CRAP 分數與變異測試
轉折發生在他意識到——既然 agent 這麼快,那他其實可以做一些自己根本做不到的事。他提到兩個很早就有、但一直沒有落地的想法:
CRAP(Change Risk Anti-Patterns) 把程式碼測試覆蓋率和每個函式的圈複雜度結合起來,用一個公式算出分數,衡量一個函式到底有多糟糕。2000 年代初他跑過一次,確實找出一堆糟糕的函式,但只能一個一個修,還得重新編譯測試,成本太高,只能擱置。
變異測試(Mutation Testing) 原理是讓一個小程式自動修改你的原始碼(比如把負號改成正號、小於號改成大於號),每改一次就跑一遍完整的測試套件,預期測試應該失敗,因為程式碼被故意破壞了。如果測試沒有失敗,就說明出現了一個「存活的變異體」需要處理。這個方法他也試過,當時得跑一整晚,雖然找出不少問題,但同樣沒法納入正常流程。
到了今年初,他突然想到:AI 速度快,而且根本不在乎工作有多枯燥,那乾脆讓 agent 去跑 CRAP,讓它負責重構和清理程式碼,也讓它跑變異測試。以前要跑一整晚的任務,現在 30 分鐘就能完成,而且還能把所有的測試漏洞補上。
他意識到,AI 寫程式碼的時候雖然會留下很多碎屑和浮毛,但它們自己也許就是清理這些東西最好的工具。於是他不斷嘗試加入更多工具,讓多個 agent 配合工作,到現在為止,效果相當不錯。
他現在的原則很明確:讓 agent 去幹活、執行這些工具,他正在努力達到一種狀態——不需要親自去看程式碼,也可以信任 AI。當然,他還是會透過其他方式驗證程式碼品質,比如檢查 CRAP 分數、抽查程式碼、執行其他測試等等。但總體思路是:既然 agent 處理程式碼的速度比人類快得多,那就讓 agent 負責寫程式碼,人類來處理更高層面的事情,確保一切正常運轉。
AI 寫的爛程式碼怎麼辦
這裡有一個關鍵的問題繞不開:AI 寫的爛程式碼該怎麼辦?
Bob 大叔很早就發現,如果一直讓 agent 往下做、不去清理它留下的爛攤子,它的速度反而會越來越慢。比如改了一個地方,無意中破壞了另一個地方;為了修復那個地方,又破壞了其他地方,最後不停繞圈子。這些 agent 雖然快也確實聰明,但和人類一樣會受到爛程式碼的影響——程式碼爛到一定程度,agent 就處理不了了,開始原地打轉,把爛攤子越搞越大。他甚至遇到過一個 agent 直接跟他說:「我處理不了了。」
多數人可能會選擇給 agent 加指令,在配置檔案裡堆滿規則,每看到壞程式碼就加一條。但 Bob 大叔選擇了另一條路:確定性的自動化檢查機制。
為什麼不選擇引導式規則
最開始他確實這麼做過,給 agent 寫的提示詞都是「這是測試驅動開發的方法」「這是整潔程式碼的要求」之類的,最後會得到一份長達數頁的文件,專門告訴它什麼是好程式碼。但他很快發現,這些模型對待規則的態度特別像《神鬼奇航》裡的海盜法典——聽起來是規則,但對它們來說更像是一堆建議。
背後其實有技術原因。他研究後發現,這和一個叫做 lost in the middle 的現象有關:模型的上下文視窗越來越大,放在最前面和最後面的內容更容易被關注,而夾在中間的內容更容易被忽略,甚至像消失了一樣。比如你寫了一份很長的提示詞,開頭前三句它記得很清楚,當作高優先順序指令,但到了第 50 句、第 80 句,這些內容可能就被丟到上下文中間的某個角落了。
但確定性的工具不會這樣,不會因為規則寫在第 50 行還是第 80 行,就把規則當作建議。所以他認為使用 agent 的關鍵,就是把初始提示詞精簡到絕對最小值,盡可能多地保留在優先順序區域,然後在這之後使用確定性工具——雖然這確實很難做到。
馬特補充了一個很好的說法,叫「上下文視窗的聰明區和愚蠢區」,這是 Dijkstra 提出來的概念:隨著上下文不斷變長,Transformer 中的注意力機制會越來越吃力,資訊逐漸被稀釋,就像一個越來越擁擠的房間,最後你很難從一片噪音中分辨出真正重要的訊號。
自動化檢查的臨界點
Bob 大叔還提到,自動化檢查確實存在一個「過多」的臨界點:如果讓 agent 的速度慢到還不如人類,那就輸了;但只要生產力依然高於人類,就依然領先。根據他目前的觀察,大概能把生產力優勢維持在 2 到 4 倍左右。
使用確定性工具會明顯拖慢 agent,因為這相當於把它放進了一個迴圈裡,不斷修改程式碼,直到工具告訴它 OK 為止——agent 就在那裡不斷迴圈,修改這個、修改那個,增加測試、降低圈複雜度、拆分函式,需要很長時間才能達到預設的合規標準。本質上是在犧牲一部分生產力,換取更高的程式碼品質。
多 Agent 協作
他正在嘗試讓多個 agent 彼此協作、相互交接:比如一個負責寫程式碼,下一個負責審查,再下一個負責測試和強化。雖然有巨大的通訊開銷,但整體速度仍然比人類快得多。
多 agent 協同其實是解決「上下文失效」問題的關鍵思路。Bob 大叔解釋了兩個優勢:
- 可以並行執行多個 agent
- 當任務聚焦在單一任務時,可以更好地控制上下文視窗,lost in the middle 的問題會減輕。你甚至可以設定這樣一套機制:讓 agent 出生、完成任務、然後消失,下一個 agent 進來的時候,面對的是乾淨的上下文視窗。
缺點是啟動時間更長——一個 agent 可能需要 10-15 秒才能啟動並理解當前上下文,所以他傾向於盡可能把任務拆得足夠細、足夠聚焦。
具體來說,他有一套完整的流程:
- 規格定義器:把人類編寫的文件轉換成 Gherkin 語言和 QA 流程
- 編碼器:負責撰寫單元測試、實現故事邏輯,讓 Gherkin 測試跑通
- 清理器:完成之後交給它做複雜度分析和常規程式碼審查
- 強化器:執行變異測試,反覆驗證測試是否真的有效,確保 100% 的測試覆蓋率(這個過程非常耗時)
- QA agent:把書面 QA 文件轉換成可執行的指令碼,用指令碼直接作業系統,給出確定性的測試結果
如果能透過這一整套流程,最終得到的程式品質會非常高。他自己算過一筆帳:給單個 agent 一個任務可能 5 分鐘就完成,但結果是否可靠很難說;採用這套流程可能需要一個小時,但依然划算,因為讓人類完成同樣的工作可能需要半天。
架構設計
在架構設計方面,Bob 大叔也有不少思考。過去一個多月,他一直手動完成模組結構設計:先讓 agent 構建東西,然後透過不斷提問來「審問」它們,比如「這裡的結構是什麼」「模組和模組之間怎麼關聯」,等他得出那些令人恐懼的答案之後,就親自設計模組結構,告訴 agent 該怎麼劃分、怎麼通訊。
這個過程非常辛苦,所以他讓 agent 幫他構建了一個架構檢視器,可以在螢幕上彈出類似 UML 的圖表,展示整個系統的模組結構和依賴關係,可以點選檢視子模組甚至直接看程式碼。
他還構建了另一個確定性工具,可以定義哪些模組應該依賴哪些模組、哪些絕對不能互相依賴,形成一份 agent 無法違反的規範檔案。如果違反了,就必須透過反轉依賴、提取介面等方式來修復。
馬特提到了約翰・歐斯特霍特(John Ousterhout)的**深層模組(deep module)**概念,即介面簡單但內部隱藏大量資訊的模組,對 agent 來說是非常理想的,因為它們只需要讀取介面而不必理解實現。Bob 大叔非常認同這一點,指出模型非常關注介面的名稱和結構,這意味著它們不必閱讀底層程式碼——這既是一種風險,也是一種優勢。他還在《程式碼整潔之道》的附錄裡記錄了和歐斯特霍特之間的一場長篇辯論,自己玩得很開心。
傳統程式設計規範在 AI 時代的調整
接下來兩人聊到,傳統程式設計規範在 AI 時代是否需要調整。Bob 大叔提到了幾個具體的調整:
圈複雜度閾值
Agent 能處理的複雜度和人類開發者不一樣,它們的短期記憶比人類強得多,而且更加精準。所以他把 CRAP 分值的閾值從人類的 4 以下放寬到了 6,甚至在考慮提高到 8。在測試覆蓋率達到 100% 的情況下,CRAP 分值為 6,意味著這個函式有 6 條執行路徑,而且每一條都經過測試。他和 agent 就這個問題爭論過多次,agent 似乎也認為 6 是一個不錯的標準。
測試驅動開發(TDD)
他曾經是 TDD 的堅定擁護者,但那是一種人類的紀律,是根據人類的思維方式演化出來的,他不會也不打算把這種紀律強加給 agent。強迫 agent 寫一行測試再寫一行生產程式碼沒有什麼意義——雖然對人類來說這樣做收益很大,但對 agent 來說,他更願意允許它們採用歐斯特霍特提倡的方式:先寫函式再寫測試。
事實上,即使他要求 agent 嚴格按照 TDD 來做,它們最終也總是會回到「先寫程式碼再寫測試」的模式上。所以他的結論是:把人類的紀律強加給 agent 可能是一個錯誤。當然,雖然不需要強加紀律,但仍然需要堅持人類的價值觀,只是閾值需要根據 agent 的特點來調整。
前期規劃的陷阱
關於前期規劃,Bob 大叔有一個很明確的判斷:現在最大的誘惑是過度撰寫規格說明,讓開發者不斷完善需求和計劃,再把這些東西一次性交給 agent。這是一個源自 70 年代的古老誘惑,後來瀑布式開發模式就是這種思路的典型,而敏捷開發的興起,很大程度上就是對這種做法的反思——過於沉重的前期規劃往往會把事情搞得一團糟,因為最終做出來的東西幾乎不可能和最初設想的一模一樣。
面對 agent,人們同樣容易掉進這個陷阱。他這週就試過這種方法,結果一次次證明是災難:agent 根本無法完全按照制定的計劃執行,因為你不可能提前考慮到所有的細節,agent 也沒有你那麼強的判斷能力,很容易朝錯誤的方向一路跑偏,你只能叫停、回滾、重新計劃、從頭開始。
所以他放棄了這種做法,回到了敏捷的方式:先讓 agent 完成一兩個具體任務,看看整體架構有沒有問題,如果需要就手動介入做一些調整,再讓它繼續。他懷疑我們可能永遠無法完全擺脫最後這部分人工組織的工作。
對於現在流行的「規格驅動開發」,他的直覺是這條路行不通:agent 非常喜歡寫計劃,會不斷修飾,寫得越來越華麗完美、細節越來越豐富,但到了真正執行的階段往往還是會崩盤。
他用了一個很巧妙的比喻:假設改建一棟房子的成本只需要一美元,包括打地基、修屋頂以及之後所有的修改,每次都只需要一美元——你會先花幾千美元請建築師設計一套完美的方案,再花一美元一次性把房子蓋出來嗎?還是直接走到承包商面前說:「地基打在這裡,做成這個形狀……啊不對,改一下,廚房放在這邊,客廳放在那邊……等等,還是換個位置吧。」顯然後一種更合理。現在軟體修改的成本已經大幅下降,接近於零,所以為什麼還要花大量精力做昂貴的前期規劃呢?這個說明是轉瞬即逝的,會消失也會頻繁變化,不等同於原始碼。
給新世代開發者的忠告
之後是對於新世代開發者的忠告,也是這場對談裡最值得深思的部分。
馬特提到了歐斯特霍特關於戰術程式設計和戰略程式設計的區分:戰術程式設計像是地面作戰的士官,負責具體戰鬥;戰略程式設計像是將軍,從更高層面統籌整場戰爭。問題是 Agent 非常擅長戰術,卻非常不擅長戰略。那麼剛入行的人,既然 AI 已經吞掉了大量戰術性的工作,該如何學習戰略程式設計呢?
Bob 大叔坦言他也沒有完美的答案,但分享了他的想法:
-
先寫至少一年程式碼。程式設計師學習程式設計的方式都應該是親手寫程式碼,這樣才能真正知道 agent 究竟在處理什麼。
-
入職後把自己當成一個 agent。當你入職一家大量使用 agent 的公司、還是一個剛完成培訓的年輕人時,你應該被當作一個 agent 來對待——那位手下執行著一堆 agent、自己負責戰略決策的首席工程師,應該把你視為一個 agent,給你分配和 agent 一樣的任務,讓你接受和 agent 一樣的確定性工具約束。你應該在這種狀態下待上幾個月,雖然產出很低,但能學到非常多東西。當你透過了這種嚴酷的考驗,也許才會被信任去親自執行一個 agent。
-
不能完全丟掉底層知識。10 年前他常常告訴人們,如果你從來沒有寫過組合語言,就應該花一個週末寫寫組合語言,這樣才能知道底層到底發生了什麼。如果你整天只寫高階語言,那你就是生活在一個幻覺的世界裡,那裡仍然存在很多你不理解的魔法。這一點在今天仍然成立。
在這條學習的路上,你必須從最基礎的東西(比如二進位)開始,一路經過組合語言、像 C 這樣的基礎程式語言、像 Python 這樣的高階語言,然後進入處理 agent 級別的工作,學習使用確定性工具,最後才能在監督下真正開始戰略性地執行 agent。
他還推薦去讀那些老書,比如湯姆・德馬克(Tom DeMarco)、艾德・約爾頓(Ed Yourdon)的著作,以及《程式設計師修煉之道》。這些書寫於七八十年代,很多重要的經驗和教訓恰恰是在那個時代總結出來的。你需要過濾掉一些過時的內容,但很多核心的東西依然適用。
如何判斷 Agent 在犯錯
至於怎麼判斷 agent 在犯錯,Bob 大叔說:早期他就是看著程式碼、發現裡面不好的程式碼,但那其實不是最重要的部分。更重要的是他會看著 agent「瞎忙」——他能看出 agent 什麼時候在掙扎。作為一名程式設計師,他自己也經歷過同樣的掙扎。問題在於一個剛入行的人,可能根本識別不出這種掙扎,他是透過艱苦的實踐來學會這一點的。
回到軟體基礎
最後回到軟體基礎這個核心話題,Bob 大叔引用了 Dijkstra 的話:軟體是人類迄今為止嘗試過的最複雜的東西,比我們做過的任何其他事情都要複雜。基礎是我們組織複雜性的一種方式,讓複雜的東西變得可以理解——不僅是讓人類理解,也讓模型理解,畢竟模型終究也是模仿人類建立起來的。
那些認為軟體基礎已經不重要的人會吃到苦頭,而且不會等太久——雖然可能比想像的更久一點,因為 agent 確實很厲害。但他已經見過它們撞牆了,他知道那堵牆就在那裡。每次抽象層向上提升——從二進位到組合語言、從組合語言到編譯器、從編譯器到模型——處在更低層級的人都會抱怨說這會毀了一切、我們都沒工作了、程式設計變得這麼簡單、5 歲小孩都能寫程式碼了。每一次都是同樣的故事,但同樣的規則依然適用:你今天扔掉的那些規則,一年之後很可能還是會從地上把它們撿起來,撢掉灰塵,重新想起為什麼當時你需要它們。
結語
Bob 大叔的這套實踐和思考,本質上不是在說 AI 不行,也不是在說傳統方式是萬能的。他在說的是:當程式碼的生成速度被大幅提升之後,組織複雜性的能力、判斷程式碼品質的能力,以及識別 agent 何時開始掙扎的能力,這些更高層面的東西非但沒有過時,反而變得更加重要了。一些以為有了 AI 就可以跳過基礎的人,遲早會撞上那堵牆。