合并 AI 代码前必查的 10 项清单
AI 生成代码表面合理但可能藏细微 bug、安全问题或团队无人理解的逻辑,作者提出「视同陌生开发者的 PR」这一审查框架并给出具体检查项。
完整中文译文
AI 可以秒级生成代码。
它可以生成一个函数、构建一个 API 端点、写一条 SQL 查询、创建测试、重构一个组件,甚至搭建整个功能。
这种速度令人惊叹。
但代码生成之后,有一个危险的时刻随之而来:
你决定它是否可以安全合并的时刻。
AI 生成的代码可能看起来完全合理,但仍然包含微妙的 bug、不必要的依赖、安全问题、错误的假设,或者团队中没有人完全理解的逻辑。
这就是为什么我不再把 AI 生成的代码当作「能跑就算搞定」的东西。
我把它当作一个来自非常快速的开发者的 PR——这个开发者并不具备应用的完整知识。
合并之前,我会检查这 10 件事。
我真的理解这段代码在做什么吗?
这是我的第一项检查,可能也是最重要的一项。
如果我无法解释生成的代码,我就不会合并它。
以下这些都不重要:
- 测试通过了
- 应用能跑
- 代码看起来很干净
- AI 很自信地解释了它
- 功能在我本地环境能正常工作
如果我不理解这段逻辑,我就是在接受一个维护问题。
例如,假设 AI 生成了这段代码:
const result = items
.filter(item => item.active)
.reduce((acc, item) => {
acc[item.category] = (acc[item.category] || 0) + item.value;
return acc;
}, {});
但合并之前,我仍然想知道:
- 当 category 缺失时会发生什么?
- value 一定是数字吗?
- 非活跃项目真的应该被排除吗?
- 这个聚合真的符合业务逻辑的需求吗?
代码语法正确不等于代码逻辑正确。
如果我无法解释它,就不会批准它。
它真的解决了问题吗?
AI 非常擅长解决 prompt 中描述的问题。
问题在于,prompt 可能并没有描述真正的问题。
这种情况经常发生在开发者给 AI 一个简化请求时,比如:
「给这个端点添加认证。」
生成的解决方案可能在技术上添加了认证。
但在当前应用中,认证意味着什么?
- 这个端点是否还需要授权?
- 是否有不同的用户角色?
- 管理员是否应该访问不同的资源?
- 这个端点是否暴露了敏感信息?
- 应用是否已经有认证中间件?
AI 可以针对你给出的请求进行优化。
但它不会自动理解更大的产品需求。
合并之前,我会问:
这段代码解决的是实际的问题,还是仅仅解决了 prompt 中描述的问题?
这个区别很重要。
在功能之外还改了什么?
AI 生成的代码制造麻烦最简单的方式之一,就是改变的内容超出你的请求范围。
你请求一个功能。
结果改了好几个文件——配置、依赖、错误处理、数据库逻辑、现有组件、格式化、测试。
一下子,一个 20 行的功能变成了 400 行的 PR。
这是一个警告信号。
我总是检查 diff。
或者,如果通过 GitHub 协作,我会审查 PR 中每个改动的文件。
我想回答一个简单的问题:
为什么每行改动的代码都需要改动?
如果答案不明确,我就缩小范围。
越小的改动越容易审查、测试、调试和回滚。
这对于 AI 编码 agent 尤为重要,因为它们可能接触到的仓库上下文远比你最初让他们修改的那个文件要大。
有没有安全问题?
这是我会变得多疑很多的环节。
AI 生成的代码可能引入安全问题,即使代码看起来是正常的。
我会特别检查:
- 硬编码的密钥
- 弱认证
- 缺失的授权检查
- SQL 注入
- 命令注入
- 不安全的文件处理
- 不当的输入验证
- 敏感数据泄露
- 不安全的 API 调用
- 不安全的反序列化
- 过度授权
例如,如果 AI 生成了这样的数据库代码:
const query = `SELECT * FROM users WHERE email = '${email}'`;
但直接将用户输入插入 SQL 查询会产生严重的注入漏洞。
一个更安全的实现会使用参数化查询:
const query = "SELECT * FROM users WHERE email = ?";
const result = await db.query(query, [email]);
具体实现取决于数据库库,但原则不变。
安全不能委托给代码生成器。
OWASP 当前关于 AI 安全编码的指南强调,人类需要对 AI 生成的更改(包括安全和可维护性考虑)拥有所有权并进行审查。
NIST 也建议,AI 生成的软件内容应由人类监控和验证,而不是盲目信任。
所以我的规则很简单:
如果 AI 生成了它,我仍然对它的安全性负责。
AI 是否引入了我不需要的依赖?
这一条出奇地容易被忽略。
「怎么把这个日期转换成这种格式?」
AI 可能会建议安装一个新的包,而不是使用项目中已有的工具。
现在你有了一个新的依赖。
多一个包意味着:
- 更多的维护工作
- 更多的更新
- 更多的潜在漏洞
- 更多的包体积
- 更多的许可考虑
- 更多的供应链风险
在接受一个新依赖之前,我会问:
- 我们真的需要它吗?
- 我们是否已经在其他地方解决了这个问题?
- 这个依赖值得信赖且有人维护吗?
我宁愿用项目已有功能写五 行能理解的代码,也不愿意为一件小事引入一个包。
除非你明确告诉它,否则 AI 没有理由关心你的依赖树是否臃肿。
测试是否真的在测试正确的东西?
最危险的假设之一是:
「AI 写了测试,所以代码一定是安全的。」
测试也可能出错。
AI 可能生成验证实现而不是验证预期行为的测试。
假设需求是:
用户不应该能访问另一个用户的资料。
AI 生成的测试可能验证请求返回 403。
但——不同的用户 ID?管理员用户?缺失的认证?过期的会话?被操纵的请求参数?直接 API 访问?
测试套件可能覆盖率很高,但仍然遗漏了重要的行为。
这些测试没有检查的可能出问题的情况有哪些?
然后我为这些情况添加测试。
至少,我会检查:
- 预期场景是否正常工作?
- 当用户提供坏数据时会发生什么?
- 当某些东西缺失时会发生什么?
- 在极限情况下会发生什么?
- 当依赖失败时会发生什么?
- 有人能访问他们不应该访问的东西吗?
好的测试不在于生成大量测试,而在于测试有意义的行為。
边界情况会怎样?
AI 倾向于为显而易见的场景生成解决方案。
真实应用很少只存在于显而易见的场景中。
假设你让 AI 创建分页。
正常情况可能是:
但以下情况会怎样:
- 或者完全不传参数?
边界情况是生产 bug 经常藏身的地方。
对于每个 AI 生成的功能,我会问:
- 当输入为空时会发生什么?
- 当输入无效时会发生什么?
- 当输入意外地大时会发生什么?
- 当依赖失败时会发生什么?
- 当两件事同时发生时会发生什么?
你不需要预测每一种可能的失败。
但你应该主动寻找生成代码所做的假设。
代码是否比它需要的更复杂?
AI 有过度设计的倾向。
让它解决一个小问题,你可能会收到:
- 一个新的抽象层
- 多个辅助函数
- 一个配置层
- 几个接口
- 一个新的工具类
- 额外的错误处理
- 一个你没有要求的设计模式
有时候这些是合理的。
考虑这个简单需求:
将字符串转换为小写。
你可能不需要一个新的工具架构。
const normalized = input.toLowerCase();
最好的代码不是架构最多的代码。
而是清晰解决问题、同时适应现有系统的代码。
在合并 AI 生成的代码之前,我会问:
- 在不丢失正确性的前提下,能更简单吗?
它与现有代码库一致吗?
AI 可能生成技术上有效但不属于你项目的代码。
假设你的代码库一贯使用:
async function getUser() {}
但 AI 引入了一个完全不同的模式:
class UserRepository {
async fetchUser() {}
}
第二种方法本身没有错。
但如果你的整个应用都遵循第一种模式,为一个功能引入新架构会造成不一致。
需要考虑一致性:
- 命名约定
- 文件夹结构
- 错误处理
- 日志记录
- 测试模式
- API 约定
- 数据库访问模式
- 类型定义
- 现有抽象
好代码不是孤立存在的。
它是存在于代码库之中的。
问题不仅仅是:
「这段代码对这个代码库来说是好的吗?」
我能在代码审查中为这段代码辩护吗?
这是我的最终测试。
想象另一个开发者问:
「你为什么这样实现?」
「因为 AI 生成的。」
这不是一个答案。
AI 不拥有这个 PR。
NIST 的安全开发指南强调代码审查和分析,而 OWASP 明确建议对 AI 生成的更改拥有所有权并由人类批准。
开发者应该能够解释:
- 什么改变了
- 为什么改变
- 做了哪些假设
- 如何测试的
- 存在什么风险
- 为什么选定的方案是合适的
如果你无法解释这些事情,你可能还不应该合并这段代码。
我的 AI 代码审查清单
在合并 AI 生成的代码之前,我会过一遍这个清单:
- [ ] 我理解了生成的代码
- [ ] 它解决了实际的需求
- [ ] 我审查了完整的 diff
- [ ] 没有改动不必要的文件
- [ ] 没有引入安全漏洞
- [ ] 没有添加不必要的依赖
- [ ] 测试验证了实际的行為
- [ ] 考虑了边界情况
- [ ] 实现没有不必要的复杂性
- [ ] 遵循了现有代码库的模式
- [ ] 我能够解释并为这个实现辩护
最后一点很重要。
如果你无法为代码辩护,就不要合并它。
AI 应该让代码审查更重要,而不是更不重要
有一种常见的假设认为 AI 生成的代码减少了对开发者的需求。
我认为它改变的是开发者的职责。
当手动编写一切时,你花大量时间在产生代码上。
当 AI 生成大部分代码时,瓶颈可能转移到别的地方。
你现在需要花更多时间问:
- 这可维护吗?
- 这属于这里吗?
这意味着代码审查变得更加重要。
AI 可以提高代码进入仓库的速度。
这使得人类判断更有价值,而不是更没有价值。
真正的技能不是让 AI 写更多代码
用代码行数来衡量 AI 生产力是很诱人的。
这是错误的指标。
一个生成 1000 行代码但花了两天调试的开发者,不一定比写了 200 行正确代码的人更有生产力。
更好的问题是:
AI 是否有助于我更快地生产可靠的软件?
这需要的不仅仅是生成。
还需要理解、测试、审查和判断。
使用 AI 的最好的开发者不一定是生成最多代码的人。
而是知道哪些生成的代码值得通过审查过程留下来的人。
AI 编码工具非常有用。
它们可以帮助开发者探索不熟悉的 API、生成样板代码、写测试、解释代码、重构重复逻辑,以及更快地从想法到可工作原型。
但生成的代码终究是生成的代码。
它需要在代码库中赢得自己的位置。
合并之前,我想知道十件事:
- 我理解它吗?
- 它解决了真正的问题吗?
- 它有没有改动不必要的东西?
- 它安全吗?
- 它引入了不必要的依赖吗?
- 测试真的证明了行为吗?
- 边界情况会怎样?
- 代码能更简单吗?
- 它适合现有代码库吗?
- 我能在代码审查中为这个决定辩护吗?
如果这十个问题的答案都是「是」,我合并它会安心得多。
目标不是不信任 AI。
目标是恰当地信任它。
AI 可以生成代码。
但开发者仍然负责决定这段代码是否属于生产环境。