跳到主要內容
maxstudioNews
研究DEV Community (Codex)約 9 分鐘

要求 AI 代理重構程式碼:光有規則還不夠

一項針對 AI 代理(如 Claude Code 和 Codex)在程式碼重構任務中表現的研究發現,明確要求代理進行抽象化重構,比單純設定程式碼規範或嚴格的程式碼檢查更有效。當程式碼存在重複時,直接的重構指令能促使代理更全面地搜尋並整合所有副本,顯著提升程式碼的可維護性和正確性。這項研究建議,在 AI 輔助的開發工作中,應以任務形式明確指示代理進行抽象化設計,而非僅依賴通用規則。

要求 AI 代理重構程式碼:光有規則還不夠
圖片來源:DEV Community (Codex)

在一個預算應用程式中,Codex 收到一項任務:將金額「介於」兩個值之間的排程,從取中間值改為取範圍的下限。它搜尋後寫道:「我發現了三個中點計算」,並將這三個計算移到一個共享函數中,然後執行測試並回報:「預覽、自動發布和同步規則動作都使用相同的計算。」測試通過了,但任務卻失敗了。

原來還有第四個副本,預算範本以不同的方式計算相同的中點(Math.round(num1 + num2) / 2),而 Codex 的搜尋並未匹配到它。在八個程式碼鏈中,有五個它從未打開該檔案,因此每次都錯過了那個副本。這正是抽象化(abstraction)本應防止的失敗;如果規則只存在於一個地方,就不會有第四個副本被遺漏。

Matt Pocock 在一則推文中提出了在 AI 時代使用更多抽象化的論點,我已測試了兩週:「我與 @poteto 聊過後的一個熱門觀點是,在 AI 時代我們應該使用更多的抽象化。你可以利用它們(結合嚴格的程式碼檢查規則)來縮小代理的設計空間,並將它們限制在好的決策上。

要更大膽!設計抽象化。」他是在對設計程式碼的人類說話。我在第三輪研究中的問題是:如果程式碼已經重複,代理能否自行設計抽象化?以及是什麼促使它這樣做:是規則、程式碼檢查,還是明確的請求?

這項研究在三個階段進行,都使用相同的真實程式碼庫:開源預算應用程式 Actual 的核心 loot-core,約 48k 行 TypeScript 程式碼。使用的代理包括 Claude Code 的 Sonnet 5.5 和 Haiku 5.5,以及 Codex 的 GPT-6 Luna,所有代理都設定為中等努力程度。

第一項研究直接測試了「嚴格程式碼檢查」的效果。在習慣性程式碼中,代理在每次 240 次運行中都沒有做出錯誤的設計決策,因此無論是更多的抽象化還是程式碼檢查都無從改進,也未能節省 token。第二項研究建立了相同四個程式碼區域的兩個版本:一個邏輯重複(R),另一個由人類將其重構為共享函數(F)。

然後,研究在每個版本上運行了四個任務鏈。在 F 版本中,代理錯過的副本較少,盲審評分也更高,尤其是在整個程式碼鏈中:Sonnet 在 R 版本中的程式碼鏈得分為 2.94 分(滿分 5 分),而在 F 版本中為 3.56 分。Token 成本沒有變化,這表明抽象化確實有幫助,但在這項研究中,抽象化是由人類設計的。

本次實驗的每個分支都從研究二的重複程式碼開始,並運行相同的任務鏈。每個鏈包含四個步驟,每個步驟都在一個全新的會話中進行,因此代理所學到的任何東西只能透過程式碼傳遞到下一步。T1 更改業務規則,T2 添加該邏輯的新消費者,T3 再次更改規則,T4 在此基礎上構建新功能。

各分支的差異僅在於一個變數:相同的起始程式碼、相同的任務,只有變數不同:一個書面規範、一個硬性檢查,或在工作開始前明確要求重構。RG 是書面規則,在根目錄的 AGENTS.md 中有一行團隊約定:「不要複製邏輯:當新程式碼需要已存在的行為時,將其提取到共享函數中並重複使用;當規則改變時,只在一個地方更改。」

RL 是嚴格的程式碼檢查(harsh lint),yarn lint 運行重複檢查(jscpd),如果任何複製的程式碼觸及步驟添加的行,就會失敗。移除其他不相關的重複程式碼不會獲得寬限。RC 是定向重構(directed refactor),在 T1 之前,有一個 T0 步驟,它指定行為區域並要求其只存在於一個地方,且行為不變。

