# 第二篇:Claude Code 在 Spring 项目中的实战工作流

0 阅读9分钟

上一篇讲完了原理,这篇直接说怎么用。

很多人拿到 Claude Code 之后,第一反应是"帮我重构一下 OrderService",然后发现效果不理想——要么改了不该改的地方,要么方案太粗糙根本没法落地。问题不在工具,在于驱动方式。这篇把一套经过验证的工作流梳理一遍,从准备工作到收尾验证,每个环节都说清楚。


一、重构前的准备工作

1.1 永远先做:让 Claude Code 生成项目地图

在让 Claude Code 动任何一行代码之前,先让它"读懂"项目。这一步非常关键,它决定了后续所有操作的质量。

指令:

请分析 src/main/java 下的所有文件,生成一份项目架构报告,包括:
1. 各模块的类数量和代码行数统计
2. 模块间的依赖关系图(用文字描述)
3. 识别出违反分层架构的地方
4. 找出代码最复杂的前5个类(可以用行数或圈复杂度估算)

这个报告将成为你和 Claude Code 协作的"共同语言"。

1.2 建立安全基线:确保测试可运行

重构的黄金法则:没有测试就没有重构。在开始之前:

请运行 mvn test 并告诉我:
1. 当前测试通过率是多少
2. 哪些模块没有单元测试
3. 如果测试覆盖率工具可用(JaCoCo),报告核心 Service 层的覆盖率

如果发现关键代码缺少测试,可以先让 Claude Code 补充测试:

OrderService 中没有单元测试,在开始重构之前,
请为现有的 createOrder、cancelOrder、queryOrderById 
三个核心方法补充单元测试。使用 JUnit 5 + Mockito,
测试文件放在 src/test/java/com/example/order/service/

二、标准重构工作流:五步法

第一步:明确重构目标(Define)

不要用模糊的指令,要给出具体、可验证的目标:

❌ 模糊的指令(效果差):

帮我把 OrderService 重构一下

✅ 具体的指令(效果好):

OrderService.java 目前有 3120 行,需要按职责拆分。
以下是我的拆分设想,请评估可行性并提出改进建议:

1. OrderCreationService - 负责创建订单、校验库存、计算价格
2. OrderQueryService - 负责所有查询操作(只读,readOnly=true)
3. OrderStateService - 负责订单状态流转(已下单→已支付→已发货→已完成)
4. OrderRefundService - 负责退款和售后

原有的 OrderService 保留为门面类(Facade),将调用转发到上述4个类。
这样改动对外 API 不变,OrderController 无需修改。

请问这个方案有什么潜在问题?

第二步:制定变更计划(Plan)

在 Claude Code 动手之前,让它先输出详细计划:

基于上面的拆分方案,请列出完整的变更计划:
- 需要新建哪些文件(列出完整路径和类名)
- 需要修改哪些现有文件
- 方法迁移的映射表(哪个方法移到哪个新类)
- 可能的风险点
- 建议的执行顺序

暂时不要开始执行,等我确认计划后再动手。

示例输出计划:

变更计划如下:

【新建文件】
1. src/main/java/com/example/order/service/OrderCreationService.java
   迁入方法:createOrder(), validateInventory(), calculatePrice(),
            applyDiscount(), generateOrderNo()
   
2. src/main/java/com/example/order/service/OrderQueryService.java  
   迁入方法:findById(), listByUserId(), listByStatus(),
            countByDateRange(), exportOrders()

3. src/main/java/com/example/order/service/OrderStateService.java
   迁入方法:pay(), ship(), complete(), cancel(), 
            getStateTransitionHistory()

4. src/main/java/com/example/order/service/OrderRefundService.java
   迁入方法:applyRefund(), approveRefund(), rejectRefund(),
            processRefundPayment()

【修改文件】
5. src/main/java/com/example/order/service/OrderService.java
   改为门面类:注入上述4个 Service,方法实现改为委托调用
   
【可能的风险】
- OrderCreationService 和 OrderStateService 都需要发送消息队列通知,
  需要确认 RocketMQTemplate 的注入不会产生循环依赖
- OrderRefundService.processRefundPayment() 调用了 PaymentService,
  需要确认这是 OK 的跨模块依赖

【执行顺序建议】
第1步:创建4个新 Service 类(先只放方法签名,不迁移逻辑)
第2步:迁移 OrderQueryService(最安全,无副作用)
第3步:迁移 OrderCreationService
第4步:迁移 OrderStateService
第5步:迁移 OrderRefundService
第6步:修改 OrderService 为门面类
第7步:运行测试验证

是否确认执行?

第三步:逐步执行(Execute)

确认计划后,分步骤执行,不要让 Claude Code 一次性做所有改动:

计划确认,请开始执行第1步和第2步:
创建4个新 Service 类的骨架(只有类定义、注解和方法签名,方法体先留空),
然后迁移 OrderQueryService 的方法实现。

完成后停下来,让我检查再继续。

这种"小步快跑"的方式让你随时掌握变更,遇到问题可以快速回滚。

第四步:验证变更(Verify)

每步完成后立即验证:

OrderQueryService 迁移完成了,请执行以下验证:
1. 运行 mvn compile 确认编译通过
2. 运行 OrderQueryServiceTest(如果存在)
3. 在 OrderService 的门面方法中,将 queryOrderById 的实现改为调用 
   orderQueryService.findById(),然后重新编译

第五步:收尾清理(Cleanup)

重构完成后:

重构完成,请做最后的收尾工作:
1. 删除 OrderService 中已经被迁移走的、不再需要的私有辅助方法
2. 确认所有新 Service 类都有正确的 Javadoc(至少类级别的注释)
3. 检查是否有 @SuppressWarnings 或临时的 TODO 注释需要处理
4. 运行完整的 mvn test,确认所有测试仍然通过
5. 生成一份变更摘要

