如何避免 AI 代码垃圾

在之前的文章中,我提到过代码审查是工程团队的新瓶颈。原因是现在我们可以用 AI 比以往更快地生成代码。

但问题是我们无法足够快地审查代码,所以审查过程成为了新的瓶颈。正因如此,许多团队正在试图在这两个选项之间找到正确的平衡:

  1. 通过进行出色的人工代码审查来阻止进度,确保质量保持高水平
  2. 仅依赖 AI 代码审查和/或进行浅层的人工代码审查
这里的问题是,全行业对如何扩展代码审查以支持新创建代码的生成速度,同时又不产生糟糕的代码(例如"AI 代码垃圾"),还没有达成共识。

幸运的是,Aviator 的 CEO Ankit Jain 是今天文章的客座作者。他将分享他推荐的解决方案。

1、代码审查流程不是为 AI 时代设计的

这篇文章是 《如何终结代码审查》 的后续,该文认为传统的代码审查在 AI 加速的软件开发生命周期中已不再是一个可行的质量门。

自然的问题是,如果不是代码审查,那是什么?应该有什么替代方案?

这篇文章是实用答案。

速度图表看起来很棒。PR 合并得更快了。如果你只看编写的代码,数字讲述了一个 AI 加速输出的故事。但在这些指标背后,工程团队正在积累一种新型的技术债务。

那些 PR 要么未审查地搁置数天,要么被橡皮图章式通过,因为没有工程师有时间仔细审查 500 行的 diff。

我不是说工程团队应该从审查每一行代码变成什么都不审查。我主张将审批门向前移动,以防止我所说的"AI 垃圾"代码。那些能编译、通过基本检查、看起来合理但实际上有微妙的错误的代码。

现在,让我更详细地分享 AI 生成代码在"看起来没错"的情况下出错的方面。

2、AI 代码出错的 6 种方式

2.1 合理但不正确的逻辑

最危险的类型。代码读起来正确,语法正确,但逻辑是错的。这些 bug 在审查中很难被发现,因为它们需要理解代码本应该做什么,而不仅仅是它实际做了什么。

2.2 过度工程化

AI 模型在大量代码体上训练,包括企业模式、框架抽象和生产加固的架构。

要求解决一个真正只需要 15 行的问题,模型可能会产生一个 200 行的抽象层,预见了没有人要求的通用性。

2.3 规范盲视

模型生成的是好的通用代码,而不是适合你系统的代码。你的仓库有关于命名、错误处理、日志模式和模块边界的规范。

AI 经常忽略它们,不是因为它学不会,而是因为在提示词中没有被告知。

2.4 幻觉 API 和过时用法

模型自信地调用不存在的方法,引用两个版本前就移除的配置选项,或调用在当前服务上下文中不可访问的内部 API。

这些错误有时会立即被发现,有时只会在生产环境中被发现。

2.5 防御性过度

过多的 try-catch 块、静默的错误吸收、冗余的日志记录。代码以"悄悄吞掉"的方式优雅地处理失败,使调试变得困难得多。

2.6 货物崇拜模式

在不理解原因的情况下复制模式,比如在重试毫无意义的上下文中添加重试逻辑。对总是同步的调用使用断路器。看起来很彻底但实际上不映射到实际失败模式的错误处理。模式在那里;背后的推理不在。

共同点是,垃圾代码通过了眼测。它看起来像真正的代码。这恰恰是它在规模化时危险的原因。

3、为什么现有流程不够

代码审查是为不同的世界设计的。

AI 生成的 PR 在性质上不同,而不仅仅是规模上不同。当人类编写代码时,意图随着作者通过审查过程传递。

他们可以解释他们考虑的权衡、他们拒绝的替代方案、以及他们在其中工作的约束。即使没有写下来,这些上下文也是可访问的。

当 AI 编写代码时,意图可能存在于一个从未保存的提示词中,一个没有捕获决策过程的工单中,或者只在工程师的头脑中。实现被保存了;背后的推理没有。

测试捕获的比我们假设的要少。自动化测试是必要的但不够。测试验证的是测试作者想到测试的范围内的行为。

