本教程面向需要在生产环境中改造遗留系统的中高级开发者。你将学会如何利用 Claude Code 的 Agent 能力,对零文档、零测试的老旧代码库执行安全的增量现代化——从依赖图谱绘制、特征测试生成,到逐模块迁移与持续回归验证。全文以一个真实的 Java EE 单体应用改造为微服务架构为主线,提供可直接复用的 Prompt 模板和 CLAUDE.md 配置。
适用人群:维护遗留系统的后端开发者、技术负责人、SRE 工程师
预计学习时长:60-90 分钟(含实操)
| 条件 | 说明 |
|---|---|
| Claude Code CLI | 已安装并完成 API Key 配置(claude 命令可用) |
| 遗留代码仓库 | 一个你熟悉的旧项目(Git 管理) |
| 基础测试工具 | 项目对应语言的测试框架(JUnit、pytest 等) |
| Git 操作意识 | 会创建分支、查看 diff、回滚 commit |
直接对遗留代码说"帮我重构"是 Claude Code 使用中最危险的操作之一。遗留系统的三大特征决定了必须采用分步策略:
Claude Code 的 Agent 模式天然适合这种场景——它能自动读取多文件上下文、生成分步计划、逐个文件修改并验证。但前提是你必须给它正确的约束。
我的项目是一个2015年的Java EE单体应用,约12万行代码。
项目目录: /home/dev/legacy-monolith
技术栈: Java 8, Spring 4, MyBatis, Oracle DB, Maven
目标: 将订单模块拆分为独立微服务
在项目根目录创建专用的 CLAUDE.md,定义项目上下文和约束规则:
# /home/dev/legacy-monolith/CLAUDE.md
## 项目概况
- 12万行Java EE单体应用,2015年开发至今
- 技术栈: Java 8, Spring 4.2, MyBatis 3.3, Oracle 11g
- 构建工具: Maven 3.x
- 无单元测试,仅有少量手动集成测试
## 现代化目标
- 将订单模块(order)拆分为独立Spring Boot微服务
- 保持与主系统的API兼容
- 使用PostgreSQL替代Oracle(仅限新服务)
## 安全约束(CRITICAL)
- 每次修改前必须先创建特征测试(Characterization Test)
- 单次修改不超过3个文件
- 每次修改后必须通过编译和现有测试
- 不修改数据库Schema,除非有明确的migration脚本
- 所有新代码必须包含错误处理和日志
## 代码规范
- 遵循现有命名约定(Service以Impl结尾)
- 新模块使用Spring Boot 3.x标准结构
- 配置使用application.yml而非properties
然后用 Claude Code 生成依赖图谱:
claude -p "分析项目结构,生成订单模块的依赖关系图谱。需要识别:
1. 订单模块涉及的所有Java包和类
2. 这些类被其他模块引用的情况(入向依赖)
3. 订单模块引用的外部模块(出向依赖)
4. 共享数据库表的访问情况
输出格式:每个依赖标注强度(强/中/弱)和风险等级(高/中/低)"
Claude Code 会输出类似这样的分析:
=== 订单模块依赖图谱 ===
入向依赖(被其他模块引用):
[强|高] PaymentService → OrderService.createOrder()
[强|高] ShippingService → OrderService.getOrderById()
[中|中] ReportService → OrderRepository.queryOrders()
[弱|低] NotificationService → OrderStatusListener.onStatusChange()
出向依赖(订单模块引用的外部):
[强|高] → UserRepository (用户模块)
[强|高] → ProductRepository (商品模块)
[中|中] → InventoryService (库存模块)
[弱|低] → LogUtil (公共工具)
共享数据库表:
[高] T_ORDER, T_ORDER_ITEM, T_ORDER_STATUS
[中] T_PAYMENT (与支付模块共享)
Q: Claude Code 报"context too large"怎么办?
使用 --max-turns 限制交互轮次,并分模块分析:
claude -p "只分析 src/main/java/com/company/order 目录下的类依赖关系" \
--max-turns 5
对于超大项目(>50万行),先用 find 和 grep 缩小范围,再让 Claude Code 分析子目录。
特征测试(Characterization Test)是 Michael Feathers 在《修改代码的艺术》中提出的核心概念——不是测试代码"应该"做什么,而是记录代码"当前实际"做什么。这是安全重构的前提。
claude -p "为 OrderService.createOrder() 方法生成特征测试。
要求:
1. 读取 OrderService.java 的完整实现
2. 读取其所有依赖的接口定义
3. 使用 Mockito 模拟所有外部依赖
4. 测试用例覆盖方法的所有分支路径
5. 每个测试用例的assert必须精确记录当前行为(包括看似bug的行为)
6. 不要修正任何现有行为,即使看起来是bug
测试文件放在 src/test/java/com/company/order/ 目录下"
Claude Code 会生成类似这样的测试:
// Characterization Test - 记录当前行为,不判断对错
class OrderServiceCharacterizationTest {
@InjectMocks
private OrderServiceImpl orderService;
@Mock
private OrderRepository orderRepository;
@Mock
private ProductRepository productRepository;
@Mock
private InventoryService inventoryService;
@Test
void createOrder_normalCase_savesOrderWithCorrectTotal() {
// 当前行为:计算总价时包含折扣但不包含税费
when(productRepository.findById(1L))
.thenReturn(new Product(1L, "Widget", new BigDecimal("99.00")));
when(inventoryService.checkStock(1L, 2))
.thenReturn(true);
OrderRequest request = new OrderRequest(1L, 2, "DISCOUNT10");
Order result = orderService.createOrder(request);
// 特征记录:当前实现的总价 = 99 * 2 * 0.9 = 178.20
// 注意:这里没有加税,这是一个已知的行为特征
assertEquals(new BigDecimal("178.20"), result.getTotalAmount());
verify(orderRepository).save(any(Order.class));
}
@Test
void createOrder_outOfStock_throwsRuntimeException() {
// 当前行为:库存不足时抛出RuntimeException(非业务异常)
when(productRepository.findById(1L))
.thenReturn(new Product(1L, "Widget", new BigDecimal("50.00")));
when(inventoryService.checkStock(1L, 100))
.thenReturn(false);
OrderRequest request = new OrderRequest(1L, 100, null);
assertThrows(RuntimeException.class,
() -> orderService.createOrder(request));
}
}
对于大量需要特征测试的类,使用 -p 模式批处理:
#!/bin/bash
# gen_characterization_tests.sh
# 为订单模块下所有Service类生成特征测试
CLASSES=$(find src/main/java/com/company/order -name "*ServiceImpl.java")
for class in $CLASSES; do
classname=$(basename "$class" .java)
echo "Generating tests for $classname..."
claude -p "为 $class 生成完整的特征测试(Characterization Test)。
使用JUnit5和Mockito。记录所有当前行为,不修正任何bug。
测试文件放在 src/test/java/ 对应路径下。" \
--max-turns 3 \
--output-format text > "test_report_${classname}.txt"
done
Q: 生成的测试编译不过怎么办?
让 Claude Code 自行修复:
claude -p "运行 mvn test-compile,修复所有编译错误。
不要修改测试的assert内容,只修复import和类型问题。"
有了依赖图谱和特征测试后,下一步是定义微服务的边界。这一步不需要写代码,而是用 Claude Code 生成结构化的接口文档。
claude -p "基于已有的依赖分析,为订单微服务定义API接口契约。
要求:
1. 分析 OrderService 和 OrderRepository 的所有public方法
2. 区分内部方法(仅限新微服务使用)和外部API(其他模块需要调用)
3. 外部API设计为RESTful接口,使用Spring Boot 3.x的@RestController
4. 生成OpenAPI 3.0规范的YAML文档
5. 列出需要保留的跨模块调用点(暂时通过HTTP客户端桥接)"
生成的 OpenAPI 文档片段:
# order-service-api.yaml
openapi: 3.0.3
info:
title: Order Microservice API
version: 1.0.0
paths:
/api/v1/orders:
post:
summary: 创建订单
operationId: createOrder
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateOrderRequest'
responses:
'201':
description: 订单创建成功
content:
application/json:
schema:
$ref: '#/components/schemas/OrderResponse'
'400':
description: 参数校验失败
'409':
description: 库存不足
/api/v1/orders/{orderId}:
get:
summary: 查询订单详情
operationId: getOrderById
parameters:
- name: orderId
in: path
required: true
schema:
type: integer
format: int64
responses:
'200':
description: 成功返回订单信息
让 Claude Code 帮你维护架构决策记录(ADR):
claude -p "创建一个ADR(Architecture Decision Record)文件:
ADR-001-订单服务拆分策略.md
记录以下决策:
1. 为什么选择先拆订单模块(而非支付或库存)
2. API兼容策略:采用BFF模式还是直接REST
3. 数据库迁移策略:双写还是CDC
4. 跨模块通信:同步HTTP还是异步MQ"
Strangler Fig(绞杀者无花果)模式是 Martin Fowler 推荐的遗留系统迁移策略——逐步用新系统替换旧系统,而非一次性重写。Claude Code 的 worktree 功能天然支持这种并行开发模式。
# 创建隔离的worktree进行新服务开发
claude worktree create order-service-migration
在新 worktree 中初始化 Spring Boot 项目:
claude -p "在当前目录创建一个Spring Boot 3.2微服务项目:
1. 使用Spring Initializr结构
2. 依赖: spring-boot-starter-web, spring-boot-starter-data-jpa,
spring-boot-starter-validation, postgresql driver, flyway
3. 配置文件使用application.yml
4. 创建标准的controller/service/repository/package结构
5. 添加健康检查端点
6. 端口设为8081"
这是最关键的一步。不要让 Claude Code 一次性迁移所有代码,而是按照以下顺序逐个方法迁移:
claude -p "迁移 OrderService.getOrderById() 方法到新的微服务。
严格按照以下步骤:
1. 在新服务中创建 OrderController.getOrderById()
2. 从旧代码复制核心业务逻辑到新的 OrderService
3. 使用JPA实体替代MyBatis映射(保持字段名不变)
4. 编写对应的Repository方法
5. 确保特征测试在新环境中通过
6. 暂时不删除旧代码,只添加@Deprecated注解"
在旧单体应用中添加路由转发,将对已迁移接口的请求转发到新微服务:
claude -p "在旧单体应用中添加一个OrderServiceProxy。
当订单API调用到达旧系统时:
1. 如果对应的 getOrderById 已迁移到新服务,转发到 http://order-service:8081
2. 如果未迁移,继续走旧的 OrderServiceImpl
3. 使用RestTemplate实现转发
4. 添加降级逻辑:如果新服务不可用,fallback到旧实现
5. 添加请求耗时的metrics日志,用于对比新旧服务的性能
在 src/main/java/com/company/proxy/ 目录下创建。"
代理类代码片段:
@Component
@Primary
public class OrderServiceProxy implements OrderService {
private final OrderServiceImpl legacyService;
private final RestTemplate restTemplate;
private final MigrationConfig migrationConfig;
@Override
public Order getOrderById(Long id) {
if (migrationConfig.isMigrated("getOrderById")) {
// 已迁移:转发到新微服务
return restTemplate.getForObject(
"http://order-service:8081/api/v1/orders/" + id,
Order.class);
}
// 未迁移:使用旧实现
return legacyService.getOrderById(id);
}
// ... 其他方法类似
}
Q: 两个系统的数据不一致怎么办?
使用双写 + CDC(Change Data Capture)策略:
claude -p "设计数据库同步方案:
1. 旧系统写Oracle的同时,通过Debezium CDC同步到PostgreSQL
2. 新服务的写操作同时写入PostgreSQL和发送MQ消息
3. 旧系统消费MQ消息更新Oracle
4. 添加数据一致性校验的定时任务"
每完成一批方法迁移,都需要运行全量验证。Claude Code 可以自动化这个过程:
claude -p "执行回归验证:
1. 运行所有特征测试: mvn test
2. 对比新旧服务的API响应(从测试报告读取)
3. 检查是否有测试失败
4. 如果有失败,分析失败原因:
- 是预期行为变化?→ 更新特征测试
- 是真正的回归bug?→ 修复新服务代码
5. 生成验证报告保存到 migration/reports/ 目录"
将验证过程写成可重复执行的脚本:
#!/bin/bash
# regression_check.sh
# 每次迁移一批方法后执行
echo "=== Step 1: 编译检查 ==="
mvn compile -q || { echo "编译失败!"; exit 1; }
echo "=== Step 2: 运行特征测试 ==="
mvn test -pl order-module -Dtest="*CharacterizationTest" -q
TEST_RESULT=$?
echo "=== Step 3: API兼容性检查 ==="
claude -p "对比以下两组API响应是否一致:
旧系统: $(curl -s http://localhost:8080/order/1)
新系统: $(curl -s http://localhost:8081/api/v1/orders/1)
忽略响应中的timestamp和traceId字段。"
echo "=== Step 4: 生成迁移进度报告 ==="
claude -p "读取 migration/ 目录下的所有ADR和测试报告,
生成当前迁移进度摘要:
- 已迁移的方法列表
- 待迁移的方法列表
- 已知风险项
- 建议的下一步操作"
if [ $TEST_RESULT -ne 0 ]; then
echo "⚠️ 特征测试失败,请检查!"
exit 1
fi
echo "✅ 回归验证通过"
在 .claude/settings.json 中配置 hooks,让 Claude Code 在每次文件修改后自动触发测试:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"command": "cd /home/dev/legacy-monolith && mvn test-compile -q 2>&1 | tail -5",
"description": "每次文件修改后编译检查"
}
]
}
}
当所有方法都迁移完毕且稳定运行后,清理旧代码:
claude -p "执行订单模块旧代码退役:
1. 检查迁移清单,确认所有方法已在新服务中实现
2. 运行全量特征测试,确认新旧服务行为完全一致
3. 移除旧单体中的OrderServiceImpl(保留接口定义作为文档)
4. 移除OrderServiceProxy的降级逻辑
5. 更新README和架构文档
6. 在git中创建 tagged release: order-service-v1.0.0
注意:每一步操作前先确认,不要自动执行。"
claude -p "基于整个迁移过程,生成最终的迁移文档:
包含:
1. 架构变更概览(Before/After对比)
2. 迁移的时间线和关键决策点
3. 遇到的问题和解决方案
4. 新服务的运维指南(部署、监控、排错)
5. 遗留系统的其他模块迁移建议
文档保存到 migration/FINAL_MIGRATION_REPORT.md"
flowchart TB
A[遗留系统现状评估] --> B[CLAUDE.md 项目画像]
B --> C[依赖图谱绘制]
C --> D[特征测试生成]
D --> E[组件边界定义]
E --> F[API契约设计]
F --> G[新微服务脚手架]
G --> H{逐方法迁移}
H --> I[复制业务逻辑]
I --> J[适配新框架]
J --> K[路由切换]
K --> L[回归验证]
L --> M{验证通过?}
M -->|是| N{还有待迁移方法?}
M -->|否| O[分析失败原因]
O --> P[修复或更新测试]
P --> H
N -->|是| H
N -->|否| Q[旧代码退役]
Q --> R[文档沉淀]
R --> S[迁移完成]
style A fill:#f9f,stroke:#333
style D fill:#ff9,stroke:#333
style L fill:#9ff,stroke:#333
style S fill:#9f9,stroke:#333
以下是一个真实的迁移会话记录(已脱敏),展示每个步骤的实际耗时和关键决策:
Day 1: 项目画像(2小时)
├── 生成CLAUDE.md配置
├── 运行依赖分析 → 识别23个类、47个依赖关系
├── 标记高风险依赖: PaymentService(强), InventoryService(强)
└── 输出: dependency-graph.md
Day 2-3: 特征测试(6小时)
├── 为OrderServiceImpl生成12个特征测试
├── 为OrderRepository生成8个特征测试
├── 编译修复: 3处泛型类型不匹配
├── 特征发现: createOrder()存在隐藏的null指针风险
└── 输出: 20个测试用例, 全部GREEN
Day 4: 接口契约(3小时)
├── 生成OpenAPI 3.0规范
├── ADR-001: 选择同步HTTP而非MQ(降低复杂度)
├── ADR-002: 选择双写而非CDC(数据量小,性价比更高)
└── 输出: order-service-api.yaml
Day 5-8: 逐步迁移(12小时)
├── getOrderById() → 1小时(只读,最简单)
├── queryOrders() → 2小时(复杂查询,需JPA Specification)
├── createOrder() → 4小时(核心逻辑,涉及3个外部调用)
├── updateOrderStatus() → 2小时(状态机逻辑)
├── cancelOrder() → 3小时(涉及退款逻辑,需要补偿事务)
└── 每个方法迁移后立即运行回归验证
Day 9-10: 路由切换与灰度(4小时)
├── 实现OrderServiceProxy
├── 配置化路由开关(按方法粒度)
├── 10%流量灰度 → 50% → 100%
└── 监控: P99延迟从120ms降至45ms
Day 11: 旧代码退役(2小时)
├── 确认新服务稳定运行48小时
├── 移除旧代码
├── 生成迁移报告
└── 创建 release tag
以下是本教程中所有关键 Prompt 的精简模板,可直接复制使用:
分析 [模块名] 的依赖关系:
1. 涉及的包和类
2. 入向依赖(被谁引用)
3. 出向依赖(引用了谁)
4. 共享资源(数据库表、配置、文件)
输出格式:每项标注强度和风险等级
为 [类名.方法名] 生成特征测试:
- 使用JUnit5 + Mockito
- 模拟所有外部依赖
- 覆盖所有分支路径
- 精确记录当前行为(包括bug)
- 不修正任何现有行为
迁移 [方法名] 到新微服务:
1. 从 [旧路径] 复制核心逻辑
2. 适配 [新框架/新依赖]
3. 确保特征测试通过
4. 旧代码添加 @Deprecated
执行回归验证:
1. 运行所有特征测试
2. 对比新旧API响应
3. 分析失败原因(行为变化 vs 回归bug)
4. 生成验证报告
mindmap
root((遗留代码现代化))
安全第一
特征测试先行
单次修改≤3文件
每次改完必编译
Git分支保护
渐进式迁移
Strangler Fig模式
方法级粒度迁移
路由切换而非重写
灰度发布验证
上下文管理
CLAUDE.md项目画像
ADR决策记录
分模块限制上下文
Worktree隔离开发
自动化验证
Hooks编译检查
特征测试回归
API兼容性对比
生成验证报告
| 维度 | 评估 |
|---|---|
| 适合训练营类型 | 企业内训(遗留系统改造专项)、高级开发者训练营 |
| 学员基础要求 | Java/Spring 经验 2 年以上,理解微服务架构概念 |
| 预计教学时长 | 4-6 小时(含实操演练) |
| 实操依赖 | 需要准备一个可编译的遗留 Java 项目作为练习素材 |
| 难度评级 | ★★★★☆(中高级) |
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
