多文件重构与复杂任务实战

系列文章

本篇是 Claude Code 系列的第 5 篇。系列将从安装入门到高级实战,由浅入深地拆解这个终端原生 Coding Agent 的每一个能力。

入门篇

  1. Claude Code 是什么?从安装到第一次对话
  2. 日常开发工作流:用 Claude Code 重塑你的编码习惯

核心篇
3. 内置命令全解:从 /compact 到 /doctor 的实战指南
4. CLAUDE.md:项目上下文工程的艺术

进阶篇
5. 多文件重构与复杂任务实战(本文)
6. Hooks、MCP 与能力扩展

高级篇
7. Headless 模式与 CI/CD 集成
8. 多 Agent 协作:从 Claude Code 到 Codex


引言:从”改一个方法”到”重构整个模块”

在前四篇文章中,我们学会了安装 Claude Code、用它处理日常开发任务、掌握内置命令、以及通过 CLAUDE.md 构建项目记忆。这些能力足以应对大多数单文件、单方法的修改。

但真实项目的挑战远不止于此。

想象这样一个场景:你需要把 Spring Boot 项目中的 User 实体重命名为 Account。这不是改一个文件的事——它涉及 Entity、Repository、Service、Controller、DTO、Mapper、测试类、甚至 SQL 迁移脚本,总共 20+ 个文件。更要命的是,这些文件之间有复杂的引用关系:改了 Entity 的类名,Repository 的泛型参数要跟着变,Service 的注入字段名要跟着变,Controller 的路径映射可能也要调整。

用 IDE 的 Rename 功能?它只能处理 Java 符号级别的重命名,跨 SQL、配置文件、前端接口就力不从心了。手动改?20 个文件逐一修改,漏一个就是运行时 ClassNotFoundException

这正是 Claude Code 真正发力的场景。 它不是逐行替换的文本编辑器,而是一个理解代码语义的 Agent——它能读懂你的项目结构,分析文件间的依赖关系,制定修改计划,然后协调执行。

本文将通过 5 个真实的 Java/Spring Boot 场景,拆解 Claude Code 处理多文件重构和复杂任务的完整工作流。


一、Claude Code 的多文件编辑:为什么它和 IDE 不一样?

1.1 传统工具的局限

在讨论 Claude Code 之前,先理解传统工具为什么在多文件场景下不够用:

工具 能力边界 典型失败场景
IDE Rename Java 符号级重命名 无法处理 XML 映射、SQL、配置文件
Find & Replace 纯文本匹配 无法区分 user 是变量名还是注释里的单词
Spring Boot DevTools 热重载 只能处理运行时变更,不涉及文件修改
手动修改 完全靠人 漏改、错改、顺序依赖

这些工具的共同问题是:它们不理解代码的语义结构。 它们要么只看到单个文件,要么只做文本替换,无法建立”这个字段在哪些文件的哪些位置被引用”的全局视图。

1.2 Claude Code 的差异化:Agentic Loop

Claude Code 的核心是一个 Agentic Loop(代理循环),它在每次任务中经历三个阶段:

1
2
3
4
5
6
7
8
9
10
┌─────────────────────────────────────────────┐
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────┐ │
│ │ 收集上下文 │───▶│ 执行操作 │───▶│ 验证 │ │
│ │ (Gather) │ │ (Act) │ │(Verify)│ │
│ └──────────┘ └──────────┘ └──────┘ │
│ ▲ │ │
│ └──────────────────────────────┘ │
│ │
└─────────────────────────────────────────────┘

在多文件重构中,这个循环的威力体现在:

  1. 收集上下文:Claude 不只读你指定的文件,它会主动搜索整个项目——grep 引用、find 相关文件、读取配置、分析依赖
  2. 执行操作:基于理解的语义关系,逐文件修改,并保持跨文件的一致性
  3. 验证结果:运行测试、编译检查、甚至启动应用验证

关键区别: IDE 的重构是”声明式”的(你告诉它重命名什么),Claude Code 的重构是”对话式”的(你描述目标,它制定计划并执行)。这在跨语言、跨层的复杂重构中优势明显。


二、场景一:实体重命名的全链路更新

2.1 问题描述

