乐于分享
好东西不私藏

AI生成的代码不能直接用:5个必须人工检查的点

AI生成的代码不能直接用:5个必须人工检查的点

我用 AI 写代码半年了。

说句实话,效率确实提上来了。以前一个工作日才能写完的接口,现在一上午能搞定三个。但有个坑我踩过不止一次:AI 生成的代码,看着特别完美,命名规范、结构清晰、编译通过、单测也绿,可一旦直接上线,就出事。

不是编译错误这种低级问题,是那种"代码是对的,但用起来是错的"问题。

后来我形成了一个习惯——AI 写完,我自己再过一遍。过什么?就是下面这五个点。这五个点是我踩了半年坑总结出来的,每一个都对应一个真实的事故。

检查点一:边界条件

这是 AI 最容易翻车的地方,也是最容易让人放松警惕的地方。

AI生成代码人工检查要点 (1)

AI 写代码有一个特点:它默认输入都是"正常"的。你让它写一个分页查询,它会给你写得很漂亮,limit、offset、排序都考虑到了。但你仔细一看,它没处理 pageNo 为 0 的情况,没处理负数的情况,也没处理 pageSize 传 1000 的情况。

我有一次让 AI 写一个用户列表的分页接口,它写出来是这样:offset = (pageNo - 1) * pageSize。逻辑没问题。但如果用户传 pageNo = 0,offset 就变成负数了,到数据库里直接报错。如果传 pageNo = -1,offset 变成更小的负数,有些数据库不报错,反而返回了从后往前的数据——这就出大事了,等于暴露了不该暴露的数据。

类似的情况还有:

  • 集合操作前不判空,直接 .size() 或遍历
  • 字符串截取不判断长度,substring(0, 10) 碰到短字符串直接越界
  • 数值计算不考虑溢出,两个 int 相乘结果还是 int
  • 时间计算不考虑跨天、跨月、跨年

我现在审 AI 代码的第一件事,就是问自己:这个方法的入参,如果是 null、空、0、负数、极大值、极小值,会发生什么? 如果答案里有任何一个会崩,那就得补上。

检查点二:安全性

这一点我放到第二位说,但优先级其实是最高的。因为安全漏洞造成的后果最严重——数据泄露、被拖库、被注入,那都是能上新闻的事。

AI 生成不安全代码,往往不是因为不懂安全,而是因为它在模仿"看起来正常"的写法时,把不安全的模式也学进去了。

最典型的就是 SQL 拼接。我让 AI 写过一个商品搜索接口,需求是"按商品名称模糊查询"。它给我写的是:

// ❌ AI 生成的错误代码——SQL 注入风险
String sql = "SELECT * FROM goods WHERE name LIKE '%" + keyword + "%'";
jdbcTemplate.query(sql, ...);

字符串拼接,看着没毛病。但这就是教科书级别的 SQL 注入。用户在搜索框输入 '; DROP TABLE goods; --,这个语句就完了。

正确的写法应该是:

// ✅ 使用参数化查询
String sql = "SELECT * FROM goods WHERE name LIKE CONCAT('%', ?, '%')";
jdbcTemplate.query(sql, new Object[]{keyword}, ...);

如果是 MyBatis,AI 偶尔会写出 ${} 而不是 #{}

<!-- ❌ 危险写法——直接拼接,可注入 -->
<selectid="searchGoods"resultType="Goods">
    SELECT * FROM goods WHERE name LIKE '%${keyword}%'
</select>

