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