这周我 review 了一个 AI 生成的支付回调模块,代码风格干净得像教科书,注释甚至比我平时自己写的还规范。但我在第 47 行停下来问了个问题:“如果这里数据库超时了,这笔订单在用户那边会怎么提示?”对方沉默了几秒,最后说“那我再想想”。
这不是孤例。我的团队从半年前开始大规模使用 AI 编程工具,PR(Pull Request)提交速度肉眼可见地快了一倍,功能联调的节奏也顺了很多。但真正让我紧张的事情也随之而来:代码合入主干的步伐越快,我心里越没底。直到连续三次线上问题都回溯到 AI 建议的“最优实现”上,我才认真做了个决定——把代码审查重新放到所有环节的第一优先级,而且不是嘴上说说。
这篇文章想聊的,就是我在这个过程中的完整思考:AI 到底把开发带到了什么状态,为什么代码写得更快之后 Review 反而更重要,以及我现在实际执行的一套让人放心的 Review 流程和检查清单。不管你是个人开发者还是带团队的一线主管,只要你日常在用 AI 辅助写代码,这篇文章就值得读完。
1. 代码写得越快,我对模型的信任越打折
1.1 “快”带来的不是自信,而是审查压力后移
先讲一个身边的数据。我们后端仓库在用上 AI 编码助手之后,单周的 PR 数量从 15 个增加到了差不多 30 个,人均代码提交量涨了大约 40%。看起来效率是实打实翻上去了。
但有个现象很微妙:大家提交代码时默认 AI 生成的逻辑是“对的”,于是自己敲键盘逐行读代码的时间大幅减少。以前手写代码时,每个人写完都要自己跑一遍逻辑、确认边界条件;现在按一下 Tab,代码就出来了,很多人会直接打上自测通过的标签,然后抛到 Review 里。
结果就是,Review 变成了最后一道也是唯一一道防线。以前担心自己写错,Review 是“复核”;现在担心模型“自信地犯错”,Review 变成了“拦截”。压力没有消失,只是完整地从编码者身上平移到了 reviewer 身上。问题是,reviewer 没有变多,Review 也没有因此获得更多时间预算。效率提高了,质量防线却变薄了,这就是我必须重新把 Review 拉回第一优先级的原因。
1.2 模型的三层不透明:代码正确、业务正确、边界正确
我花了一段时间去理解,为什么 AI 生成的代码特别容易让人放松警惕。后来我想明白了——它不是单个错误,而是三层“不透明”叠加在一起。
第一层是代码正确性。AI 生成的语法、命名、函数结构通常挑不出毛病,风格甚至比很多中级开发者更统一。这层不透明让大多数人误以为“质量很好”。
第二层是业务正确性。AI 并不真的理解你的订单状态机、你的支付回调时序、你的库存扣减规则。它能帮你写出“看起来在调用正确接口”的代码,但它不知道在你的业务流程里,有没有存在消息丢失、重复通知、异步乱序的场景。
第三层是边界正确性。这是最阴险的:AI 能写出循环、排序、分页、重试,但往往只在“典型输入”下成立。一旦用户数据分布变了、配置项变成空值、网络抖动达到临界值,它预设的逻辑就会在偏差点上崩掉。
三层不透明叠加带来的效果是,你审查代码时看到的是一个“处处正常”的表面,你需要自己把模型没说出来也不知道的上下文全部检查一遍。这就是为什么 Review 不能走马观花。过去我们注意力放在“这段代码逻辑对不对”,现在另外两个问题同等重要:这段代码在真实数据下会不会对,这段代码放在真实业务链路里会不会对。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. AI 生成代码的高危区:我踩过的五类坑
与其泛泛地说“AI 会犯错”,不如把我在项目里遇到的、以及帮朋友团队排查过的典型问题归纳出来,这几类是我默认每次 Review 都会重点盯的区域。
2.1 边界条件:AI 对“空”和“相等”的判断太乐观
第一类是空值。AI 写判空逻辑时非常爱写 if (data != null),看着没问题对吧?但真实场景里你常遇到的是 if (data.isEmpty()) 这种检查没做,或者是用 == 比较字符串,甚至是拿一个可能为 null 的字段直接调方法。这些错误在类型不严格的动态语言里尤其高发。
我这边是 Java 为主,AI 生成的一段工具类里,明明入参可能传 null,它却直接在第二行就调用了 field.getName()。单元测试用例填的是正常值,当然跑得通,但上了生产,一个异常请求就能炸掉整个接口。我在 Review 时会专门看:入参解析、空集合、空字符串、零值、超长输入、首次登录、最后一条数据,凡是边界性的输入,必须逐项问一遍。
2.2 并发场景:AI 默认了不存在的安全感
第二个高发区是并发。AI 训练数据里充斥着大量的“教科书正确”代码,它非常习惯写出不加锁、不做原子保护的操作。比如生成一个“获取订单并更新状态”的方法,它可能只会用先查再更新的朴素写法,中间状态完全没考虑。
最典型的一次是我让 AI 帮忙优化一段库存扣减逻辑,它直接在 items 集合上做了 read-modify-write,没有加锁也没有用乐观锁版本号。单位造数测没问题,一百并发一压,超卖立刻出现。后来我在 Review 清单里加了一条铁律:凡是涉及共享状态的修改,必须检查是否具备原子性保障。
2.3 错误处理:看似健壮,实则吃掉了异常
第三类是错误处理。这是我最担心的类型,因为出了这类问题不会立即报错,而是让错误在系统里“潜伏”。AI 生成代码时很喜欢加 try-catch,但如果 catch 块里只是 log 一下然后返回一个默认值,那问题就来了。
一次支付回调里,AI 生成的代码 catch 到了签名验签异常后直接 return success,理由是“避免重试导致重复通知”。从表面看,这个处理让调用方不再重试了,很好的设计。但实际上验签失败意味着数据有可能被篡改,正确动作是要么告警要么返回失败让对账环节介入,而不是“礼貌地成功”。这属于把异常吃掉、把问题藏起来的典型误用。Review 时我遇到这种情况,一律要求改写成抛出业务异常或者至少进入人工处理队列。
2.4 安全与敏感信息:最隐蔽的一类问题
第四类是安全和敏感信息类问题。AI 特别擅长帮你拼 SQL 和拼日志字符串,问题也恰恰出在这里。
有一次 AI 根据历史代码风格生成了一段动态排序查询,用了字符串拼接把排序字段放进去。单看代码很自然,和仓库里老代码风格一致。但排序字段来自前端提交的请求参数,那这就等于给了用户一个 SQL 注入点。这个问题静态扫描工具能识别一部分,但拦不住“和前文代码风格一致”的绕过。还有日志问题,AI 生成 debug 日志时常常直接把整个请求对象或者数据库实体打出来,上面如果带着手机号、身份证、银行卡号,就会直接落进日志系统。这类问题在 Review 里我会要求重点排查:只要是用户输入,必须参数化;凡是日志输出,必须脱敏。
2.5 性能陷阱:时间复杂度在特定数据下崩塌
第五类是性能陷阱。这里有个反直觉的事:AI 生成的排序或者说“最优”算法,在常规数据量下确实高效,但一旦数据分布特殊,性能立刻崩塌。
典型例子是我让 AI 实现一个“查找出现频次最高的 N 个元素”。它给的方案是先排序再计数,时间复杂度 O(n log n)。大部分数据下没问题,但我这次的数据量是千万级,而且要求低延迟,这个方案直接超时。AI 并不是不知道哈希表计数是 O(n),但它在“快写快出”的模式下倾向于选择最简单可读的方案,而不是针对你场景做优化的方案。Review 时必须紧盯算法复杂度与数据量级是否匹配、有没有多余的循环嵌套、数据库查询有没有命中索引、批量操作有没有逐条执行。
这些坑跨语言通用,只要是 AI 辅助开发,不管你在写 Python 写 Java 还是写 Go,都要带着这个敏感度去看 AI 给的每一段代码。
3. 我的 Review 工作流:从“事后检查”变成“提交红线”
发现问题再多,如果流程上不给 Review 让位,它照样会被压缩。这半年我调整了团队的工作流,核心是把 Review 从“提交之后的某个步骤”变成“来代码合入之前不可逾越的红线”。我自己这套流程很简单,分享出来供参考。
3.1 一条铁律:AI 生成代码必须经过二次手写确认
先说最重要的习惯,这一条是从我自己的教训里逼出来的:AI 生成的关键代码块,不允许直接复制粘贴进工程。我会先把代码读一遍,然后在编辑器里重新用自己的逻辑手写结构,哪怕最终写出来和 AI 建议是同一套写法,也必须完成这个“二次手写确认”过程。
为什么这么折腾?因为复制粘贴的过程里你的大脑几乎没有参与。而手写一遍,你会被迫思考每一行变量的作用、每个分支的意义。我后来在团队里观察,凡是跳过这个步骤直接粘贴的,Review 时被揪出问题的概率明显更高。这个习惯看起来繁琐,但它把“被动接受”变成了“主动断言”,是我把 Review 前移到编码阶段的最实际的手段。
3.2 把 Review 拆成三个时刻:提交前、合并前、发布后
我把代码审查拆分成了三个时刻,不再是过去只有“提交后看看”一个环节。
提交前,每人过一遍差异,少则五分钟多则二十分钟,专门看 AI 生成的高危区域;合并前,必须经过一位不在同一个功能模块的同事做独立 Review;发布后还有一层“审查后续效果”的闭环,我会在代码上线后一到三天再翻一次日志和监控,确认没有隐藏异常。
这套流程一开始阻力不小,有人觉得合并前的独立 Review 增加了等待时间,但实际跑了俩月之后,团队自己承认返工减少了,晚上十点被叫醒修 bug 的情况变少了。合并前 Review 等待它消耗几分钟,线上事故消耗的是几小时,这笔账很容易算。
3.3 工具辅助但不能替代:我用 AI 审 AI 的边界在哪
我说的“AI 审 AI”不是让一个模型去判断另一个模型的输出,而是用工具完成那些重复性、确定性的检查,把人的注意力留给真正需要判断力的地方。
我现在的配合方式是:先让静态扫描工具跑一遍安全与规范类问题;再用 AI 辅助生成针对该代码块的测试用例,尤其是边界类、异常类输入;最后我带着明确问题去读代码——这段改动动了什么状态、它依赖什么前置条件、它失败后会怎样。工具负责“有没有”,我负责“对不对”。比如高危区那五类问题,可以在工具扫描漏掉之后靠人工逐项盯住。你如果也想用 AI 辅助 Review,我的建议是让它去生成测试、去查资料、去解释某段 API 文档,不要让它直接给“是否通过”的结论。审查结论永远是人的判断。
4. 一份可以直接抄走的 AI 代码 Review 清单
工具和流程说完了,给你一份我现在打印出来贴显示器旁边的实体清单。分三个层次:通用检查、AI 专项检查、业务验证。
4.1 通用代码审查清单(快速版)
这一层适合所有代码,不管是否 AI 生成。我一般每项过一遍,耗时控制在十分钟内。
| 检查项 | 具体关注点 |
|---|---|
| 变量命名与结构 | 命名是否符合语义,函数是否过长,职责是否单一 |
| 空值与类型边界 | 所有可能为 null 的入口,所有类型转换点 |
| 异常处理 | catch 之后是否正确返还,是否吞异常 |
| 并发与共享状态 | 共享对象是否原子,锁范围是否过大 |
| 可读性与注释 | 注释是否解释“为什么”,而不是复述“是什么” |
| 测试覆盖 | 新增代码是否有对应测试,边界用例是否包含 |
4.2 针对 AI 生成代码的专项审查项
第二层才是重点,专门用来对抗 AI 代码“表面完美”的错觉。
- 入参来源追溯:这个变量从哪来?是用户输入、数据库读取还是第三方接口?如果是外部输入,有没有经过校验?
- 失败路径演练:如果依赖服务超时、数据库不可用、消息队列积压,这段代码会表现为什么?
- 业务状态一致性:这次修改是否改变了某个业务状态流?旧状态、新状态、中间状态是否都有定义?
- 重复代码的血缘:AI 经常复刻仓库老代码,Check 一下它是不是把一段已知有 bug 的老逻辑也照搬过来了。
- 算法与数据量匹配:当前数据量级、调用频次、响应时间要求,和 AI 选择的算法是否匹配。
这一层没有公式可以套,因为每段代码的业务上下文都不同。但携带这五个问题去 Review,可以把注意力精准带回到模型不知道的业务维度。
4.3 Review 输出的格式:让问题回到“人话”
我要求自己在 Review 里留下的每条评论都带上“上下文 + 具体问题 + 期望改进”,而不是一句冷冰冰的“这里有问题”。两者差别很大:前者是协作,后者是审判。
我会尽量把问题转述成能被快速理解的句式,比如:“这个字段在用户重复点击时会先走缓存逻辑,但目前没有兜底;如果缓存故障,建议加降级开关。” 这样的输出让作者不用反复猜上下文,改起来更快,也减少无谓的线上争论。同时我会给每条评论标注优先级:P0 必须修复,P1 应该修复,P2 建议优化。P0 不过绝不合并,这是底线。
5. 三个差点漏掉的生产事故复盘
光有清单还是不够,我再复盘三个真实遇到过的坑,帮你看清“AI 代码为什么会看起来无害却暗藏杀机”。
5.1 事故一:AI 写了正确的分页,却烧掉了数据库连接
这个问题的起因是让我重构一个报表查询接口。原始代码取 10 万条全量数据到内存做过滤,性能很烂。我让 AI 给了个改进方案,它立刻写出一段标准的分页查询,看起来无懈可击——每页 100 条,循环查询数据库,直到取完。
问题出在循环判断条件:它用 while (hasNextPage) 这种写法,只要某页数据大于 0 就继续查,完全没考虑数据库在查询期间数据变化。报表系统数据在持续写入,这个循环往小了说多查几页,往大了说变成了无限循环。上线当晚数据库连接数被打满,监控直接告警。后来加了一个最大页数限制和快照时间戳作为分页边界,问题才根治。
这里教训不是“分页循环写错了”,而是 AI 对“什么条件是稳定不变”没有感知。它假设数据库是静态的,但实际不是。所有带循环、带多次查询的 AI 代码,我都会先问一句话:这个循环的终止条件在真实环境下真的可靠吗?
5.2 事故二:排序“看起来对”,数据分布一变就崩
第二个案例是我用 AI 实现一个商品列表的“按销量优先+按最新上架”排序需求。它给出的是先按销量排序再按上架时间排序的稳定排序实现,测试环境数据量小,一切正常。
但真实数据里存在大量销量为零的商品,首屏只要少数几个有销量的商品排在前面,后面整片都是零销量商品,此时按上架时间排序的期望就被完全打乱了。用户刷了几十屏,看到的顺序和预期不符,反馈大量进来。最终修复是让排序规则先区分销量是否大于零,再在零销量分区内做上架时间排序。从代码结构看,这只是一条 if 和一条 else 的差距,但从业务感知看,体验是完全不同的两套结果。这类问题,AI 不可能从你的需求描述里推断出来,因为“零销量”这个分组的业务权重没有写进需求。Review 的意义就在这里:从业务结果倒推代码逻辑,而不是从代码逻辑判断业务结果。
5.3 事故三:日志没报错,但泄露了用户隐私
第三个是最让我后怕的。AI 在生成一个用户详情接口时,顺手在 debug 级别日志里打印了整个实体对象。实体对象里包含用户的手机号、详细地址、近三十天的浏览记录。代码没问题、运行没报错、毫无异常迹象。
直到安全团队例行检查日志系统,发现日志文本里大量完整手机号,才追查到这个来源。还好是 debug 级别,生产环境日志级别较高没有实际输出,否则这会是一起严重的数据安全事件。从这之后,我 Review 里关于日志的处理统一走一个原则:日志中不允许完整打印实体对象,不允许直接拼接展示字段,统一走脱敏工具类。这其实是极小的一个改动,但对用户隐私保护来说是一道实打实的防线。
这三个案例各有侧重:第一个是循环终止条件,第二个是业务分组的隐式规则,第三个是敏感信息外泄。放到一起看,你会发现它们的共同点是“代码能跑、测试能过、单看无误”——只有站在业务端、站在数据端、站在合规端去审视,漏洞才会显形。
6. 最后一点个人体会
如果你也是 AI 辅助开发的重度用户,我最后的建议就是:让 Review 成为你提交代码前主动执行的检查,而不是别人催你才做的流程。我现在的习惯是,每次 AI 给出一段逻辑后,默认先质疑它三个点:这段代码对业务状态的影响是什么,它在失败时的表现是什么,它是否引入了新的依赖或者新的输入源。想清楚这三个点,我才允许自己把它写进工程里。
Review 这件事,说到底是把人的注意力放回代码真正面对的现实世界里去。AI 让写代码飞起来,我们更要把审查的时间保护起来。不需要什么复杂机制,就是从今天开始,在每个 PR 保持 pending review 状态的时间里,认真读一遍那些由工具生成的漂亮代码,然后问自己上面那三个问题。你会发现,那种踏实感值得每一次停留。