一个典型的 Spring Boot 电商项目,需要将 Product 实体重命名为 Merchandise。涉及的文件类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
src/main/java/com/example/
├── entity/Product.java → Merchandise.java
├── repository/ProductRepository.java → MerchandiseRepository.java
├── service/ProductService.java → MerchandiseService.java
├── service/impl/ProductServiceImpl.java
├── controller/ProductController.java → MerchandiseController.java
├── dto/ProductDTO.java → MerchandiseDTO.java
├── mapper/ProductMapper.java → MerchandiseMapper.java
└── exception/ProductNotFoundException.java

src/main/resources/
├── mapper/ProductMapper.xml → MerchandiseMapper.xml
└── application.yml (如果有表名配置)

src/test/java/com/example/
├── service/ProductServiceTest.java
└── controller/ProductControllerTest.java

db/migration/
└── V2__rename_product_to_merchandise.sql

2.2 Claude Code 实战

第一步:探索阶段

1
2
> 将项目中的 Product 实体重命名为 Merchandise,包括所有相关的类、
接口、XML 映射、测试和数据库迁移脚本。先不要修改,先分析影响范围。

Claude 会执行以下操作:

  • grep -rn "Product" src/ 搜索所有引用
  • 读取关键文件理解泛型关系、依赖注入
  • 分析 XML 映射中的 resultTypeparameterType
  • 检查测试类中的 mock 对象和断言

它会返回一份影响分析报告,类似:

1
2
3
4
影响分析:
- 直接修改:12 个 Java 文件 + 1 个 XML + 1 个 SQL
- 间接引用:3 个配置文件中的表名引用
- 风险点:ProductMapper.xml 中的 namespace 需要同步更新

第二步:执行阶段

确认分析无误后:

1
> 好的,按照你的分析执行重构。每改完一组文件就运行一下编译检查。

Claude 会按照依赖顺序执行:

  1. 先改 Entity(最底层)
  2. 再改 Repository、Mapper(依赖 Entity)
  3. 然后改 Service(依赖 Repository)
  4. 接着改 Controller(依赖 Service)
  5. 最后改测试和迁移脚本

第三步:验证阶段

1
> 运行 mvn compile 检查编译,然后运行测试。

Claude 自动执行 mvn compile,如果有编译错误,它会分析错误信息并修复遗漏的引用。

2.3 设计原理:为什么”先分析后执行”很重要?

Claude Code 的 Plan Mode(按 Shift+Tab 两次切换)是一个关键设计。在复杂重构中,”先看清楚再动手”比”边改边看”安全得多。

这和外科手术的原则一样:术前影像诊断 > 直接开刀。 Plan Mode 让 Claude 充当”影像科医生”——先扫描整个项目,绘制依赖图,识别风险点,然后你确认方案后才开始”手术”。


三、场景二:跨层接口签名变更

3.1 问题描述

你的 Spring Boot 项目需要给所有 Service 方法的查询操作添加分页支持。原来的方法签名是:

1
2
3
List<Product> findByCategory(String category);
List<Order> findByUserId(Long userId);
List<Product> searchByName(String name);

需要统一改为:

1
2
3
Page<Product> findByCategory(String category, Pageable pageable);
Page<Order> findByUserId(Long userId, Pageable pageable);
Page<Product> searchByName(String name, Pageable pageable);

这涉及 Service 接口、实现类、Controller、以及前端调用方式的全链路变更。

3.2 Claude Code 实战

1
2
3
> 找到项目中所有返回 List 的 Service 查询方法,统一改为返回 Page<T>,
并添加 Pageable 参数。同步更新 Controller 层,从请求参数中提取 page 和 size。
先列出所有需要修改的方法清单。