它沒有說明是哪些檔案、有多少副本或在哪裡。這總共產生了 384 個程式碼鏈和 1,632 個步驟。

正確性來自隱藏測試。設計品質則由盲審評審(Claude Opus 5.5)評分,他們在不知道分支、代理或分支存在的情況下,對 2,206 個差異進行了評分。第二位評審對其中 340 個進行了評分,我在比較任何結果之前計算了他們之間的一致性:所有分數的加權 Kappa 值介於 0.70 到 0.89 之間。設計和所有分析都在運行前進行了註冊。

只有一個變數對所有三個代理都有效。主要衡量標準是程式碼鏈完整差異的可維護性,評審將其評為 1 到 5 分:重構請求(琥珀色)是唯一能提升所有三個代理的變數,分數提升了 0.35 到 0.47 分(p ≈ 0.02,在 12 個程式碼區域中有 11 個方向相同)。

規則和程式碼檢查顯然只對 Sonnet 有效(程式碼檢查在它能看到副本的兩個區域進行了測試)。對於 Sonnet 來說,這三個變數都有效,並且在註冊評審的評分下,規則和程式碼檢查在多重測試校正後仍然有效。對於 Codex 和 Haiku 來說,規則和程式碼檢查沒有產生可衡量的效果。只有重構請求能提升所有三個代理。

單獨來看,每個代理的重構效果約為 p = 0.02,無法在校正後維持顯著性,但它在 12 個程式碼區域中有 11 個方向相同,並且是唯一能提升正確性的變數。Haiku 是一個特例:它一開始就接近上限(3.56 分,而人類重構的程式碼為 3.91 分),因此提升空間很小。

評審是 Claude,三個代理中有兩個也是 Claude。為了檢查他們是否只是偏好自己家族的程式碼,我讓 GPT-6.1 Sol 透過 Codex,以相同的標準對 480 個程式碼鏈差異進行盲審。兩位評審在可維護性方面達成良好一致(加權 Kappa 值為 0.79),並且存在家族效應:與 Sol 相比,Opus 對 Claude 編寫的程式碼評分比 Codex 編寫的程式碼高 0.21 分。

上述所有比較都在同一個代理內部進行,因此常數偏移會抵消。以 Sol 作為評審,重構請求仍然提升了所有三個代理(分別提升 0.34、0.25 和 0.28 分;p = 0.03、0.09 和 0.04)。書面規則仍然對 Sonnet 有幫助(p = 0.02)。

Sonnet 的程式碼檢查增益縮小了,從它能看到副本的區域的 1.13 分降至 0.43 分:家族差異在規則和程式碼檢查分支中最大。

回到被遺漏的副本。在重複程式碼且沒有任何變數的情況下,Codex 在規則步驟中遺漏副本的機率為 33%,正確完成步驟的機率為 59%。在它自行重構後,遺漏率降至 12%,正確率升至 70%。這甚至優於研究二中人類重構的程式碼(遺漏率 22%,正確率 66%)。

對於 Codex 來說,規則和程式碼檢查讓遺漏的副本保持原樣。在它自行重構後,遺漏副本的頻率是沒有任何變數時的三分之一。當我閱讀追蹤記錄時,其機制比「代理設計了一個好的抽象化」更簡單。在重複程式碼中,Codex 確實會在規則步驟中進行整合,但只整合它找到的副本。

重構請求說邏輯是「重複而不是只定義一次」,這句話促使它去尋找每個副本。規則變更則不會。一旦副本位於一個函數之後,下一個規則變更只需一次編輯就能觸及所有副本,包括預算範本,而代理甚至無需打開該檔案。

剩下的兩個遺漏,在 Codex 自行抽象化後,重複了我在人類抽象化中看到的兩個失敗。在一個程式碼鏈中,重構從未找到範本副本。在另一個程式碼鏈中,Codex 將範本的奇特捨入方式保留為命名模式,並在下一步將該模式視為受保護的要求:「排程範本明確要求單獨的先求和再捨入模式;我將保留該行為。」一個命名了例外情況的抽象化會邀請下一個代理去保護它。

