Skip to content

第13章 Codex 调试与重构:遗留代码安全操作

代码写出来难,调试已有代码更难。遗留代码(Legacy Code)通常有几个特点:文档缺失、测试不足、逻辑复杂、修改风险大。Vibe Coding 擅长从零创作,但在遗留代码中操作,需要额外的谨慎。本章学习如何在不破坏现有功能的前提下,用 Codex 安全地调试和重构遗留代码。


13.1 遗留代码分析策略

遗留代码分析三步法

bash
# Step1: 理解系统全貌
claude "分析 src/backend 目录的代码结构,输出:
1. 模块划分和依赖关系图
2. 主要的入口点和核心流程
3. 数据流向和存储方式

使用 mermaid 语法输出模块关系图"

# Step2: 定位目标模块
claude "深入分析 src/backend/modules/order/ 目录:
1. 核心类和接口
2. 公开方法及其调用关系
3. 依赖的外部服务和数据库表
4. 已知的问题和坑"

# Step3: 理解边界条件
claude "分析 order 模块中的边界条件和异常处理:
1. 空指针检查
2. 并发场景
3. 超时处理
4. 事务边界
5. 是否有隐藏的副作用?"

架构图生成

用 AI 自动生成项目的架构图:

bash
claude "扫描项目源码,生成架构文档:
1. README-arch.md:整体架构说明
2. 使用 plantuml 或 mermaid 生成:
   - 模块依赖图
   - 数据流图
   - 请求处理流程图"

13.2 Bug 定位与修复工作流

AI 辅助 Bug 定位

当程序报错时,将报错信息和上下文交给 Codex 分析:

bash
claude "分析以下错误:

错误信息:
java.lang.NullPointerException: Cannot invoke method on null object
  at com.travelwise.service.OrderService.calculateDiscount(OrderService.java:42)
  at com.travelwise.controller.OrderController.getDiscount(OrderController.java:18)

上下文:
- OrderService.calculateDiscount() 方法在第 42 行报错
- 这个方法接收一个 Order 对象作为参数
- 怀疑是 order.getUser() 返回了 null

请:
1. 分析可能的根因
2. 列出所有可能导致 user 为 null 的场景
3. 给出修复方案
4. 推荐单元测试覆盖场景"

修复工作流

bash
# 1. 让 AI 先理解相关代码
claude "阅读 OrderService.java 的 calculateDiscount 方法及相关代码,理解上下文"

# 2. 让 AI 分析根因
claude "分析 NullPointerException 的根因,给出修复方案"

# 3. 让 AI 生成修复代码
claude "修改 OrderService.calculateDiscount():
1. 添加 null 检查
2. 记录异常日志
3. 返回合理的默认值
4. 不要改变原有逻辑结构"

# 4. 验证修复
claude "为修复后的代码编写单元测试,覆盖之前分析的几种 null 场景"

13.3 安全重构策略

重构前:建立安全网

bash
# 1. 确保有版本控制
git status
git branch backup-before-refactor

# 2. 生成当前代码的快照测试
claude "为 OrderService 所有公开方法生成单元测试,确保重构前的行为被测试覆盖"

# 3. 记录重构前的行为
claude "运行所有测试,记录当前通过的测试用例数量和名称"

重构执行

bash
# 分步骤重构,每次只改一个点
claude "重构 OrderService.calculateDiscount(),目标:
1. 提取长方法,拆分为多个小方法
2. 消除重复代码
3. 统一变量命名风格

步骤:
1. 先提取 calculateBasePrice() 方法
2. 再提取 calculateUserDiscount() 方法
3. 最后提取 calculateFinalDiscount() 方法
4. 每步提取后运行测试验证"

重构后验证

bash
# 1. 运行所有测试
pnpm test

# 2. 回归测试
claude "对比重构前后的测试结果,确保没有功能退化"

# 3. 性能测试(如涉及性能)
claude "如果 calculateDiscount 在性能关键路径上,编写 JMH 基准测试对比重构前后的性能"

13.4 单元测试生成与覆盖增强

AI 生成测试

