這篇文章基本上是將這則漫畫擴展成一篇完整的文章:
很久以來,我都不需要寫像現在這篇這樣的文章。如果有人提出從規格文件中生成程式碼的想法,我會分享上面的圖片給他們,這通常就足夠了。
然而,代理編碼的倡導者聲稱他們找到了一種方法,可以克服重力,僅從規格文件中生成程式碼。此外,他們還攪渾了水,讓我覺得上述漫畫需要額外的評論,來說明他們的說法為何具有誤導性。
根據我的經驗,他們的倡導源於兩個常見的誤解:
誤解一:規格文件比對應的程式碼更簡單
他們利用這個誤解,向那些認為代理編碼是下一代外包的信徒推銷代理編碼。他們夢想著工程師變成管理者,撰寫規格文件,然後將工作外包給一群代理人來完成,這只有在指定工作的成本比實際執行工作的成本更低時才有效。
誤解二:規格撰寫工作必須比編碼工作更深思熟慮
他們利用這個誤解,向那些擔心代理編碼會產生無法維護的垃圾程式碼的懷疑者推銷代理編碼。他們的論點是,透過規格文件來過濾工作將提高品質並促進更好的工程實踐。
我將透過一個具體的例子,詳細說明為什麼我認為這些是誤解。
我將從 OpenAI 的 Symphony 專案開始,OpenAI 將其譽為從規格文件中生成專案的範例。
Symphony 專案是一個代理協調器,聲稱是從「規格」(SPEC.md)生成的,我之所以在「規格」兩字加上引號,是因為這個文件更像是 Markdown 格式的偽代碼,而不是規格。如果你仔細查看該文件,你會發現其中包含諸如資料庫結構的文字描述:
在編碼代理子程序運行時追蹤的狀態。
運行時根據運行映射中當前追蹤的狀態來計算問題。
……或者為了讓模型能夠快速生成程式碼而特意添加的段落,例如:
此部分故意冗餘,以便編碼代理能夠快速實現配置層。
我認為代理編碼的倡導者將其作為程式碼的替代品進行推銷,這非常不誠實,因為規格文件讀起來就像程式碼(或者在某些情況下就是程式碼)。
別誤會我的意思:我並不是說規格文件永遠不應該包含偽代碼或參考實現;這兩者在規格工作中都相當常見。然而,當規格文件讀起來像程式碼時,你就不能聲稱它們是程式碼的替代品。
我提出這一點是因為我相信 Symphony 很好地說明了第一個誤解:
誤解一:規格文件比對應的程式碼更簡單
如果你試圖讓規格文件精確到足以可靠地生成可工作的實現,你必然必須將文件扭曲成程式碼或非常類似程式碼的形式(例如高度結構化和正式的英語)。
Dijkstra 解釋了為什麼這是不可避免的:
我們在此期間知道,介面的選擇不僅僅是(固定數量的)勞動力的劃分,因為跨介面的協調和溝通工作也必須加進來。我們在此期間——從令人清醒的經驗中,我必須補充——知道改變介面很容易在兩邊都增加工作量(甚至大幅增加)。因此,越來越傾向於所謂的「狹窄介面」。因此,儘管轉換為機器和人類之間使用後者母語進行的溝通會大大增加機器的負擔,但我們必須質疑這會簡化人類生活的假設。
簡要回顧數學史,就會發現這種質疑是多麼合理。希臘數學停滯不前,因為它仍然是一種口頭的、圖像的活動,伊斯蘭的「代數」在進行了謹慎的符號化嘗試後,當它回歸修辭風格時就消亡了,而現代文明世界只能在西方歐洲擺脫中世紀經院哲學的束縛——一種徒勞的追求語言精確性的嘗試!——得益於我們從 Vieta、Descartes、Leibniz 和(後來)Boole 等人那裡獲得的精心設計或至少是有意識設計的形式符號系統,才能夠出現——無論好壞。
代理編碼者正在付出沉重的代價,了解到你無法逃避工程勞動所需的「狹窄介面」(閱讀:程式碼);你只能將這種勞動轉化為表面上不同的東西,但仍然需要同樣的精確度。
此外,從規格生成程式碼甚至都不可靠!我實際上嘗試了 Symphony README 所建議的:
告訴你最喜歡的編碼代理,用你選擇的程式語言構建 Symphony:
根據以下規格實現 Symphony:https://github.com/openai/symphony/blob/main/SPEC.md
我要求 Claude Code 用我選擇的程式語言(Haskell 2,如果你從我的部落格名稱猜不到的話)構建 Symphony,但它沒有成功。你可以在我的 Gabriella439/symphony-haskell 儲存庫中找到結果。
不僅有多個錯誤(我不得不提示 Claude 修復,你可以在提交歷史中找到這些修復),而且即使在「工作」時(意思是:沒有錯誤訊息),編碼代理也只是默默地運行,而沒有在以下範例 Linear 票證上取得任何進展:
無需創建 GitHub 專案。只需創建一個空白的 git 儲存庫
換句話說,Symphony 的「徒勞的語言精確性嘗試」(借用 Dijkstra 的話)仍然無法可靠地生成可工作的實現 3。
這個問題也不僅限於 Symphony:即使對於像 YAML 這樣廣為人知的規格,我們也看到了同樣的問題。YAML 規格非常詳細,廣泛使用,並包含一個一致性測試套件,但絕大多數 YAML 實現仍然無法完全符合規格。
Symphony 可以嘗試透過擴展規格來修復這種不穩定性,但它已經相當長了,長度約為包含的 Elixir 實現的 1/6!如果規格進一步擴展,它們將會重現 Borges 的短篇小說「論科學的精確性」:
……在那個帝國,地圖繪製的藝術達到了如此完美的程度,以至於一張省的地圖佔據了整座城市,而一張帝國的地圖則佔據了整個省。隨著時間的推移,那些不負責任的地圖不再令人滿意,製圖師公會繪製了一張帝國的地圖,其大小相當於帝國本身,並且與之逐點吻合。後代人,他們不像他們的祖先那樣熱愛製圖學的研究,他們認為那張龐大的地圖毫無用處,而且毫不無情地將其交給了太陽和冬天的摧殘。在西部的沙漠中,至今仍有那張地圖的破敗廢墟,居住著動物和乞丐;在這片土地上,再也沒有地理學的學科遺留下來了。
規格工作應該比編碼更困難。通常,我們在進行工作之前編寫規格文件的原因,是為了鼓勵以一種沉思和批判的視角來看待專案,因為一旦開始編碼,我們就會切換模式,變得以行動為導向。
那麼,為什麼我說這是一個誤解呢:
誤解二:規格撰寫工作必須比編碼工作更深思熟慮
問題在於,由於業界推動科技公司減少和貶低勞動力的努力,這種深思熟慮已不再是我們可以理所當然的事情。當你從「我告訴人們規格工作應該比編碼更容易」的前提開始時,你就注定要失敗。如果你以交付速度為優化目標,你就不可能完成規格撰寫所需的艱難且令人不適的工作。這就是為什麼你會得到像 Symphony 的「規格」這樣看似規格文件,但在仔細審查後卻分崩離析的東西。
事實上,Symphony 的規格讀起來就像是人工智能寫的垃圾。第 10.5 節是我所說的垃圾的一個特別惡劣的例子,例如這段摘錄:
目的:使用 Symphony 為當前會話配置的追蹤器身份驗證,針對 Linear 執行原始 GraphQL 查詢或變異。
可用性:僅在 tracker.kind == "linear" 且已配置有效的 Linear 身份驗證時才有意義。
query 必須包含恰好一個 GraphQL 操作。
variables 是可選的,存在時必須是 JSON 對象。
實現可以額外接受原始 GraphQL 查詢字符串作為簡寫輸入。
每次工具調用執行一個 GraphQL 操作。
如果提供的文檔包含多個操作,則將該工具調用視為無效輸入並拒絕。
operationName 選項超出了此擴展的範圍。
重用活動 Symphony 工作流程/運行時配置中配置的 Linear 端點和身份驗證;不要要求編碼代理從磁盤讀取原始令牌。
將 GraphQL 響應或錯誤負載作為結構化工具輸出返回,以便模型可以在會話中檢查。
這是一堆「規格形狀」的句子,讀起來像是一個代理的工作成果:缺乏連貫性、目的性或對大局的理解。
像這樣的規格文件必然是垃圾,即使是由人類撰寫的,因為它們優化的是交付時間而不是連貫性或清晰度。在當前的工程環境下,我們不能再理所當然地認為規格是經過深思熟慮和仔細權衡的產物。
規格從來都不是為了節省時間而設計的。如果你優化的是交付時間,那麼你最好直接撰寫程式碼,而不是通過中間的規格文件。
更廣泛地說,「垃圾進,垃圾出」的原則在這裡適用。你無法輸入一個缺乏清晰度和細節的文件,然後期望一個編碼代理能夠可靠地填補這些缺失的清晰度和細節。編碼代理不是讀心者,即使它們是,如果你的想法本身就很混亂,它們也無能為力。
如果你想知道為什麼程式碼片段沒有高亮顯示,那是因為我保留了原始文件中的 GitHub 風格 Markdown。所有程式碼片段都明確標記為文本(這也是該文件是人工智能生成的跡象之一)。事實上,這很可能是模型只遵守請求字面意思而非精神的例子。我非常確定發生的是,人類要求人工智能將專案的初稿轉換為散文規格,然後人工智能決定將程式碼片段標記為文本,使其更像散文而不是程式碼。↩
人們經常告訴我:「如果你用更主流的語言而不是 Haskell 來生成程式碼,你會得到更好的結果。」對此我回答:如果代理在生成 Haskell 程式碼時遇到困難,這就表明代理無法可靠地從其訓練數據中泛化。↩
如果你認為我處理不當,也可以自己嘗試這個練習。↩