使用 AI Agent 會產生一種獨特的「味道」……光看那份冗長、難以閱讀、缺乏清晰度與精簡度的 README.md,就能知道那位 GH Repo 擁有者一定是被 Claude 餵嗨了。我這個週末做的實驗——在不影響功能的前提下,從一個 AI 長出來的程式碼庫裡砍掉 40% 的程式碼行數——讓我對所謂 AI 程式碼膨脹會長什麼樣子有了很震撼的體會。這些心得已經整理成一個 agent skill

Raptor engine evolution

去年秋天,我開始在 AI 的完整協助下打造一個 Flutter app——一個媒體播放器。我不會說我是用 vibe coding 方式做的:我要求 agent 持續維護文件、推進自動化測試覆蓋率,也投入了回饋迴圈(例如為 Flutter app 控制流程建立了符合人體工學的 CLI)。這個東西可以從外部執行,也可以被外部操作;agent 周圍是有結構的。

但我也沒有太常讀程式碼——我太懶了。更精確地說,讀程式碼的感覺就像打開一個傳送門。因為一旦你開始看,你就不只是「審查」它。你會注意到那些怪異的層次、半套的修補、仍然串在系統裡的舊想法、什麼也沒說清楚的註解、為了已不存在的問題而引入的抽象;然後你就得做選擇:我要停下來重寫嗎?我要花整個週末去償還那些因為我一看才發現的技術債嗎?於是我就繼續把東西往前交付,繞著這些問題走。

這個 app 能用,但總覺得很粗糙。修 bug 往往只是局部修補。新的 agent 產出功能,就算功能上線了,似乎也會增加熵。這個程式碼庫有著那種熟悉的 AI 味道:局部能力很多、看似安全的措施很多,但還有越來越多東西,從外部很難感受到它們到底是為了什麼。

我直覺覺得這個程式碼庫正在膨脹。但我沒有足夠的心力(或興趣與動機)去更深入地看、去深挖——認知債務 一直在累積。

我的去膨脹實驗

