# 审 AI 生成的代码：为什么“看着没毛病”不算通过

从先看改了什么、再核对它对项目的假设，到边界、错误处理和权限这三处它最常替你拍板的地方，一套有重点的审查动作

> 代码审查 · AI 编程协作 · 边界条件 · 约 6 分钟 · 09 月 26 日

## 本篇要点

1. 人类写错代码时会留下“卡壳的痕迹”，AI 生成的代码没有这些痕迹，所以“读起来别扭”这个平时最有效的审查信号在 AI 的 diff 上失效，“看着没毛病”不能当作通过。
2. 替换的问题是：这段代码在假设什么，而这些假设谁承诺过——它指向可以核实的东西，而不是你的感觉。
3. 审查从最客观、最不依赖判断力的部分开始：先看改动了哪些文件、先读被删掉的行、把测试文件的改动单独拉出来看。
4. 被删掉的边界守卫读起来像“简化”，这是最容易漏、也最容易造成回归的一类改动，所以要刻意先读减号。
5. 第二类是它对项目现状的断言：依赖是否真实存在、方法签名是否匹配你装的版本、引用的配置项是否真的存在。研究测得被推荐的包中约 19.7% 是虚构的，而且同一个提示再问一遍，约有一半的情况会再次给出同一个不存在的包名。
6. 第三类是它替你拍板的地方，集中在边界、错误处理和权限：这三件事通常不写在需求里，你不提，模型就必须填一个值，它填的是最像样的，不是最安全的。
7. 错误处理要看它是处理错误还是隐藏错误；把异常吞掉返回默认值，会把错误推迟到离源头很远的地方爆发。
8. 用户实验显示，能用 AI 助手的被试写出更不安全的代码，同时更相信自己写得安全；SQL 注入一项是 36% 对 7%。
9. “测试通过”是生成代码的同一个过程写出来的一句话，要自己跑，并检查用例数是往上走还是原地不动；把一行逻辑改错看测试会不会失败，可以验证测试是否真的有效。
10. diff 大到读不完，问题出在任务规模，正确做法是拆开，而不是更努力地读。
11. 这套动作适用于要留下来的代码；格式化、lint、类型检查和自动 review 应该交给机器先跑，人把注意力留在意图、范围和被默默填上的默认值上。

---

上一篇审的是计划，在代码写出来之前。这一篇审的是已经躺在你面前的 diff：要不要留下它。

麻烦在于，你平时判断“这里得仔细看看”，靠的是一个很具体的信号——读起来别扭。名字奇怪、注释含糊、风格和别人不一样、留了个 TODO，这些痕迹说明作者在这里卡过壳，卡壳的地方通常就是 bug 藏身处。AI 生成的代码把这个信号抹平了：命名统一、格式干净、该有注释的地方都有注释，它写对和写错的时候是同一种语气。所以“我看着没问题”这句话，在一份 AI 写出来的 diff 上几乎没有信息量。不是你看得不认真，是这个信号本身失效了 [3]。

要换的问题是：**这段代码在假设什么，而这些假设谁承诺过？** 它比“看起来对吗”好用，是因为它指向可以核实的东西，而不是你的感觉。

## 一、先看它改了什么，而不是先读逻辑

第一个动作是打开 diff，不是打开文件。模型经常动手改它并不需要改的地方——重排一个工具函数、删掉一个它觉得没人用的常量、顺手“简化”一处分支。只读最终文件，这些改动是隐形的。

先看文件列表。一个说好改两个文件的任务动了九个文件，这件事本身就是一条发现，在你读第一行逻辑之前就该记下来：多出来的七个文件，改动理由是什么？答不上来的部分拆掉。

然后先读减号，也就是被删掉的行。这是整套动作里收益最高、也最容易被跳过的一步。模型删掉一个边界判断或一个前置守卫，产出的是一个读起来像“简化”的 diff——新增的那几行里没有任何线索提示你少了什么，而少掉的分支会在线上以别的方式回来。看新增容易，看删除需要刻意，所以顺序要定死。

第三步，把测试文件的改动单独拉出来看一遍。实现和测试同时改是正常的，断言变松不是。`assert count == 3` 被改成 `assert count > 0`，夹在五十行实现改动里你多半扫过去看不见；单独拉出来，它一眼就露出来。

这三步排在最前面，理由很单纯：它们不需要判断力，只需要看得见。做完之后，后面要仔细读的范围往往已经缩小了一圈。

## 二、再核对它对项目的每一句断言

计划里的“复用现有函数”是断言，代码里的 import 也是断言，而且更好核实。

**每一个不熟悉的依赖，去包仓库确认它真实存在。** 这不是小概率事件：一项覆盖 16 个模型、223 万个被推荐包的研究测得，其中 19.7% 的包是虚构的，商业模型约 5.2%，开源模型约 21.7% [4]。更麻烦的是它不是随机噪声——同一项研究发现，用同一个提示再问一遍，约有一半的情况会再次给出同一个不存在的包名。也就是说，你再看一遍、再问一遍，它不会自己消失，只会显得更可信。

**方法签名要对着官方文档看，不要对着它的解释看。** 它的解释和它的代码来自同一个生成过程，一起错是常态。有个可操作的习惯：抽它总结里的一条事实性断言去核实，如果这条是假的，其余部分要重读一遍——这两件事的可信度是绑在一起的。

**它引用的配置项和环境变量，真的存在吗？** 这类问题最阴，因为开发环境跑不出症状：配置读不到会走 fallback 分支，程序照常运行；等配置真的加上去，或者别人加了一个名字相近的 key，行为才变。

