
接手 Spring Boot 老项目时,真正麻烦的往往不是编译报错,而是分散在业务代码中的空指针、N+1 查询、异常吞没和并发隐患。本文记录一次使用 Claude Code 辅助代码 Review 的完整流程,并给出提示词、Java 示例、修改方案和人工复核方法。
项目能运行,不代表代码没有问题
前段时间接手一个维护多年的 Spring Boot 项目,编译正常,接口也能访问,但团队里几乎没人敢轻易修改核心模块。
原因并不复杂。这个项目经历过多轮需求迭代,同一个业务规则散落在 Controller、Service、定时任务和消息消费者中。修改一个判断条件,可能影响另一个几个月没人碰过的流程。
更麻烦的是,项目里的大部分问题不会在编译阶段暴露。空指针只有遇到特殊数据才会出现,N+1 查询要等数据量增加后才会变慢,并发问题甚至可能只在线上高峰期偶发。
我决定先用 Claude Code 做一轮辅助代码 Review,但没有让它直接修改项目,而是要求它完成三件事:
- 理清项目结构和核心调用关系;
- 找出有明确代码证据的风险;
- 按严重程度生成待人工确认的问题清单。
这次排查最终找出了四类值得处理的问题。

不要直接输入“帮我检查整个项目”
刚开始使用 AI 做代码审查时,很多人会输入一句:
帮我检查一下这个项目有哪些问题。
这样的任务范围太大,输出结果通常会变成一组正确但没有执行价值的建议:
建议完善异常处理
建议增加单元测试
建议优化数据库查询
建议提高代码可读性
这些建议放在任何 Java 项目中都成立,却不能直接告诉开发者应该修改哪个文件、什么情况下会出错。
我后来把任务拆成了两轮。
第一轮:只理解项目,不修改代码
请读取当前 Spring Boot 项目的目录结构、pom.xml 和主要配置文件。
需要输出:
1. 项目使用的主要技术栈;
2. Controller、Service、Mapper、定时任务和消息消费者的分布;
3. 核心业务模块及依赖关系;
4. 可能涉及数据库写入、事务和外部接口调用的模块。
暂时不要修改任何文件,也不要输出通用优化建议。
这一步的目的不是发现 Bug,而是检查 AI 有没有理解项目。
如果它连核心模块、技术栈和调用关系都判断错了,后面的代码审查结果也很难可信。
第二轮:按风险类型逐项扫描
请对当前项目进行第一轮代码风险审查,暂时不要修改代码。
重点检查:
1. 可能出现空指针异常的位置;
2. 循环内执行数据库查询的代码;
3. 捕获异常后没有记录、没有转换、没有继续抛出的代码;
4. 单例对象中共享非线程安全变量的代码;
5. 文件流、数据库连接或线程池未正确关闭的问题。
每个问题必须包含:
- 严重级别
- 文件路径
- 类名和方法名
- 相关代码
- 触发条件
- 可能影响
- 修改建议
无法确认的问题请标记为“需要人工验证”,不要直接下结论。
任务被限制后,AI 输出的内容会更像一份可以执行的代码审查报告,而不是泛泛而谈的项目建议。
问题一:测试数据正常,历史数据却可能触发空指针
项目中有一个订单详情查询方法:
public OrderVO getOrderDetail(Long orderId) {
Order order = orderMapper.selectById(orderId);
OrderVO vo = new OrderVO();
vo.setId(order.getId());
vo.setUserName(order.getUser().getName());
return vo;
}
这段代码在测试环境里一直没有报错,因为测试订单都有对应的用户数据。
但生产环境中存在部分历史订单。用户信息已经被清理,订单记录仍然保留。这时 order.getUser() 可能返回 null。
此外,代码还存在另外两个风险:
- 调用方可能传入空的
orderId; - 数据库中可能不存在对应订单。
可以先把参数和查询结果处理清楚:
public OrderVO getOrderDetail(Long orderId) {
if (orderId == null) {
throw new IllegalArgumentException("orderId must not be null");
}
Order order = orderMapper.selectById(orderId);
if (order == null) {
throw new OrderNotFoundException(
"Order does not exist, orderId=" + orderId
);
}
OrderVO vo = new OrderVO();
vo.setId(order.getId());
User user = order.getUser();
vo.setUserName(user == null ? "未知用户" : user.getName());
return vo;
}
这段修改只能作为结构示例,不能不看业务规则直接复制。
当关联用户不存在时,有些系统允许订单继续展示,只把用户名标记为“未知用户”;有些系统则需要把订单识别为异常数据,并通知运营人员修复。
AI 可以发现空指针风险,却无法只凭一个方法确定最终业务语义。这类问题必须由熟悉订单规则的人确认。
问题二:列表接口里隐藏着 N+1 查询
订单列表接口中还有一段常见写法:
List<Order> orders = orderMapper.selectByCondition(condition);
List<OrderVO> result = new ArrayList<>();
for (Order order : orders) {
User user = userMapper.selectById(order.getUserId());
OrderVO vo = new OrderVO();
vo.setOrderId(order.getId());
vo.setUserName(user == null ? ""
1724





被折叠的 条评论
为什么被折叠?