<!-- ✅ 安全写法——参数化绑定 -->
<selectid="searchGoods"resultType="Goods">
    SELECT * FROM goods WHERE name LIKE CONCAT('%', #{keyword}, '%')
</select>

除了 SQL 注入,还有几个要警惕的:

  • XSS:AI 把用户输入直接返回到前端,没有转义
  • 越权:AI 写的接口没校验"这个数据是不是当前用户的",导致横向越权
  • 敏感信息泄露:AI 在日志里打印了完整请求体,里面有密码、身份证号
  • 反序列化漏洞:AI 用了不安全的序列化方式接收外部数据

越权这个特别要小心。你让 AI 写"修改订单状态"的接口,它会写得很顺,但你细看,它没校验这个订单是不是当前登录用户的。任何一个用户传一个 orderId 就能改别人的订单——这是真实发生过的线上事故。

检查点三:事务和并发

这一块是 AI 的重灾区,也是我踩过最深的坑。

先说事务。AI 写代码的时候,它不太能感知到"这一段操作必须要么全成功要么全失败"。你让它写一个"下单扣库存 + 生成订单 + 扣余额"的逻辑,它可能给你拆成三个独立的方法调用,每个方法自己存自己的。一旦中间某一步挂了,前面的已经落库了,后面的没执行——数据就不一致了。

更隐蔽的是事务范围。AI 会加 @Transactional,但它加的位置经常不对。比如它把注解加在一个内部私有方法上,结果 Spring 的代理根本不生效,事务等于没开。或者它把 @Transactional 加在了一个方法上,但方法里有一段远程调用——事务持有数据库连接的时间被拉长了好几倍,连接池很快就爆了。

@Transactional 使用注意事项:

  • 必须加在 public 方法上,Spring AOP 代理只拦截 public 方法
  • 避免在 @Transactional 方法内做远程调用(RPC、HTTP),会长时间占用数据库连接
  • 注意自调用问题:同类内 this.methodB() 不会触发事务代理,要用 AopContext.currentProxy()
  • 回滚默认只捕获 RuntimeException 和 Error,检查型异常需要显式指定 rollbackFor = Exception.class
  • 事务隔离级别默认 READ_COMMITTED,高并发场景下配合锁或唯一索引兜底

再说并发。AI 写的代码,默认是单线程思维。它不会主动想到"这段代码如果两个人同时点会怎样"。

我踩过一个真实的坑:AI 写了一个"用户签到领积分"的逻辑。流程是:查今天有没有签到 → 没有则记录签到 → 加积分。单线程下完美。但用户疯狂连点签到按钮,两个请求几乎同时进来,都查到"没签到",都加了积分。一天领了二十多次积分。

后来我学会了一个问 AI 的问题:**"这段代码,如果两个请求同时进来,会发生什么?"** AI 大概率会回答你"会有并发问题",但你得主动问,它不会主动告诉你。

检查点四:性能

AI 写的代码,能跑,但经常跑得慢。原因很简单——AI 优化的目标是"正确",不是"高效"。

最常见的就是 N+1 查询。AI 写列表接口的时候,习惯一个循环一个循环地查。比如"查询订单列表并展示商品名称",它会在 for 循环里根据 goodsId 去查商品。10 个订单还好,1000 个订单就是 1000 次数据库查询,接口直接超时。

// ❌ N+1 查询:循环内逐条查数据库
List<Order> orders = orderMapper.selectAll();
for (Order order : orders) {
    Goods goods = goodsMapper.selectById(order.getGoodsId()); // 每次循环一次DB查询
    order.setGoodsName(goods.getName());
}

// ✅ 批量查询:先收集ID,一次性查
List<Order> orders = orderMapper.selectAll();
List<Long> goodsIds = orders.stream()
    .map(Order::getGoodsId).collect(Collectors.toList());
Map<Long, Goods> goodsMap = goodsMapper.selectByIds(goodsIds).stream()
    .collect(Collectors.toMap(Goods::getId, Function.identity()));
for (Order order : orders) {
    Goods goods = goodsMap.get(order.getGoodsId());
    order.setGoodsName(goods != null ? goods.getName() : "未知");
}

还有内存分配。AI 喜欢 new 各种中间集合,转换、过滤、再转换,一道流程下来中间对象能创建好几层。数据量小的时候没事,一旦碰到批量导入导出,GC 就开始抽风。

不合理的循环嵌套也是重灾区。AI 写一个"判断两个列表有没有交集",能给你写成两层 for。其实一行 Set 的 retainAll 就搞定了,但 AI 不会这么想——它的思路是"把问题解决",不是"把问题优雅地解决"。

我审 AI 代码的第四件事,就是问:这段代码,数据量放大到一万倍,还能跑吗? 如果答案是不能,那就得改。

检查点五:业务逻辑正确性

这一点最难查,也最容易翻车。因为前面四个都是"技术问题",这一个不是——它是"理解问题"。

AI 不懂业务。你给它一段需求,它按字面意思去实现,但字面意思往往不是业务真正的意思。

举个真实的例子。我们有个需求:"订单取消后恢复库存"。我把这个需求丢给 AI,它写得很快:取消订单 → 把订单关联的库存数量加回去。功能是对的,逻辑也没错。

但业务上的"订单取消后恢复库存",远不止是"把数量加回去"。还有:

  • 恢复的库存要进入哪个批次?是原来的批次,还是新批次?
  • 如果原来的批次已经过期了怎么办?
  • 库存恢复了,要不要同步给仓储系统?
  • 已经发优惠券的订单取消,券要不要回收?
  • 取消的订单要不要留痕?是物理删除还是逻辑删除?

AI 只做了第一步——把数量加回去。后面那些它根本不知道存在。如果我当时直接用了,库存数字是对的,但仓储系统对不上、券乱飞、订单也找不回来了。

这种坑没有通用的检查方法,只有一个笨办法:AI 写完,你拿着业务规则一条一条对。对不上的,补上。AI 不知道的,你告诉它,让它改。但前提是你自己得先把业务想清楚。

检查的优先级

五个检查点都说完了,但实际工作中你不可能每段代码都逐一细抠。我自己的做法是按优先级来:

五个检查点优先级速查:

优先级
检查点
典型风险
后果严重程度
发现难度
🔴 P0
安全性
SQL注入、XSS、越权、敏感信息泄露
全站数据泄露/资损
🔴 P1
业务逻辑
状态机错误、业务规则遗漏、副作用未处理
资损/客诉
🟡 P2
边界条件
null/空/负数/极大值未处理
接口报错,影响面有限
🟡 P3
事务并发
数据不一致、重复操作、死锁
偶发问题,排查困难
🟢 P4
性能
N+1查询、内存浪费、O(n²)
慢,但一般不致命

所以时间紧的时候,我先扫安全性,再扫业务逻辑,剩下三个有时间再细看。当然,如果是涉及资金、订单、库存这类核心链路,五个都得看,一个都不能省。

一个真实案例

讲一个我亲历的事故,方便大家理解为什么这事不能糊弄。

去年我们做一个积分系统,有个"用户每日签到"功能。我让 AI 写了实现:查签到记录 → 没签到就插入一条 → 给用户加积分。AI 写得很快,单测也写了,mock 了数据库,跑全绿。代码 review 的时候我也看了,逻辑没问题,事务也加了 @Transactional,看着很稳。

上线头两天风平浪静。第三天运营来找我:有个用户一天领了八次签到积分。

排查下来,是这个用户开了个脚本,在零点零分零秒的瞬间发了八个请求。这八个请求几乎同时到达,同时查询签到记录——都查到"今天没签到"——于是都往下走,都加了积分。

@Transactional 的默认隔离级别是 READ_COMMITTED,根本挡不住这种并发。真正的解法要么是数据库唯一索引(一个用户一天只能有一条签到记录),要么是分布式锁,要么是 SELECT ... FOR UPDATE。但这些 AI 一个都没加,因为它压根没意识到这是个并发问题。

最后我们改了三个地方:加了唯一索引兜底,签到逻辑改成"先插后查",前端加了防重。线上才算稳了。这个事故让我彻底明白一件事:AI 写的代码能过编译,能过单测,但不一定能扛住真实流量。

AI 代码审查清单

最后分享一份我自己在用的审查清单,每次 AI 写完代码,我照着过一遍。不用每条都死磕,但至少心里要有数。

一、边界条件

  • [ ] 入参为 null / 空集合 / 空字符串时,会不会崩?
  • [ ] 数值为 0、负数、极大值时,逻辑还成立吗?
  • [ ] 字符串截取、集合访问有没有越界风险?
  • [ ] 时间计算有没有考虑跨天、跨月、时区?

二、安全性

  • [ ] 所有 SQL 都用了参数化,没有字符串拼接?
  • [ ] 用户输入回显到页面有没有转义?
  • [ ] 涉及资源操作的接口,有没有校验"这是不是你的"?
  • [ ] 日志里有没有打印密码、身份证、手机号这些敏感信息?
  • [ ] 文件上传有没有校验类型、大小、路径?

三、事务和并发

  • [ ] 多步写操作有没有包在事务里?
  • [ ] @Transactional 加的位置对不对?是不是 public 方法?有没有自调用?
  • [ ] 事务里有没有远程调用、慢操作?
  • [ ] 同一资源被并发访问时,有没有锁或唯一约束兜底?
  • [ ] 缓存更新和数据库更新,顺序对不对?

四、性能

  • [ ] 有没有循环里查数据库?
  • [ ] 列表查询有没有用批量?有没有 N+1?
  • [ ] 中间集合创建得太多了吗?能不能复用?
  • [ ] 数据量大的时候,会不会 OOM?会不会超时?
  • [ ] 有没有该加索引却没加的查询条件?

五、业务逻辑

  • [ ] 这段代码实现的功能,是不是需求真正想要的?
  • [ ] 异常场景下,业务状态有没有正确回滚或保留?
  • [ ] 涉及状态变更的,前置状态校验做了吗?
  • [ ] 有没有副作用没处理?比如取消订单后券、库存、通知怎么办?
  • [ ] 业务规则里那些"潜规则",AI 知道吗?

写在最后

用 AI 写代码这半年,我最大的体会是:AI 不是替你写代码的,是替你打草稿的。

草稿写完,你还是得审。审什么?就是上面这五件事。这五件事看起来都是常识,但 AI 偏偏就在常识上摔跟头——因为它只会"模仿写法",不会"理解场景"。

我不是劝大家不用 AI,恰恰相反,我现在离不开 AI 了。但用得顺手的前提,是你得知道它会在哪里出错,然后在那些地方多看一眼。

多看这一眼,可能就是线上事故和你今晚能正常下班的区别。