AI代码静默出错,如何补上检查
文章汇总读者补充的AI代码失误类型,强调结果格式正确也可能存在语义错误,例如将年度数据读成月度数据。给出的检查方法包括随机抽取五行,与原始资料逐项核对。
AI 深度解读
AI 写的代码越来越像那么回事,验收却未必更容易。这篇文章收集了读者补充的七种「差一点就对了」:数值填错但格式正确,修一行却顺手改了多个文件,测试全绿却漏掉失败路径,甚至修复文件根本没进部署包。
作者给出的办法很朴素:给每类问题配一个约 30 秒的检查入口。它值得读的地方,是提醒程序员把验收从「代码看起来合理」推进到「数据、交互、测试和交付是否真的对得上」。不过,这些建议主要来自读者经历,并不是有对照数据的效果验证。
格式正确、首屏正常,为什么仍然会错
作者最担心的是第 8 类问题:字段有值、结构合法、程序不报错,但值并不忠于来源。
读者 Maxwell 举的例子是,解析器可能把整片布局里的年费当成月费。这里出错的是数据的含义,类型检查和异常捕获都可能保持安静。一份错误报表因此可能存活数月。
他的快速检查方法,是随机抽 5 行,手工对照原始来源。随机很关键:只检查看着奇怪的行,会漏掉那些外观正常、实际错误的数据。作者补充,应当同时核对标签和单位;如果来源提供可信的列合计或行数,也可以交叉验证。
总数对得上只能增加信心,不能证明每个字段都正确。
第 10 类问题则发生在时间维度。Khali Sollis 提到,custom hook 或异步流程可能通过首次渲染检查,却因为遗漏响应式依赖、闭包读取旧状态,在用户连续操作后逐渐失步。大白话解释就是:回调执行时,拿到的还是之前那一刻的数据。
因此,刷新后点一次按钮远远不够。应该连续改变几次状态,再触发定时器和异步回调,观察后续结果是否仍与当前界面一致。
diff 中新增的 eslint-disable,尤其是针对 react-hooks/exhaustive-deps 的禁用,也应单独审查。它可能有合理原因,但警告消失不能代替依赖关系得到解释。
这两类问题有一个共同点:错误藏在表面正确的结果后面。检查字段是否存在、组件能否渲染,都只覆盖了其中一层。
测试全绿,究竟证明了什么
第 11 类问题,是本应暴露缺陷的测试边界被 mock 掉了。
读者 mp3sss 在 Electron 开发中遇到过这样的情况:AI 写出的 IPC handler 看似完整,却没有验证 renderer 发来的数据,而相关集成测试又使用了 mock。代码通过测试,并没有证明真实输入能被正确处理。
自动更新和崩溃报告也有类似风险。成功路径容易生成,断网、下载不完整、过期崩溃转储这些失败路径却被跳过。Mudassir Khan 提供了另一个例子:catch 返回空数组,所有测试仍然通过,因为实现和测试由同一个 agent 编写,共享了同一套错误预期。
作者建议,每次 AI 改动都主动制造一次相关失败:切断网络、发送非法 payload,或者让依赖不可用,确认系统能显式暴露错误,而不是悄悄交出空结果。
第 12 类问题是测试被改得更容易通过。除了把 toBe 放宽成 toBeTruthy,还要检查超时是否变长,以及是否新增 retries、jest.retryTimes 或 --retries。
旧超时可能确实不适合 CI,但也可能只是竞争条件或慢调用还在,测试终于不再投诉。判断标准应当是:修改者能否解释具体的时序问题。
Mudassir 团队还有一条低成本规则:任何涉及测试文件的 diff,都必须在 PR 中写一句修改原因;审查机器人会拦下跳过测试、把断言放宽到 toBeDefined 等情况。
实现和测试一起改过之后,绿灯首先证明的是新测试能通过。
作者顺带介绍了自己的 Claude Code 插件 proof-of-done。按原文描述,它会标记跳过或删除测试、弱化断言、新增 eslint-disable 或 @ts-ignore 等变化,并要求 agent 在完成报告中解释;它也支持逐个检查 monorepo 中的 package,提示测试未执行到的改动行。不过,原文没有给出误报率或实际拦截效果。
修复范围和部署产物,也得一起验收
第 9 类问题,是无声扩大的修改范围。用户只要求修一行,agent 却顺手改变量名、清理 import,并触碰其他文件。
Aliza Ali 指出,自动接受编辑会放大这种风险,但逐步确认也不能完全解决。因为每一步单看都合理,只有最终 diff 才能看见累计影响。
作者建议,读代码前先运行 git diff --stat;如果 agent 已提交,则使用 git diff --stat main...HEAD。文件变多不自动构成问题,修复可能确实需要配套类型和测试,但每个意料之外的文件都应该有解释。
第 13 类问题,是依赖真实存在,却用得太重。Artur Pokładek 曾接受 agent 引入的包,后来发现一个原本约 15 行代码能完成的小功能,带来了一串依赖。他建议查看 lockfile 的 git diff --stat:小功能导致数百行变化,就值得追问原因,但不能仅凭行数判错。
他的取舍也有边界:小型辅助函数可以自己写并测试;日期、解析和密码学交给成熟库;看似轻巧、实际不熟悉的替代包,反而最不放心。
第 14 类问题更直接:修复存在于本地,却没有发布出去。Guillermo Baigorria 的 AI 修复新增了一个补丁目录,测试全部通过,但部署脚本只打包固定目录,新目录没有进入产物。
下面这条命令用于列出相对 main 新增的文件,随后逐个核对交付内容:
git diff --name-status main | grep '^A'
要注意 grep '^A' 只筛新增文件;它不会验证文件是否已经打包。还需要对照 Dockerfile 的 COPY、package.json 的 files 列表,并检查实际构建包。
他们团队保留固定目录名单,是为了防止意外发布 .env。改进办法是检查构建产物、标记新增文件,再由人决定是否纳入。
我的判断
我认同文章把验收视线移出代码正文。AI 的问题未必是语法写错,也可能是误解单位、放宽测试,或者没理解部署约束。这些错误需要不同证据,单纯多读几遍函数很难覆盖。
但「30 秒检查」适合作为排查入口,不能当作完成标准。随机抽 5 行可以发现部分问题,对少见格式和局部解析错误却没有覆盖保证。复杂异步流程也不是连续点几次就能验收完毕。
相比只追加更多正常路径单测,主动制造失败、对照来源、检查产物,能补上不同盲区;它们也不能替代可靠的单测和集成测试。mock 和重试都有用途,关键是有没有遮住正在验证的行为。
原文提供的是具体现象和团队经验,没有对照数据证明哪一项收益最高。作者认为错误数值最危险,是有道理的风险判断,但不能据此给所有团队排出统一优先级。
能拿来做什么
- 接手 AI 生成的 React hook 时,连续改变相关状态,再检查定时器和异步回调的结果,并要求解释新增的依赖规则禁用。
- 审查 Node 数据处理改动时,随机抽 5 条记录对照来源,同时核对字段、单位和可信合计,避免只验证输出结构。
- 在 PR 模板里要求说明测试文件的修改原因,重点查看断言放宽、跳过测试、超时增加和新增重试,并为相关改动制造一次失败。
- 合并涉及新文件或新依赖的修复前,先看 diff 范围和 lockfile 变化,再确认新增文件确实进入最终部署产物。