bash
claude "为 AttractionService 生成单元测试:
1. 使用 JUnit 5 + Mockito
2. 覆盖以下场景:
   - getById 正常查询
   - getById 查询不存在的 ID
   - list 分页查询
   - search 关键词搜索
   - search 城市筛选
3. Mock 所有外部依赖(Repository、Redis)
4. 测试文件放在 test/ 目录,与源文件同包"

生成的测试示例:

java
@ExtendWith(MockitoExtension.class)
class AttractionServiceTest {

    @Mock
    private AttractionRepository attractionRepository;

    @InjectMocks
    private AttractionService attractionService;

    @Test
    void should_return_attraction_when_getById_exists() {
        UUID id = UUID.randomUUID();
        Attraction attraction = new Attraction();
        attraction.setId(id);
        attraction.setName("西湖");

        when(attractionRepository.findById(id)).thenReturn(Optional.of(attraction));

        AttractionDTO result = attractionService.getById(id);

        assertNotNull(result);
        assertEquals("西湖", result.getName());
    }

    @Test
    void should_throw_when_getById_not_found() {
        UUID id = UUID.randomUUID();
        when(attractionRepository.findById(id)).thenReturn(Optional.empty());

        assertThrows(BusinessException.class, () -> attractionService.getById(id));
    }
}

13.5 性能优化与内存泄漏排查

性能分析

bash
claude "分析 AttractionService 的性能:
1. 识别可能的 N+1 查询问题
2. 识别不必要的全表扫描
3. 识别可以缓存的查询结果
4. 给出优化建议和代码修改方案"

常见性能问题修复

问题类型症状修复方案
N+1 查询循环内查询数据库使用 JOIN FETCH 或批量查询
大对象复制对象序列化慢使用引用或不可变对象
内存泄漏堆内存持续增长检查集合清理、连接释放
同步阻塞并发差异步化或增加缓存
索引缺失查询慢添加数据库索引

内存泄漏排查

bash
claude "用 VisualVM 或 JProfiler 分析应用程序的内存使用:
1. 找出占用内存最多的对象
2. 追踪可疑对象的 GC Roots
3. 识别内存泄漏的类
4. 给出修复建议"

13.6 遗留代码重构案例:订单模块

案例背景

订单模块经过多人维护,存在以下问题:

  • 代码重复严重(约 30% 的逻辑重复)
  • 方法过长,最长的方法超过 500 行
  • 缺乏异常处理,很多异常直接抛给上层
  • 没有事务边界,数据库操作分散

重构步骤

bash
# Step1: 分析现状
claude "分析 order 模块的问题:
1. 统计代码重复率
2. 列出超过 200 行的方法
3. 识别缺失异常处理的方法
4. 识别没有 @Transactional 的数据库操作
输出:重构优先级列表"

# Step2: 提取公共逻辑
claude "提取 order 模块的公共逻辑到 OrderUtils:
1. 价格计算公式
2. 日期处理逻辑
3. 状态转换规则
4. 参数校验规则"

重构后的模块结构

order/
├── controller/OrderController.java     # 入口,职责最小化
├── service/
│   ├── OrderService.java               # 协调层,事务边界
│   ├── OrderDomainService.java         # 领域逻辑
│   └── OrderUtils.java                 # 公共工具
├── repository/OrderRepository.java     # 数据访问
├── model/
│   ├── entity/Order.java               # 实体
│   ├── dto/                           # 数据传输对象
│   └── event/OrderEvent.java           # 领域事件
└── exception/                         # 异常定义

本章小结

技巧核心要点
遗留代码分析三步法:全貌 → 目标模块 → 边界条件
Bug 定位报错信息 + 上下文 → 根因分析 → 修复方案 → 验证
安全重构建安全网 → 分步执行 → 逐步验证
测试生成JUnit 5 + Mockito,覆盖正常/异常/边界场景
性能优化N+1 查询、大对象复制、索引缺失是常见问题
重构案例提取公共逻辑 → 领域分离 → 事务边界明确

下一章,我们将学习团队协作与规范落地——如何让团队成员都遵循相同的规范,如何在团队中高效地使用 Codex。

热爱生活,喜好美食,追求未来!