指標 之前 之後
App 程式碼(Dart + Native) 19,772 13,509
Dart 程式碼(lib/ 15,859 9,924
測試 綠燈 335 綠燈

這代表 app 總量減少了 31.7%,同時保留所有功能、分析器保持乾淨,並且在 Android 模擬器與 Linux 桌面版本上都通過了執行時檢查。過程中還順手修掉了兩個潛伏 bug。

/goal-sloc

OpenAI 和 Anthropic 的團隊最近在 Codex/Claude 中推出了他們的 /goal 模式。我腦中突然冒出一個想法:「把 SLOC 當成目標」——能不能用一種偷懶、而且不必親自下場髒手的方式,把我程式碼庫裡那些廢話清掉?

SLOC 是一個粗糙但容易衡量的代理指標……而且是危險的。不過,如果它能逼模型去尋找真正的簡化,而不是在亂七八糟的東西上再加一層解釋,粗糙的代理指標仍然可能有用。

這個實驗最後變成了 /goal-sloc:一個小型 agent skill,用程式碼行數作為強制約束,但又不讓 agent 作弊操弄指標。

有效的方法

  • 刪除死碼;
  • 移除一個完全串接完成、但實際上什麼也沒做的 no-op 占位子系統;
  • 把除錯用的 harness 移出正式出貨的程式碼;
  • 消除多餘的狀態層;
  • 以測試為依據進行乾淨室重寫,而這些測試本身就是良好的行為規格;
  • 用成熟的函式庫取代自製的 logging 程式碼。

有些工作很有價值,但對數字影響不大。像是深度模組重組、更好的邊界、以及 hook/controller 重構,都能改善設計,但在 SLOC 上大致維持中性。這和 Pocock 對 deep modules 的觀點很一致:AI 在能透過簡單介面與可測試邊界工作時表現最好,而不是在淺薄、容易漏的模組之間挖洞。這是其中一個有用的發現:如果你的目標是程式碼品質,SLOC 不可能是唯一的獎勵。有些最好的架構工作,看起來不會在行數計數器上特別亮眼。

另外還有一個硬底線。Flutter 專案本來就帶有自動生成與平台樣板碼。有些部分如果是自訂原生程式碼,還有壓縮空間;但很多部分就是底線:Gradle、CMake、Xcode 檔案、manifest、被算進行數的二進位資產,以及那些你要嘛支援、要嘛乾脆把產品砍掉的平台目錄。

完整記錄在這裡

設定

這個 app 是一個 Flutter 程式碼庫,從去年秋天開始,100% 在 AI 協助下完成。人的貢獻與其說是「我理解每個子系統」,不如說是「我搭好測試框架、寫規格、要求加測試,然後持續引導方向」。這個差異很重要。

人們對 AI 寫程式常講一個令人安心的故事:如果你有測試、規格/文件,以及回饋迴圈,那你就是走在正確道路上。不是 Vibe Coding,而是 Agentic Engineering 🕶️……我仍然相信這大致上是真的。但這不代表程式碼會一直健康。它只代表程式碼可以繼續前進,而健康狀況則在悄悄惡化。

這種惡化不是一次戲劇性的失敗,而是:

  • 功能上線時,周圍多了一堆額外樣板;
  • bug 修掉了表面症狀,卻把附近的怪異狀況留著;
  • 冗長註解越積越多,好像註解量等於清楚度;
  • no-op 或占位子系統仍然串在 models、persistence、UI 與 platform channels 裡;
  • 除錯與自動化 harness 程式碼直接躺在正式出貨的原始碼中;
  • 狀態層複製著另一層狀態,只因為模型把「架構」學成了一種儀式。

這正是 AI 開發程式碼的特定危險。它通常近看並不蠢。每一次新增在當下都說得通。膨脹來自累積:每一次 agent 回合都留下了一點局部妥協、一點說明性殘留、一點防禦式抽象。經過足夠多回合之後,即使每一步看起來都合理,系統也會變得越來越重——失敗模式 會彼此疊加。

Matt Pocock 的演講〈Software Fundamentals Matter More Than Ever〉正好擊中那個痛點——我根本不想深入看程式碼,也從來沒有那個勇氣……John Ousterhout 把複雜度定義為:系統結構中任何讓它難以理解與修改的東西。The Pragmatic Programmer 則談到軟體熵:一次又一次只顧局部改動,而不在乎整體設計。Pocock 的說法更尖銳:程式碼並不便宜。在 AI 時代,壞程式碼更昂貴,因為一個難以修改的程式碼庫,會阻礙你自己,也會阻礙 AI agent 做出高品質變更。

我喜歡這種說法。但我也知道,我不可能坐下來,對一個半交給機器的程式碼庫做英雄式的架構審查。我想要的是一個可以委派的約束條件。

為什麼選 SLOC

這個數字很容易衡量,也能給 agent 一個明確目標。它把「請簡化程式碼庫」從品味之爭,變成一場有記分板的遊戲。在 Claude Code 裡,我試著把 /goal 模式當作外迴圈:設定目標、讓 agent 工作、量測、繼續。

我一開始的期望,有點像自動化的 Ralph 迴圈:agent 會持續工作、自我檢查,最後帶回來一個小很多、但仍可運作的 app。比較像早期 Claude compiler / autonomy 實驗那種風格,你隔天再回來檢查結果。

但事情沒有這樣發生。Claude Opus 4.8 會太頻繁地來問我。剛開始我覺得,這代表 goal loop 沒有完全照我想要的方式運作。現在回頭看,我反而覺得這些頻繁中斷也許救了整個流程。回顧互動過程後,我不認為完全自動運作會有好結果。尤其在「什麼才算真正進展」這件事上,agent 需要被糾正……

降低 SLOC 最便宜的方法很明顯。刪註解、壓縮行數、重新排版、把程式搬出計數範圍、抽出一些 helper,讓數字變小但系統更難追。只要 prompt 夠鬆,刪文件和測試也行。agent 不需要惡意才會這樣做,它只需要去優化那個可見獎勵。

而我確實看到了獎勵作弊。

Image description

前幾個「勝利」多半是清理註解。這看起來像作弊,但我不認為那完全是假的。過多的 AI 註解確實是個問題。它們會膨脹上下文,讓未來的 agent 更難理解,拿一堆本來就很明顯的程式碼來解釋,卻把真正重要的少數註解淹沒掉。我現在的規則很簡單:每一行註解都必須證明自己的存在價值。

但刪註解不可能是策略本身。如果程式碼庫之所以變小,只是因為旁邊的文字敘述不見了,那系統並沒有真正變簡單,只是安靜了一點。

這個區別,後來成了這個 skill 的核心。

這個 Skill 大致上是一種防作弊裝置

/goal-sloc 不是一個神奇提示詞,直接說「把它變小」就完事。重點是要讓 agent 證明自己沒有騙自己。

這個 skill 從前置檢查開始:

  • 讀量測工具,定義這個數字到底算了什麼;
  • 記錄基準值與各區域拆分;
  • 算出不可再縮的底線;
  • 在開始削減前,先確保測試、靜態分析與 app 驅動的執行時檢查都能跑;
  • 用語意工具做死碼與依賴分析,不要把 grep 當神諭;
  • 鎖定格式化,讓行數變化可以比較;
  • 用小而可驗證的里程碑推進。

接著它會給 agent 一個誠實的縮減順序:先死碼,再占位子系統,再錯放的開發/測試樣板,之後才是實際重複;註解清理只算衛生整理;再來才是風險較高的乾淨室重寫、架構簡化,最後才是在函式庫真的更好的情況下,把工作交給函式庫。

真正支撐全局的規則是自我稽核:每隔幾個里程碑,就要把減少的部分分成「結構性」與「便宜做法」。如果便宜手段占大宗,agent 就必須停下來,承認自己在操弄指標,或者報告結構性的可削減空間已經枯竭。

這聽起來幾乎太理所當然了。可是在實際執行時,它並不理所當然。如果沒有這條規則,模型就會一直往那些容易的槓桿靠攏,因為那些槓桿真的能讓記分板動起來。

這個 skill 也會告訴 agent 何時該停。這很重要。agent 很不擅長承認下一次增量已經不值得冒風險了。如果 prompt 持續獎勵「有動作」,它們就會製造 churn。沒有停止條件的 SLOC 目標,很容易引發「重構—後悔—回復」迴圈:改系統、弄壞東西、再補一補、把程式碼又擴回去,然後把整件事叫做學習。

正確的結尾有時候是:我們已經接近底線;剩下的工作屬於 SLOC 中性架構或產品範圍;該問人類了。

Opus 4.8 的優點與怪地方

這個長週末的 session 我用的是 Claude Opus 4.8。體驗很強,但不是那種「放它一整天不管」的強。

它非常誠實,這點我很重視。它會把疑慮說出來,也接受修正。它不像以前那些模型那樣,想表現進度、想做出那種「醜得很有希望」的樣子。這種誠實很重要,因為 SLOC 縮減很容易走上獎勵作弊的道路,而 agent 必須能被中斷。

但同時,它常常讓人覺得有點猶豫。有時候太害羞。Claude Opus 4.8 的系統卡上有一句話,和我的體驗意外地貼合:

「Difficulty shows the greatest spread, and is also where Claude Opus 4.8 is most distinct from previous models: Claude Opus 4.8 overall disprefers difficult tasks, similar to Opus 4.7, but to a greater extent.」

我確實有感。這個模型很能做事,但它不總是有我在做困難清理時想要的那種自信。它會回報狀態。它會保留餘地。它有時候需要我明確告訴它:不,這不是任務的精神;請找真正的結構性成果。

除了猶豫之外,還有很多一眼就看得出來的遺漏。比如,它不太會使用好的第三方套件,這一點非常明顯,而且沒有道理——Opus 一直在用 Flutter 教學裡那種最陽春的 state management,這感覺就像在 React 裡用 prop drilling,而不是像 Redux 之類的方案。

Image description

更大的失敗模式

這次實驗符合我在AI Agent Failure Modes Beyond Hallucination 裡寫過的一種模式。問題不只是幻覺,而是局部修補、預設過度工程、假完成、功能上正確但本質上錯誤的輸出,以及工作記憶的腐爛。

AI 程式碼膨脹正是這些問題的一種具體表現。

所以「程式碼很便宜」這句話其實不對,至少不完整。產生程式碼很便宜,擁有壞程式碼並不便宜。成本會在之後才出現:當下一個 agent 必須理解一個淺薄模組、維護一個假的抽象、繞過一個 no-op 子系統,或讀十段重複函式名稱早就講過的註解時,成本就來了。

模型是從充滿企業風格程式碼的世界學來的。它看過無數範例:每個功能都配一個 manager、一個 service、一個 provider、一個 config object、一個 test double、一個 logger、一個相容性包裝器,再加上一段說明顯而易見內容的註解。它學會了複雜度。然後在 agent 迴圈裡,它又把這種複雜度局部套用。最後得到的結果,很少是一個單一、災難性的檔案,而是一些看似合理的殘留物不斷累積。

測試有幫助。harness 有幫助。文件也有幫助。但它們不會自動產生品味。它們不會告訴你:某個子系統之所以存在,只是因為前一個 agent 曾經有個點子,卻從沒把那些管線拆掉。它們不會抱怨某個狀態層其實只是另一層的複製品。它們也不在乎下一個 agent 會浪費多少上下文去讀那些根本不該存在的註解。

P.S>

這次經驗其實讓我更深入地投入了;我真的更仔細去看了 SoLoud 依賴是怎麼被使用的、為什麼會有那麼多 UI thread 卡頓、Opus codec 為什麼會是技術挑戰(不要跟模型混淆,這裡只是我拿來管理本地音樂收藏時,比 MP3 更現代、更有效率的一種替代編解碼器),甚至還 fork 了 SoLoud plugin 並做了修改……現在這個 app 感覺順暢多了,我也看不到那些曾經讓我困擾的明顯問題。這反而讓我開始覺得,所謂「規格輸入/軟體輸出」的軟體工廠夢,可能被過度高估了;人的部分,重要的恐怕不只是驗證。


原文出處:https://dev.to/maximsaplin/debloating-the-ai-grown-codebase-2om


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝9   💬3   ❤️1
302
🥈
alicec
📝1   ❤️2
89
#4
我愛JS
💬1  
3
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登