Claude 会:

  1. 扫描所有 *Service.java*ServiceImpl.java 文件
  2. 识别返回 List<T> 的查询方法
  3. 区分”需要分页的查询”和”不需要分页的枚举查询”(如 findAllStatuses()
  4. 生成修改清单并请求确认

关键洞察:Claude 不会机械地把所有 List 改成 Page。它会理解哪些方法是真正的查询(需要分页),哪些是枚举或配置加载(不需要分页)。这就是”语义理解”和”文本替换”的本质区别。

3.3 Controller 层的级联更新

Service 签名变了,Controller 必须同步更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 修改前
@GetMapping("/products")
public List<Product> getProducts(@RequestParam String category) {
return productService.findByCategory(category);
}

// 修改后(Claude 自动添加)
@GetMapping("/products")
public Page<Product> getProducts(
@RequestParam String category,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return productService.findByCategory(category, PageRequest.of(page, size));
}

Claude 还会自动处理一些容易遗漏的细节:

  • @RequestParam 的默认值设置
  • 返回类型从 ListPage 的序列化差异(JSON 结构会变)
  • 如果项目有 Swagger/OpenAPI 注解,同步更新 @ApiResponse

四、场景三:框架迁移——从 MyBatis 到 JPA

4.1 问题描述

这是最考验 Agent 能力的场景:把项目的数据访问层从 MyBatis 迁移到 Spring Data JPA。这不是简单的”替换关键字”,而是编程范式的转换

维度 MyBatis JPA
SQL 管理 XML 映射文件 注解 + 方法名推导
查询方式 手写 SQL findByXxx()@Query
关联映射 <association> / <collection> @ManyToOne / @OneToMany
分页 RowBounds / PageHelper Pageable + Page<T>
事务 @Transactional(相同) @Transactional(相同)

4.2 Claude Code 的处理策略

1
2
3
> 将项目的数据访问层从 MyBatis 迁移到 Spring Data JPA。
保留现有的数据库表结构不变,只改 Java 代码和配置。
分步骤执行,每步完成后运行编译检查。

Claude 会制定分步计划:

Step 1:依赖更新

1
2
3
4
5
6
7
8
<!-- 移除 MyBatis -->
<!-- <dependency>mybatis-spring-boot-starter</dependency> -->

<!-- 添加 JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Step 2:Entity 改造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// MyBatis 风格(纯 POJO)
public class Product {
private Long id;
private String name;
private Long categoryId;
// getters/setters
}

// JPA 风格(Claude 自动转换)
@Entity
@Table(name = "product")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private String name;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
private Category category;
}

Step 3:Repository 替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// MyBatis Mapper(删除)
public interface ProductMapper {
Product selectById(Long id);
List<Product> selectByCategory(String category);
int insert(Product product);
int update(Product product);
int deleteById(Long id);
}

// JPA Repository(Claude 生成)
public interface ProductRepository extends JpaRepository<Product, Long> {
List<Product> findByCategoryName(String categoryName);
// JpaRepository 已内置 save(), deleteById(), findById() 等
}

Step 4:删除 XML 映射文件

