用 Claude Code 做代码 Review——和人工 Review 相比,它能发现什么
用 Claude Code 做代码 Review——和人工 Review 相比,它能发现什么
适读人群:做过代码 Review 的工程师,想知道 AI Review 能不能替代人工 | 阅读时长:约15分钟 | 核心价值:用真实有问题的代码对比人工和 AI Review 的结果,说清楚 AI Review 适合什么、不适合什么
Code Review 这件事我做了大概 6 年了,从被人 review,到 review 别人,再到搭 review 流程。我对这件事有自己的判断。
去年有段时间,我开始系统地用 Claude Code 做代码 Review,不是随手问一下,是认真地对比 AI Review 和人工 Review 的差异。我做了几十次对比测试,得出了一些结论。
结论不是"AI Review 可以替代人工 Review",也不是"AI Review 没用"。是一个更具体的回答:在哪些维度上 AI 比人强,在哪些维度上人比 AI 强,以及最合理的用法是什么。
测试用的代码
我用了一段真实项目里的代码做对比。这是一个用户积分系统里的方法,在用户完成某个行为之后,给他加积分:
@Service
public class PointsService {
@Autowired
private PointsMapper pointsMapper;
@Autowired
private UserMapper userMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 给用户加积分
public void addPoints(Long userId, int points, String reason) {
// 查询用户是否存在
User user = userMapper.selectById(userId);
if (user.getStatus() == 0) {
return;
}
// 检查今天是否已经加过这个reason的积分
String redisKey = "points:daily:" + userId + ":" + reason;
Object existing = redisTemplate.opsForValue().get(redisKey);
if (existing != null) {
return;
}
// 加积分
PointsRecord record = new PointsRecord();
record.setUserId(userId);
record.setPoints(points);
record.setReason(reason);
record.setCreateTime(new Date());
pointsMapper.insert(record);
// 更新用户总积分
userMapper.updatePoints(userId, points);
// 标记今天已经加过
redisTemplate.opsForValue().set(redisKey, "1");
// 发积分变更通知
sendPointsNotification(userId, points);
}
private void sendPointsNotification(Long userId, int points) {
// TODO: 发消息队列通知
System.out.println("用户 " + userId + " 获得 " + points + " 积分");
}
}这段代码我让三个同事分别做了 review(都是 5+ 年经验的 Java 工程师),然后也让 Claude Code 做了 review。
人工 Review 的结果
三个同事的 review 意见汇总之后,覆盖了这些问题:
同事 A(后端老手,做过高并发系统):
- 并发问题:Redis 的"检查-写入"不是原子操作,在并发请求下可能失效,应该用
setIfAbsent - 缺少事务:数据库插入和更新没有事务保护,如果
updatePoints失败,insert已经执行了,数据不一致 user.getStatus() == 0这个判断如果user为 null 会 NPE
同事 B(关注代码可读性):
status == 0是魔法数字,不知道 0 代表什么,应该用枚举或常量- 方法名
addPoints没有体现"每日限制"的逻辑,调用方看方法名不知道有这个约束 TODO注释里是实际的业务逻辑(发通知),这是技术债,应该明确标注 JIRA 或时间
同事 C(做过积分和优惠系统):
- 积分数量没有校验:
points参数如果传负数或者 0 怎么办? reason参数没有校验,空字符串或者 null 会导致 Redis Key 异常- Redis Key 的 TTL 没有设置,理论上
points:daily:xxx:xxx这个 key 会永久存在
Claude Code 的 Review 结果
我把同样的代码丢给 Claude Code,让它系统性地 review。结果如下(节选核心部分):
正确性问题(标注为严重):
[严重] NullPointerException 风险
user.getStatus() == 0在user为 null 时会抛出 NPE。selectById如果查不到用户会返回 null。修复:
if (user == null || user.getStatus() == 0) { return; }
[严重] 并发安全问题:Redis 检查-写入不是原子操作
get和set之间有窗口,高并发下两个请求可能同时通过existing != null的判断,导致同一天同一 reason 积分被加两次。修复:使用
setIfAbsent替代 get + set:Boolean isNewKey = redisTemplate.opsForValue().setIfAbsent(redisKey, "1", 1, TimeUnit.DAYS); if (Boolean.FALSE.equals(isNewKey)) { return; // 今天已经加过了 }注意:这里同时解决了 TTL 未设置的问题。
[严重] 数据一致性:缺少 @Transactional
pointsMapper.insert和userMapper.updatePoints没有在同一个事务里。如果updatePoints执行失败,insert 已经成功,积分记录和用户总积分会不一致。修复:在方法上加
@Transactional(rollbackFor = Exception.class)
性能和设计问题(标注为建议):
[建议] Redis Key 未设置 TTL 如果不依赖上面
setIfAbsent的修复,原代码中redisTemplate.opsForValue().set(redisKey, "1")没有设置过期时间,key 会永久存在,随着时间累积会消耗大量 Redis 内存。修复:设置 TTL 到当天结束:
long secondsUntilMidnight = ...; // 计算到第二天 00:00 的秒数 redisTemplate.opsForValue().set(redisKey, "1", secondsUntilMidnight, TimeUnit.SECONDS);
可维护性问题:
[建议] 参数未校验
points参数没有非负校验,reason参数没有非空校验。建议在方法入口加参数校验:if (points <= 0) { throw new IllegalArgumentException("积分数量必须大于0"); } if (userId == null || reason == null || reason.isBlank()) { throw new IllegalArgumentException("userId 和 reason 不能为空"); }
[建议] 魔法数字
user.getStatus() == 0中的 0 含义不明确,应该定义为常量或枚举。
[建议]
sendPointsNotification方法用 System.out.println 代替了实际的消息队列实现 如果这是临时的,建议用log.warn()标注,并且在注释里说明预计完成时间。
对比分析:AI 和人工各自发现了什么
把两个结果放在一起对比,很有意思:
AI 和人工都发现的问题:
- NPE 风险(user 为 null)
- Redis 并发问题
- 缺少 @Transactional
- Redis Key 无 TTL
- 魔法数字
status == 0 - 参数未校验
只有人工发现的问题:
- 方法名没有体现每日限制的约束(这是一个"命名和业务语义"的问题)
- TODO 注释代表真实的技术债,需要明确跟踪(同事 B 知道这类 TODO 在项目里的处理惯例)
只有 AI 发现的问题:
- 给出了具体的修复代码示例(人工 review 通常只说"应该怎样",不给代码)
setIfAbsent的修复方案同时解决了并发问题和 TTL 问题(这个一石二鸟的方案,三个同事分别指出了两个问题,但没有人给出同时解决两个问题的方案)
AI Review 的真实能力边界
经过几十次对比测试,我对 AI Review 的能力边界有了比较清晰的判断。
AI 擅长的:
技术性的、有固定模式的问题。NPE、并发竞态、事务缺失、SQL 注入风险——这类问题有规律可循,AI 检查的覆盖率很高,而且稳定,不会因为今天累了就漏掉。
给出代码级的修复建议。人工 reviewer 通常说"这里有问题,你去改一下",AI 会直接给你改好的代码。
检查覆盖率:AI 会系统性地过一遍所有维度,不会因为注意力分散或者时间紧就跳过某个检查项。
AI 不擅长的:
业务语义的判断。"这个方法名是否准确反映了业务意图"这类问题,需要了解业务上下文,AI 没有这个上下文就给不出好的判断。
团队代码风格的一致性。我们团队有一些约定俗成的规范,是没有写在文档里的,AI 不知道。
技术债的优先级判断。"这个 TODO 是不是真的要跟踪",AI 不知道当前项目阶段和资源情况,它只能从代码本身判断,有时候会过度报告技术债。
我的实际使用方式
现在我的 review 流程是:
在人工 review 之前,先跑一次 AI review。AI review 的输出作为"背景材料"给人工 reviewer 看。
这样做有几个好处:
人工 reviewer 不需要再花时间找那些"有固定模式"的技术问题,那些 AI 已经覆盖了。人工 reviewer 可以把精力放在 AI 不擅长的地方:业务语义、架构决策、团队规范。
对于提交代码的人:AI review 相当于一个零成本的"pre-review",在给人看之前先自查一遍。
这个流程让我们的 code review 时间缩短了,而且 review 的质量没有下降,某些维度上反而提高了(特别是技术安全类的问题,现在几乎不会漏)。
一个实际的使用模板
如果你想把 Claude Code 用于 code review,我建议用这个提示词模板,比直接说"review 一下"效果好很多:
请对以下代码做系统性 review,按这四个维度分析:
1. 正确性(NPE、边界条件、并发安全、事务)
2. 性能(N+1 查询、不必要的循环、内存使用)
3. 可维护性(命名、职责、重复代码)
4. 安全性(参数校验、权限、SQL 安全、日志安全)
每个问题标注 [严重] 或 [建议],严重问题给出修复代码示例。
代码:
[粘贴代码]这个模板给 Claude 一个结构化的输出框架,结果会比随机 review 质量稳定很多。
AI Review 不是来抢 reviewer 工作的,是来做那些消耗注意力、但又有规律可循的检查工作的。把这部分交给 AI,把注意力留给那些真正需要人来判断的地方,这才是合理的用法。
