分析 AI Agent 生成代码的特有问题——冗余膨胀、低效模式,提出代码审计和清理策略,对 AI 时代的代码质量很有参考价值。
31.7% 的减少量,测试仍全部通过
使用 AI Agent 会产生一种独特的"代码味道"……只看 GitHub 仓库就能看出,如果 README.md 冗长且难以理解、缺乏清晰度和简洁性,那么这个仓库的所有者一定是对 Claude 特别热情。我周末的一个实验从一个 AI 生成的代码库中削减了 40% 的代码行数(在不损害功能的前提下),这个经历启发深远,让我真切体验到什么叫 AI 代码膨胀。我把这些学习凝聚成了一个 agent skill。
去年秋天,我开始用 AI 完全构建一个 Flutter 应用 —— 一个媒体播放器。我不会说这是"感觉编码"—— 我要求 Agent 持续更新文档、推动自动化测试覆盖、投入反馈循环(比如为 Flutter 应用创建了人体工程学的 CLI)。这个应用可以正常运行,并允许从外部进行交互。整个工作流程有结构。
但我也没有花太多时间阅读代码 —— 我太懒了。或者说得更准确一点,阅读代码感觉像打开了一个潘多拉盒子。一旦你开始审视,你就不只是"检查"而已。你会发现奇怪的层级结构、不完整的修复、系统中仍然保留的旧想法、没有实际解释的注释、为已不存在的问题引入的过度抽象,然后你得做个选择:停下来重写吗?还是用周末还清我只因为看代码才发现的技术债?所以我一直在绕过这些问题继续发布。
应用能工作,但总感觉很不顺畅。Bug 修复通常是不完整的。新的 Agent 生成的功能似乎增加了复杂度,即使功能本身完成了。代码库有那种熟悉的 AI 气味:有很多局部能力、看似合理的地方,但越来越多的东西,其目的从外部难以理解。
我感觉代码库在不断膨胀。我没有足够的心智容量(或者说没有兴趣和动力)去深入查看 —— 认知债务不断堆积。
这是应用总体的 31.7% 代码量减少,所有功能保留、分析器清洁、Android 模拟器和 Linux 桌面构建上都通过了运行时检查。过程中还修复了两个潜在的 bug。
OpenAI 和 Anthropic 团队最近在 Codex/Claude 中发布了 /goal mode。我突然想到一个想法:"把 SLOC(源代码行数)作为目标" —— 这可能是一种不用亲手操作的懒人方式来清理代码库中的废话吗?
SLOC 是个粗略的指标,容易测量,但也很危险。不过粗略指标仍然有用,只要它能迫使模型寻找真正的简化,而不是在混乱之上再加一层解释。
那个实验最后变成了 /goal-sloc,一个小的 agent skill,用代码行数作为强制函数,同时防止 Agent 通过造假来玩弄这个指标。
有些工作很有价值,但对 SLOC 数字的改进不多。深层模块重组、更好的边界定义、hook/controller 重构能改进设计,同时保持 SLOC 基本中立。这清晰对应了 Pocock 关于深度模块的观点:AI 通过简单接口和可测试的边界工作时表现更好,而不是在浅层、泄漏的模块中挖掘。这是个有用的发现:如果目标是代码质量,SLOC 不能是唯一的奖励指标。最好的架构工作往往在行数计数上看不出来。
还有一个硬下限。Flutter 项目包含生成的代码和平台脚手架。有些如果写成自定义原生代码可以削减,但大部分就是下限:Gradle、CMake、Xcode 文件、manifests、计入代码行的二进制资产,以及你选择支持或作为产品决策割掉的平台目录。
完整详情见这里。
这个应用是个 Flutter 代码库,去年秋天开始,100% 由 AI 协助构建。人类的贡献不是"我理解每个子系统",而是"我搭建了框架、写了规范、要求了测试、并持续指导"。这种区别很重要。
人们常讲一个关于 AI 编码的舒服故事:如果你有测试、规范/文档和反馈循环,就是在正确使用它。不是感觉编码,而是 Agentic Engineering 🕶️……我仍然相信这大体上是对的。但这不意味着代码保持健康。这意味着代码能继续移动,而健康状况在悄悄退化。
退化不是一次戏剧性的失败:
这是 AI 开发代码的特有危险。它往往看起来不愚蠢。每次添加在当时都是合理的。膨胀来自积累:每个 agent 轮次都留下一点局部妥协、一点解释残留、一点防御性抽象。经过足够多的轮次,即使每步看起来都合理,系统也会变重 —— 故障模式是复合的。
Matt Pocock 的演讲"软件基础知识比以往任何时候都更重要"击中了我的痛点 —— 我不愿意深入代码,从未有过勇气……John Ousterhout 把复杂性定义为"系统结构中任何使其难以理解和修改的东西"。《实用程序员》讲述软件熵:一次次本地改变,不关心整体设计。Pocock 的表述更尖锐:代码不便宜。坏代码在 AI 时代更贵,因为难以改变的代码库既阻止你自己也阻止 AI agent 做出质量改变。
我喜欢这个框架。我也知道自己不会坐下来对一个我半委托给机器的代码库进行英雄式的架构审查。我需要一个能委托的约束。
这个数字容易测量。它给 agent 一个目标。它把"请简化代码库"从品味争论变成了有计分板的游戏。在 Claude Code 中,我尝试用 /goal mode 作为外层循环:设置目标、让 agent 工作、测量、继续。
最初我希望实现一种自主的循环:agent 持续工作、检查自己、最终返回一个更小的、仍能正常运行的应用。有点像旧的 Claude 编译器/自主实验,你过一段时间后回来检查结果。
没有这样。Claude Opus 4.8 太频繁地跟我交互。起初这感觉像是 goal 循环没有达到我想要的效果。现在回想起来,我认为频繁的中断可能拯救了这个过程。看过那些交互,我不认为完全自主操作会很顺利。agent 需要更正,特别是关于什么算是真正的进展……
减少 SLOC 的廉价方式显而易见:删除注释、打包行、重新格式化、把代码移出计数路径、提取让计数器变小但系统更难理解的辅助函数、如果提示不够谨慎就删除文档和测试。Agent 不需要恶意才会这样做。它只需要优化可见的奖励。
我确实看到了奖励黑客。
一些早期的"胜利"是注释清理。这可能看起来像作弊,但我不认为完全是虚假的。过度的 AI 注释是真正的问题。它们吹胀上下文。它们让未来的 agent 理解变得更糟。它们解释明显的代码,同时隐藏了真正重要的少数注释。我现在的规则很简单:每条注释都必须赚得自己的位置。
不过,注释删除不能是策略。如果代码库只是因为周围的文字消失了而变小,系统在本质上并不更简单。它只是更安静。
这种区别成为了这个 skill 的中心。
/goal-sloc 不是一个说"让它更小"的魔法提示。核心意图是让 agent 证明它不是在自欺欺人。
这个 skill 从预检开始:
然后它给 agent 一个诚实的删除顺序:死代码优先,占位符子系统第二,错位的开发/测试脚手架第三,真正的重复之后,注释卫生仅作为卫生,然后是更有风险的干净室重写、架构简化,最后是委托给库,其中库是真正更好的工程。
核心规则是自审计:每隔几个里程碑,把删除分类为结构性或廉价。如果廉价杠杆占主导,agent 必须停下来、承认它在玩弄指标,或报告结构潜力已枯竭。
这听起来几乎太明显了。在实际运行中并不明显。没有这条规则,模型不断漂向廉价杠杆,因为廉价杠杆让计分板移动。
这个 skill 还告诉 agent 何时停止。这很重要。Agent 不擅长承认下一个增量不再值得风险。如果提示一直奖励活动,它们会制造混乱。没有停止条件的 SLOC 目标会邀请重构-后悔-恢复循环:改变系统、破坏什么、修补它、重新展开代码,称整个混乱为学习。
正确的结局有时是:我们已接近下限;剩余工作是 SLOC 中立的架构或产品决策;问人类。
我在这个长周末用了 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 教程中能找到的最基础的状态管理,这就像在 React 中用 prop-drilling 而不是 Redux。
这个实验符合我在《AI Agent 失败模式超越幻觉》中写过的模式。问题不仅是幻觉。还有局部修补、默认过度工程、虚假完成、功能但错误的输出、工作记忆衰退。
AI 代码膨胀是这一点的具体体现。
这就是为什么"代码很便宜"感觉不对,或至少是危险的不完整。生成代码很便宜。拥有坏代码不是。成本在后来才出现,当下一个 agent 需要理解一个浅层模块、保留虚假抽象、绕过没有作用的子系统,或读十条重复函数名已说的注释时。
模型从充满企业风格代码的世界学习。它看过百万个例子,其中每个功能都配备了 manager、service、provider、config 对象、test double、logger、兼容性包装器,以及解释明显事物的注释。它学到了复杂性。然后在 agent 循环里,它在本地应用这种复杂性。结果很少是灾难性的单个文件。而是一堆看起来合理的残留品积累。
测试帮助。框架帮助。文档帮助。但它们不会自动创造品味。它们不会告诉你一个子系统只存在因为早期某个 agent 有个想法且从未移除管道。它们不会抱怨状态层镜像另一个状态层。它们不会在意下一个 agent 会浪费上下文读应该不存在的注释。
这个经历实际上让我更深入投入,我确实查看了 SoLoud 依赖如何使用、为何有很多 UI 线程冻结、Opus 编解码器为何是个技术挑战(不要跟模型混淆,它只是比 MP3 更现代高效的替代品,我用于本地音乐库),甚至 fork 了 SoLoud 插件并做了修改……现在应用感觉顺畅得多,我没看到之前困扰我的明显问题。这让我想到软件工厂的梦想(规范输入/软件输出)可能被高估了,人类部分不仅仅是验证。
有些评论可能仅对已登录用户可见。登录查看所有评论。有些评论已被文章作者隐藏 - 了解更多。
如需进一步行动,你可考虑屏蔽此人和/或举报滥用。