三、处理复杂依赖:循环依赖是重构的大敌

Spring 项目重构中最常见的麻烦是循环依赖。比如重构后:

OrderCreationService → InventoryService → OrderService → OrderCreationService

Claude Code 在处理这类问题时,你需要给它正确的引导:

在迁移过程中,发现 OrderCreationService 和 InventoryService 存在循环依赖。
请分析并提供解决方案,可选方案包括:
1. 提取公共接口,通过接口解耦
2. 使用事件驱动(ApplicationEvent)解耦
3. 延迟注入(@Lazy)作为临时方案

请推荐最适合当前项目的方案,并说明理由。

Claude Code 的典型回答:

分析了调用关系后,循环依赖的根本原因是:
OrderCreationService.createOrder() 需要检查库存(调用 InventoryService),
而 InventoryService.reserveStock() 需要记录关联订单号(调用 OrderService 查询)。

这实际上是一个领域建模问题。推荐方案2——领域事件:

修改方案:
- 在 OrderCreationService.createOrder() 中,先创建订单(状态=待确认),
  然后发布 OrderCreatedEvent
- InventoryService 监听 OrderCreatedEvent,异步完成库存预留
- 取消 InventoryService 中对 OrderService 的直接调用

这种方式更符合 DDD 的设计原则,也让库存预留变为异步,
提升了下单接口的响应速度。

是否需要我展示完整的实现代码?

四、数据库迁移场景:修改 Entity 和 Repository

4.1 安全地重命名数据库字段

需要将 order 表的 create_time 字段重命名为 created_at(统一命名规范)。
请帮我完成:
1. 修改 Order.java Entity 中的字段映射
2. 修改所有 OrderRepository 中引用了该字段的 JPQL/SQL
3. 检查是否有 XML mapper 文件(MyBatis)也需要修改
4. 生成对应的 Flyway 迁移脚本(V2__rename_create_time_to_created_at.sql)
5. 检查是否有前端 API 响应中直接暴露了这个字段名(需要同步修改 DTO)

4.2 拆分大表对应的 Entity

当一个 Entity 对应的表字段超过 50 个时,通常需要拆分:

Order 实体目前有 67 个字段,需要按业务域拆分:
- 核心字段(订单号、状态、金额等)保留在 Order 实体
- 收货地址相关字段(6个)提取到 OrderShippingAddress 值对象
- 发票相关字段(8个)提取到 OrderInvoice 值对象

请使用 JPA 的 @Embedded 方式实现,
并确保数据库表结构不变(所有字段仍在同一张 order 表)。

五、配置重构:统一管理散落的配置

企业级 Spring 项目通常有大量的配置散落在各处:

请帮我排查项目中所有的"配置坏味道"1. 找出所有使用 @Value("${...}") 的地方,统计有多少个不同的配置项
2. 找出硬编码的字符串常量(URL、文件路径、业务参数等)
3. 检查 application.yml 是否有重复的配置项
4. 建议哪些配置应该迁移到统一的 @ConfigurationProperties 类中

重构后的目标是:每个模块有一个 XxxProperties.java 配置类:

@ConfigurationProperties(prefix = "app.payment")
@Data
public class PaymentProperties {
    private String apiKey;
    private String callbackUrl;
    private Duration timeout = Duration.ofSeconds(30);
    private int maxRetry = 3;
}

六、团队协作场景:如何让 Claude Code 遵守团队规范

6.1 在 CLAUDE.md 中编码团队规范

# CLAUDE.md

## 代码审查检查项(每次修改后必须自查)
- [ ] 新增的 public 方法必须有 Javadoc
- [ ] Service 方法必须有 @Transactional 注解
- [ ] 不允许在 catch 块中只写 e.printStackTrace(),必须用 log.error()
- [ ] 新增接口必须在 swagger 注解中写描述
- [ ] 数字字面量(除 0 和 1)必须定义为常量

## Git 提交规范
提交信息格式:<type>(<scope>): <description>
type: feat/fix/refactor/test/docs
示例:refactor(order): 将 OrderService 按职责拆分为4个子服务

6.2 让 Claude Code 生成符合规范的代码

在每次要求生成代码时,加上引用:

请按照 CLAUDE.md 中定义的编码规范,
为 OrderStateService 中的 pay() 方法补充完整实现。
特别注意:
1. 事务注解不能省略
2. 状态流转失败时必须抛出 OrderStateException(不是 RuntimeException)
3. 每个状态变更都要发布领域事件

七、实战中的常见问题与解决

问题1:Claude Code 修改了不该修改的文件

解决: 在指令开头明确边界

以下操作只能修改 order 模块(src/main/java/com/example/order/)下的文件,
不能修改 payment 模块和 common 模块的任何内容。

问题2:Claude Code 生成的代码不符合项目风格

解决: 给它一个参考示例

请参考 ProductService.java 的代码风格(日志记录方式、异常处理模式、
注释格式),用相同的风格实现 OrderCreationService。

问题3:重构后编译通过但运行时出错

解决: 让它执行更完整的验证

发现运行时报 NullPointerException,错误出现在 OrderService 第 45 行。
请:
1. 查看第 45 行的上下文
2. 检查 OrderCreationService 是否在所有需要注入它的地方都正确添加了 @Autowired
3. 检查 Spring 扫描配置(@ComponentScan)是否覆盖了新建的类所在的包

一句话原则

把上面这些方法压缩成一句话:小步快跑,每步验证,不要让 AI 一次性做太多。

这不是废话——实际使用中,很多人会把复杂的重构任务一股脑丢给 Claude Code,然后在输出里找 bug 找了半天。拆细、确认、执行、验证,这四个动作循环起来,才是真正省时间的方式。