AI 生成的代码引入了按定义来说是意外的失败模式,因为如果工程师预见到它们,他们就会在提示词中指定它们。

你无法针对你不知道要表达的需​​求编写测试。

4、缺失的要素:意图

在实现之前形式化意图的概念并不新鲜。行为驱动开发、测试驱动开发和契约式设计都试图在编写任何代码之前以结构化、人类可读的形式定义行为。

这些方法经常被视为开销。在交付压力下,它们被跳过。AI 使它们以前从未有过的实用。

AI 可以帮助从简报中生成结构化的验收标准、规格和契约式描述。它也可以根据这些验证输出。当规格本身是 AI 辅助的,开销反对就消失了。

4、我们在真实功能上测试了意图驱动验证

在 Aviator,我们最近运行了一个实验来测试意图驱动验证方法。我们想要回答的主要问题是:

如果审查发生在代码编写之前会怎样?

团队实现了一个中等范围的全栈功能。一个分层的、按仓库的配置系统,零手动编写的应用代码。

所有内容都由团队在生成一行代码之前审查并同意的规格指导。

它跨越了完整的栈,包括用于配置历史的数据库模型和迁移、模式/验证层、合并仓库和全局配置的解析引擎、用于读取和更新配置的 GraphQL API(类型 + 变更)、将解析的配置集成到所有运行时子系统(沙箱配置、CI 处理、代码生成、聊天处理、人设选择)中,以及一个带有配置编辑器、变更历史视图和显示每个设置来源的来源指示器的前端设置页面。

这种功能,按传统方式完成,会在多个 PR 中产生数十条审查评论。

5、编写规格

规格是协作生成的:一个脚手架 PRD 被输入到 Claude Code,它被指示提出由团队同步回答的澄清问题。

生成的规格在两个层面进行了审查。

第一个是实现细节

特定的 UI 组件选择、输入验证需求、性能策略如配置查找的 Redis 缓存。

这些是通常在代码审查而非规格审查中出现的关注点。

第二个是范围和完整性

团队捕获了一个缺失的 UX 需求,质疑人设的功能是否定义得足够好可以包含,并标记了规格文本与实际设计不同步的地方(例如,引用了一个不存在的表)。

这些决策,如果推迟到实现后审查,将需要返工。规格审查花费了大约 10 小时的工程工作,分布在多个工程师之间。

它产生了 14 个验收标准,包含 65 个可检查项。它还产生了一个可以作为代码验证依据的文档。

6、实现和验证

我们使用 Claude Code 来实现规格。代理决定将实现分为四个阶段:

  1. 基础设施,
  2. 后端核心,
  3. 集成,以及
  4. 前端。

整个实现大约 6k 行代码,其中 40% 是应用代码,40% 是测试,20% 是 GraphQL 自动生成的文件。

值得注意的是,规格中没有明确要求编写测试的指令,这表明代理满足验收标准的策略是编写测试。

然后第二个代理针对生成的 PR 验证了 65 个验收标准。

这花了六分钟,并产生了结构化的报告,每个项目都有文件引用和解释:

  • 60 个通过,
  • 4 个失败,以及
  • 1 个部分通过。

一个人类彻底做同样的验证需要几个小时。

7、代码审查捕获了什么

人类审查者平均每个 PR 留下 10 条评论。发现的 bug 很少,主要的是一个陈旧的编辑器状态。代码审查捕获了规范级别的问题,如导入位置、枚举重复和命名模式。

这是一个预期的结果。规格审查捕获的是设计级别的问题,如缺失的需求、定义不足的功能和范围问题。

代码审查捕获的是规范级别的问题,那些需要对你特定代码库熟悉的方面。两个层面都是必要的,因为它们捕获不同的东西。

8、工程团队的五个防护措施

我们仍在学习如何编写足够精确以让代理可靠实现、同时又足够灵活以至于团队不会在规格上花比编码更多时间的规格。

工程师往往过度担心规格应该是什么以及编写它的开销。