## 三、它最常替你拍板的三处

上面两类问题都能直接核实。还有一类是它替你做了决定，而你没意识到这里有决定，集中在三个地方。

共同原因是同一个：这三件事通常不写在你的需求里。你不提，模型就必须填一个值，它填的是最像样的默认值，不是最安全的默认值。这不是它偷懒，是它的工作方式就这样——回到第一篇那个问题，正确性由谁定义。

**边界。** 模型写的是演示用例：三条记录、每个字段都在、每个请求都返回 200。你要问的是每个函数“它能合法收到的最丑的输入长什么样”——空列表、零字节文件、`page=0`、缺字段的响应、超长字符串。这些不会报错，只会悄悄给出一个错的结果。

**错误处理。** 两个极端，都属于“写了一个把错误藏起来的动作”：一是把异常吞掉返回默认值（`except Exception: return {}`），二是包一层 try/catch 只打日志。危害不在于出错时没处理，而在于错误被推迟到离源头很远的地方才炸出来：调用方拿到 `{}`，理解成“这个用户不存在”继续往下跑，最后在距离原始问题八百里的地方空指针。判断只有一句：这个 catch 是在处理错误，还是在隐藏错误？

**权限和信任边界。** 这里有份很直接的实验证据：能使用 AI 助手的被试，在五个编程任务里的四个写出了安全性更差的代码，同时更倾向于认为自己写得安全；其中一个任务里，用 AI 的一方有 36% 写出了可被 SQL 注入的实现，对照组是 7% [5]。原因不难还原：模型并不恶意，它只是照着你的话做。你说“加一个按 id 查订单的接口”，它就把接口写出来，跳过你没提的归属校验——在它看来那不是缺少，而是你没要求。

所以凡改动涉及认证、钱、用户数据，就不要靠扫一眼，把这些方法单独列出来逐个问：绕过界面直接调这个接口会发生什么？这条规则由谁强制？答不上来，说明这段还没准备好合并 [2]。

## 四、要证据，不要解释

“测试全部通过”是一句话，而且是由生成代码的同一个过程写出来的。自己跑一遍，并注意用例数：它应该往上走了，而不是原地不动或少了几条。还有个很便宜的做法能验证测试是否真的有效——把被测的一行逻辑改错，看测试会不会失败；改了逻辑测试还绿着，这个测试是假的。

另外，diff 大到读不完，问题出在任务规模，不在你的耐心。一份你读不完的改动，最后一定会被点通过。这时候正确的动作不是更努力地读，而是让它拆开 [2]。

## 五、这套动作适用于哪里

它面向要留下来的代码。一次性的、跑完就丢的脚本不需要这么走流程——但如果你已经开始说“先这样，以后再收拾”，它就已经不是一次性脚本了。

机器能做的部分应该先交给机器：格式化、lint、类型检查、CI 里的静态扫描、第二个模型的自动 review。它们在“局部可以穷举”的问题上比你可靠，也省时间。但它们判断不了“这是不是你要解决的问题”，也判断不了你自己项目里的约定——这些信息只有你有 [1]。合理的分工是：让机器跑在前面清掉形式问题，把你的注意力花在意图、范围和那些被默默填上的默认值上。

## 术语表

- 审查信号失效：人类代码里的困惑痕迹（怪名字、含糊注释、风格不一致）是“这里要慢看”的提示，AI 生成的代码把这类痕迹抹平了，所以这个信号不能再用。
- 范围审查：先看这次改动动了哪些文件、删了哪些行，再看逻辑本身；超范围改动和被删除的守卫都藏在这里。
- 断言核对：代码里“某个包存在”“某个方法这样调用”“某个配置项在那”这类对项目现状的说法都有真假，可以直接查文档和包仓库验证。
- 默认值替决：需求里没指定的地方，模型必须填一个值，它填的是最像样的那个，通常不是最安全的那个；边界、错误处理、权限是最常被这样填掉的三处。
- 证据与解释的区分：测试是否通过、说明是否成立，都要自己跑一遍、核实一条，而不是接受它写出来的那句话。

## 来源

1. [GitHub 官方：Review AI-generated code — 验证意图、评估质量、核查依赖、识别 AI 特有陷阱](https://docs.github.com/en/copilot/tutorials/review-ai-generated-code)
2. [Reviewing AI-generated code: a practical checklist — 先看文件列表、先读删除行、测试文件单独 diff、自己跑测试](https://continuumcode.ai/guides/reviewing-ai-generated-code/)
3. [How to review AI-generated code: a human's guide — 为什么“读起来别扭”的审查直觉在 AI 代码上失效](https://diffdojo.com/blog/how-to-review-ai-generated-code)
4. [We Have a Package for You! A Comprehensive Analysis of Package Hallucinations by Code Generating LLMs — 223 万个被推荐包中 19.7% 不存在](https://arxiv.org/abs/2406.10279)
5. [Do Users Write More Insecure Code with AI Assistants? — 有 AI 助手的一组写出更不安全的代码，却更自信；SQL 注入 36% 对 7%](https://arxiv.org/abs/2211.03622)

---

原文：https://pangzhengboyin.com/articles/reviewing-ai-generated-code-plausible-diff-d168d6b2

> **庞征博引** · 想学的，慢慢都会
>
> 庞征博引是把想学的东西写成连载的 AI 学习工具。说出想学什么，它会先了解你的基础，再把主题写成一篇篇 5–10 分钟能读完的文章；边读边问，接下来学什么跟着你走。这篇就是这样写出来的。
>
> 开始你自己的连载 → https://pangzhengboyin.com