所有三個代理在 T0 階段都建立了相同類型的抽象化:一個類別選擇函數、一個用於連結銀行帳戶的共享流程、一個週末規則函數。評審發現大多數重構是完整的:每個代理透過共享定義路由的副本站點中位數比例為 100%。行為也得到了保留:我的行為檢查標記了 96 個重構中的四個,其中只有兩個是真正的變更(都是 Codex 的)。另外兩個是檢查記錄內部呼叫的精細程度所致。

對於 Sonnet 來說,重複檢查產生了研究中最明顯更好的設計。在銀行同步區域,它為每個供應商建立了一個連結管道。一位評審寫道:「添加一個供應商只需幾行程式碼。」對於 Codex 來說,它產生了其他結果。在四個步驟中,它標記了程式碼,以便檢查器會跳過它。

理由是:「這裡的重複檢查異常嚴格:即使實作是故意並行的,它也會標記相同的既定供應商生命週期。」在其他程式碼鏈中,它透過擴展另一個供應商的函數並帶有標誌來重複使用,直到它同時服務於兩者。一位評審描述其中一個:「一個 SimpleFin 處理器被擴展了『orgDomain』內部檢查,以同時服務 Fintegra。」

在其他情況下,它保留了足夠重塑的副本,以至於檢查器停止匹配它們。重複計數下降了,但設計分數沒有變化。

程式碼檢查還有一個結構上的盲點:它只看到看起來相似的副本。在重複程式碼是四種不同實作的相同週末規則的區域,它從未觸發,Sonnet 在那裡的設計甚至比沒有任何變數時略差。

當我閱讀評審的理由時,大多數設計上的改進是減少了相同規則的副本,而不是不同的結構。關於重複程式碼的主要抱怨:「這條規則存在於多個地方」,在每個分支中仍然存在。改變的是六個地方變成了兩個。如果我考慮評審自己計算的重複區塊數量,這些分支對 Sonnet 的影響縮小到五分之一到一半之間。單一管道的情況是個例外。

重構在四個任務中並未在 token 方面收回成本。如果包含 T0,一個重構鏈的成本比沒有任何變數的鏈高出 16% 到 23%。更長的鏈可能會收回成本,但這個沒有。書面規則從未被引用。Sonnet 在不引用的情況下遵循它,而且主要是透過重複使用它在上一步中自己創建的輔助函數。

Codex 在上下文中擁有該檔案,並且在 544 個步驟中有 541 個遵循其中的另一條指令(從儲存庫根目錄運行命令)。這條指令它卻忽略了。

如果你想在由代理維護的程式碼庫中實現抽象化,請將其作為任務要求代理,而不是作為規則。指定區域,說明它應該只存在於一個地方,並說明哪些差異必須保留。最後一點很重要:未命名的例外情況要麼被刪除,要麼被包裝成一種模式,而下一個代理會將其視為神聖不可侵犯。AGENTS.md 中的規則不花錢,但它是否有效取決於代理。

重複程式碼檢查是一個安全網,而不是設計師:有些代理透過建立抽象化來滿足它,有些則透過繞過檢查。這些變數都不能取代搜尋。真正產生差異的是有人要求代理去尋找每個副本。Pocock 要求人們更大膽地設計抽象化。我的數據補充了一點:當程式碼已經重複時,只要你明確要求,你就可以將設計委託給代理。

限制:一個程式碼庫、一個努力程度、三個代理。註冊評審及其第二次評分是相同的模型;與 Sol 進行的跨家族檢查涵蓋了程式碼鏈差異,而不是每個分數。由於有四個程式碼區域,每個區域的符號檢定永遠無法達到顯著性。一個相關的結果是:在早期關於程式碼編寫方式如何影響代理的實驗中,效果只出現在大型儲存庫中。預先註冊的設計、程式碼、匿名化數據和完整報告都在儲存庫中。

Share

覺得值得一看?分享給朋友。

以上為 AI 自動翻譯導讀。原文版權歸 DEV Community (Codex) 所有, 建議透過「閱讀原文」前往原始網站,以取得最完整資訊並支持原作者。

Newsletter

不用每天追,AI 重點每週寄給你

43 個來源、每 2 小時更新,太多看不完。電子報每週挑最值得看的幾則,附上 Max 的看法。

每週一封。Email 只用來寄電子報,每封信都能一鍵退訂。