Claude 会删除 src/main/resources/mapper/*.xml 和 MyBatis 配置。

Step 5:更新 Service 层

productMapper.selectById(id) 改为 productRepository.findById(id).orElseThrow(...)

4.3 风险控制:分步验证

框架迁移的风险很高。Claude Code 的做法是每步验证

1
2
3
4
5
6
7
8
# Step 2 完成后
> mvn compile -pl domain # 只编译 Entity 层

# Step 3 完成后
> mvn compile -pl repository # 编译 Repository 层

# Step 5 完成后
> mvn test # 运行全部测试

这种”小步快跑”的策略,和 Martin Fowler 在《重构》中强调的”每次修改后运行测试”原则完全一致。Claude Code 通过 Agentic Loop 自动化了这个过程。


五、场景四:API 版本化——从 v1 到 v2 的平滑过渡

5.1 问题描述

你的 REST API 需要升级到 v2,但要保持 v1 的向后兼容。这意味着:

  • Controller 要支持 /api/v1//api/v2/ 两套路径
  • v2 的 DTO 可能有字段变更
  • Service 层要能同时处理两个版本的逻辑

5.2 Claude Code 实战

1
2
3
4
5
6
> 为项目的 REST API 添加 v2 版本支持。
v1 保持不变,v2 的主要变更:
1. 用户接口返回值增加 avatarUrl 和 lastLoginAt 字段
2. 商品列表接口改为返回树形分类结构
3. 分页响应统一包装为 { data, pagination } 格式
先分析现有代码结构,制定迁移计划。

Claude 会先分析现有的 Controller、DTO、Service 结构,然后提出方案:

1
2
3
4
5
6
7
8
9
10
11
12
迁移方案:
1. 创建 v2 DTO 包:dto.v2.*
2. 创建 v2 Controller:controller.v2.*
3. 复用现有 Service,通过 Mapper 转换 DTO
4. 在 v2 Controller 中注入 v1 Service + v2 Mapper

文件变更清单:
+ src/.../dto/v2/UserResponseV2.java
+ src/.../dto/v2/PagedResponse.java
+ src/.../controller/v2/UserControllerV2.java
+ src/.../controller/v2/ProductControllerV2.java
~ src/.../config/WebMvcConfig.java (路径映射)

5.3 设计选择:为什么不直接改 v1?

Claude 会解释版本化的设计原理:

API 版本化的黄金法则:永远不要破坏已发布的接口。 v1 的消费者(前端 App、第三方集成)依赖现有的响应格式。新增 v2 路径是添加,不是修改——这是 Open-Closed Principle 在 API 设计中的体现。


六、场景五:大规模代码风格统一

6.1 问题描述

团队合并后,两个代码库的风格不一致:

  • A 团队用 @Slf4j + Lombok,B 团队手写 LoggerFactory
  • A 团队用 var 局部变量类型推断,B 团队用显式类型声明
  • A 团队的异常处理用 @ControllerAdvice,B 团队在 Controller 里 try-catch

6.2 Claude Code 的批量处理

1
2
3
4
5
6
> 统一项目中的日志声明风格:
1. 所有手写的 private static final Logger logger = LoggerFactory.getLogger(...)
改为 @Slf4j 注解
2. 移除手写的 logger 字段
3. 确保 @Slf4j 的 import 语句正确
先统计有多少个文件需要修改。

Claude 会:

  1. grep -rn "LoggerFactory.getLogger" src/ 统计影响范围
  2. 按文件逐一修改:添加 @Slf4j、删除 Logger 字段、更新 import
  3. 运行编译确认没有遗漏

七、上下文管理:大代码库的生存策略

7.1 上下文窗口的物理限制

Claude 的上下文窗口是有限的。当项目有数百个文件时,不可能把所有文件都塞进上下文。Claude Code 通过以下策略管理这个问题:

按需加载,而非全量扫描

Claude 不会一开始就读取所有文件。它根据任务需要,逐步读取:

  • 先搜索(grepfind)定位相关文件
  • 再读取(read)关键文件的内容
  • 最后编辑(edit)目标文件

自动压缩(Auto-compaction)

当上下文接近满时,Claude 自动压缩历史对话:

  • 保留最近的消息和关键代码片段
  • 丢弃早期的详细工具输出
  • 你可以用 /compact focus on 认证模块重构 手动指定保留重点

Subagent 委托

对于大型重构,Claude 可以将子任务委托给 Subagent:

1
2
> 先用一个 subagent 分析 src/main/java 下所有文件的依赖关系图,
然后基于分析结果制定重构计划。

Subagent 有自己独立的上下文窗口,不会污染主会话的上下文。它完成分析后返回摘要,主会话基于摘要决策。

7.2 实战技巧:/context 命令

在长会话中,定期运行 /context 查看上下文使用情况:

1
2
3
4
5
6
Context window usage:
Conversation: 45% (你的对话历史)
File contents: 30% (读取的文件内容)
CLAUDE.md: 5% (项目指令)
System: 10% (系统指令)
Available: 10% (剩余空间)

如果 “File contents” 占比过高,说明你让 Claude 读了太多文件。此时可以:

  1. 运行 /compact 压缩上下文
  2. 用 Subagent 委托分析任务
  3. 更精确地指定需要读取的文件范围

八、Git 集成:重构的安全网

8.1 Checkpoint 机制

Claude Code 在编辑文件前会自动创建快照。如果重构出了问题:

  • Esc 两次可以回退到任意历史状态
  • 或者直接告诉 Claude:”撤销刚才对 ProductService 的修改”

这和 Git 的 stash 类似,但是自动的、细粒度的。你不需要手动 commit 就能回退。

8.2 与 Git 工作流的集成

重构完成后,Claude 可以帮你完成 Git 操作:

1
2
> 查看本次重构的所有变更,生成一个有意义的 commit message,
然后创建一个 PR。

Claude 会:

  1. git diff --stat 查看变更概览
  2. 生成 Conventional Commits 风格的消息
  3. git add -A && git commit
  4. gh pr create 创建 PR,附带变更说明

8.3 Worktree 并行重构

如果重构太大,可以使用 Git Worktree 在多个分支上并行工作:

1
2
3
4
5
6
# 终端 1:重构 Entity 层
cd ~/project && claude

# 终端 2:重构 Controller 层(在 worktree 中)
git worktree add ../project-controllers feature/refactor-controllers
cd ../project-controllers && claude

两个 Claude 会话各自独立工作,互不干扰。最后合并分支。


九、与其他工具的对比

维度 Claude Code Cursor GitHub Copilot IDE Refactor
跨文件理解 ✅ 全项目扫描 ⚠️ 依赖索引 ❌ 当前文件为主 ⚠️ 符号级
自然语言驱动 ✅ 描述目标即可 ✅ Chat 模式 ⚠️ 补全为主 ❌ 需要菜单操作
执行验证 ✅ 运行测试/编译 ⚠️ 需手动触发 ❌ 纯建议 ❌ 纯重构
Git 集成 ✅ commit + PR ⚠️ 需手动 ❌ 无 ❌ 无
框架迁移 ✅ 理解范式差异 ⚠️ 部分支持 ❌ 不支持 ❌ 不支持
上下文管理 ✅ 压缩 + Subagent ⚠️ 有限 ❌ 无 N/A

核心差异: Claude Code 是唯一一个能在”理解整个项目 → 制定计划 → 协调执行 → 自动验证”这个完整闭环中工作的工具。其他工具要么只做理解(Copilot),要么只做执行(IDE Refactor),无法端到端地处理复杂重构。


十、局限性与最佳实践

10.1 Claude Code 的局限

诚实地说,Claude Code 在以下场景中仍有不足:

  1. 超大单文件:如果一个 Java 文件超过 2000 行,Claude 的编辑可能出错(行号偏移)。建议先拆分文件再重构。

  2. 隐式依赖:Spring 的 @ComponentScan、MyBatis 的 XML 映射等隐式关联,Claude 可能遗漏。重构后务必运行完整测试。

  3. 数据库 schema 变更:Claude 能生成迁移脚本,但无法执行数据库变更。需要你手动运行 Flyway/Liquibase。

  4. 运行时行为:Claude 看的是代码文本,不是运行时状态。反射调用、动态代理、AOP 切面等运行时行为可能不在它的分析范围内。

10.2 最佳实践清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
✅ 重构前:
□ 用 Plan Mode(Shift+Tab × 2)让 Claude 先分析再动手
□ 确保有完整的测试覆盖(至少覆盖核心路径)
□ 创建 Git 分支,方便回退
□ 在 CLAUDE.md 中记录项目的架构约定

✅ 重构中:
□ 分步执行,每步后编译验证
□ 用 /context 监控上下文使用
□ 上下文紧张时用 /compact 或 Subagent
□ 不要一次改太多文件(建议每批 5-10 个)

✅ 重构后:
□ 运行完整测试套件
□ 检查 git diff 确认变更范围
□ 用 Claude 生成 PR 描述
□ Code Review 时重点关注跨文件一致性

总结

多文件重构是 Claude Code 从”好用”到”不可替代”的分水岭。

核心认知升级:

  1. Claude Code ≠ 高级 Find & Replace——它理解代码语义,不是文本匹配
  2. 先规划后执行——Plan Mode 是复杂任务的安全阀
  3. 上下文是稀缺资源——用 /context 监控,用 Subagent 分流
  4. 小步快跑——分步执行 + 编译验证 = 低风险重构
  5. Git 是最后的安全网——Checkpoint + Worktree + PR = 多层保护

下一篇我们将进入 Hooks、MCP 与能力扩展——当 Claude Code 的内置工具不够用时,如何通过 Hooks 自动化工作流、通过 MCP 连接外部服务、通过 Skills 封装可复用的复杂流程。


参考资料