如果我们简单地认为编写规格就像编写一个 JIRA 工单,这种压力就可以消失。对于一个简单的问题,单行意图或你在工单中写的内容可能就足够了,验收标准可以由 AI 生成。

我们的实验效果足够好,足以改变我们对验证 AI 代码和防止代码垃圾的思考方式。

以下实践不是一个完整的方法论。它们是一组团队可以逐步采纳的具体干预措施,从他们最有痛点的地方开始。

8.1 严格限制 AI 任务范围

大型、开放式的提示词产生最多的垃圾。"构建这个功能"给了 AI 太多的自由度。

范围明确、边界清晰的任务:一个特定的函数、一个定义的 API 表面、或一个受限的重构,产生的输出要好得多,也更容易验证。

对于看到 AI 价值在于处理大型任务的团队来说,这是反直觉的。价值确实在那里,但当大型任务被分解为更小的、明确定义的子任务,并在它们之间设置明确的检查点时,效果更好。

这与我们人类更高效工作的方式没有区别。

8.2 让意图成为一等工件

在生成任何代码之前,应该以一种可以被审查和批准的形式记录意图。这不需要正式的方法论。它需要的是在生成"如何做"之前记录"做什么"的习惯。

验收标准也可以由 AI 编写。意图实际上隐藏在用户与 AI 之间的提示词对话(决策树)中,或者用户可能添加在工单中的细节中。

在更严格的端,这意味着 BDD 风格的规格或可以用来验证输出的契约式描述。

确切的格式不如明确捕获意图的纪律重要。同样,我不是在试图增加开销和发明新文档来写。我们在提示 LLM 时已经很好地表达了意图。

在实践中,这很可能看起来像一个轻量级的 AI 辅助工作规格模板。

两三句关于范围的描述、验收标准列表,以及关于什么明确不在范围内的注释。让它成为 PR 要求。这同样可以由 AI 生成。

8.3 在实现之前审查意图

对于任何超过一定复杂度阈值的 AI 辅助任务,要求在代码生成开始之前获得规格批准。

最昂贵的审查是在代码存在之后发生的审查。

当一个设计决策在规格审查中被发现时,修复它只需要改一句话。当它在代码审查中被发现时,可能需要大量的返工。

将审查提前的团队,在生成代码之前批准规格,将高价值决策前置,让代码审查去做它真正擅长的事情:捕获实现级别的问题。

8.4 尽可能自动化

测试、linting 和类型检查捕获表面级别的垃圾,你绝对不应该跳过这些。正如我在我的文章中所说,对 AI 代码的信任是分层的。我们堆叠不完美的过滤器,直到没有任何东西通过。

8.5 建立和维护团队垃圾注册表

每个代码库都有 AI 一致搞错的模式。也许是特定的错误处理规范、命名模式、被违反的模块边界,或者 AI 偏好但你已弃用的库。这些都是可预知和可预防的。

垃圾注册表有两个目的:将这些模式反馈到提示词中(以防止它们发生)和 informing CI 检查(以在它们确实发生时捕获它们)。

9、你不需要完整的工作流就能看到效果

对规格优先工作流最常见的反对意见是它们更慢。

编写规格需要时间。获得审查和批准需要时间。已经通过 AI 工具高效工作的工程师不想减速。

看起来这种意图驱动验证增加了流程,但我们只是改变了我们习惯的工作方式。工具还没准备好。组织结构还没准备好。我们正在过渡中。

如果你的团队在这个过程的早期,最有价值的第一步通常是最简单的:要求为任何超过定义的复杂度阈值的 AI 辅助任务捕获来自提示词的意图。

格式一开始不必完美和严格定义。几句话的范围描述、简短的验收标准列表,以及关于什么不在范围内的注释。

从那里开始,在代码生成之前引入规格审查作为步骤。添加垃圾注册表。建立针对验收标准的自动化验证。

完整的工作流需要时间来建立。但朝这个方向迈出的每一步都会改善最终进入代码库的信噪比,并使 AI 投资返回它应该返回的东西。


原文链接:How to Avoid AI Code Slop

汇智网翻译整理,转载请标明出处