一、接手这个"烂摊子"

事情的起因很简单:公司有一套跑了三年多的内部审批流后端服务,基于 Spring Boot 构建,负责处理员工请假、报销、采购等十几种审批场景。

系统能跑,但没人敢动。

接手之后我打开代码,第一感受是窒息——ApprovalController 单个文件将近 4000 行,ApprovalService 更夸张,超过 8000 行。整个文件从头到尾几乎没有一行注释,方法名是 doApprovedoApprove2doApproveNewdoApproveV2Final 这种风格。没有任何单元测试,上线全靠手动点页面验。

我需要在两周内完成重构,同时保证线上服务不中断。

正常情况下光是读懂这堆代码就要花掉将近一周,更别说重写了。我决定把 Claude Code 拉进来——Anthropic 出的命令行 AI 编程工具,可以直接在终端里操作项目文件。

技术栈情况:

  • 框架:Spring Boot 2.7 + MyBatis-Plus
  • 数据库:MySQL 8.0
  • 构建工具:Maven
  • 代码规模:11 个核心模块,约 3.2 万行代码

二、Claude Code 真正能干活的地方

用下来,有三个场景它真的让我眼前一亮。

场景一:读懂旧代码,秒出注释和文档

第一步不是重写,是先搞清楚这堆代码到底在干什么。

我把 ApprovalService 里最核心的审批状态流转方法丢给 Claude Code,让它先生成 Javadoc 注释:

请为以下 Java 方法添加完整的 Javadoc 注释,
说明方法用途、每个参数的含义、返回值、
以及可能抛出的异常。不要修改任何业务逻辑。

[粘贴代码]

输出质量出乎意料地高。原来这个方法我读了二十分钟没完全搞明白,Claude Code 生成的注释把状态流转逻辑描述得清清楚楚。当然有两个参数的描述有点偏,但整体八成以上可以直接用。

改写前(无注释):

public Boolean doApprove(Long id, Integer status, String remark, Long operatorId) {
    ApprovalRecord record = approvalMapper.selectById(id);
    if (record == null || record.getStatus() != 1) return false;
    record.setStatus(status);
    record.setRemark(remark);
    record.setOperatorId(operatorId);
    record.setUpdateTime(new Date());
    approvalMapper.updateById(record);
    if (status == 2) {
        notifyService.sendApproved(record);
    } else if (status == 3) {
        notifyService.sendRejected(record);
    }
    return true;
}

Claude Code 生成注释后:

/**
 * 执行审批操作,更新审批记录状态并触发对应通知。
 *
 * <p>仅当审批记录处于「待审批」状态(status=1)时,操作才会生效。
 * 审批通过(status=2)将触发审批通过通知;
 * 审批拒绝(status=3)将触发审批拒绝通知。</p>
 *
 * @param id         审批记录唯一ID
 * @param status     目标状态(2-通过,3-拒绝)
 * @param remark     审批备注,允许为空
 * @param operatorId 执行审批操作的用户ID
 * @return 操作是否成功;记录不存在或状态不为待审批时返回 false
 */
public Boolean doApprove(Long id, Integer status, String remark, Long operatorId) {
    // ... 原有逻辑不变
}

这一步帮我节省了大量"读代码猜意图"的时间,整个模块的文档注释覆盖,一天不到全部完成。

场景二:重复性重构,十几个 if-else 变策略模式

这套审批系统支持十几种审批类型,原来的实现方式是一个巨大的 if-else 链:

if ("LEAVE".equals(type)) {
    // 请假审批逻辑,约200行
} else if ("EXPENSE".equals(type)) {
    // 报销审批逻辑,约180行
} else if ("PURCHASE".equals(type)) {
    // 采购审批逻辑,约230行
}
// ... 还有十几个分支

整个 processApproval 方法超过 1500 行。

让 Claude Code 把它重构为策略模式,它给出的结构非常清晰:

// 策略接口
public interface ApprovalStrategy {
    String getType();
    ApprovalResult process(ApprovalContext context);
}

// 各审批类型实现类(Claude Code 自动生成骨架)
@Component
public class LeaveApprovalStrategy implements ApprovalStrategy {
    @Override
    public String getType() { return "LEAVE"; }

    @Override
    public ApprovalResult process(ApprovalContext context) {
        // 原有请假逻辑迁移至此
    }
}

// Context 统一调度
@Service
public class ApprovalStrategyContext {
    private final Map<String, ApprovalStrategy> strategyMap;

    public ApprovalStrategyContext(List<ApprovalStrategy> strategies) {
        this.strategyMap = strategies.stream()
            .collect(Collectors.toMap(ApprovalStrategy::getType, s -> s));
    }

    public ApprovalResult process(String type, ApprovalContext context) {
        ApprovalStrategy strategy = strategyMap.get(type);
        if (strategy == null) throw new UnsupportedApprovalTypeException(type);
        return strategy.process(context);
    }
}

原来 1500 行的怪物方法,拆成了 14 个职责单一的策略类。每个类单独维护,新增审批类型只需加一个实现,不再动核心逻辑。

场景三:单元测试从 0 到 60%

重构之前,这个项目的单元测试覆盖率是 0%。让 Claude Code 针对核心 Service 方法生成 JUnit5 + Mockito 用例:

@ExtendWith(MockitoExtension.class)
class ApprovalServiceTest {

    @Mock
    private ApprovalMapper approvalMapper;

    @Mock
    private NotifyService notifyService;

    @InjectMocks
    private ApprovalService approvalService;

    @Test
    void doApprove_shouldReturnFalse_whenRecordNotFound() {
        when(approvalMapper.selectById(anyLong())).thenReturn(null);
        Boolean result = approvalService.doApprove(1L, 2, "ok", 100L);
        assertFalse(result);
        verify(notifyService, never()).sendApproved(any());
    }

    @Test
    void doApprove_shouldNotify_whenApproved() {
        ApprovalRecord record = new ApprovalRecord();
        record.setStatus(1);
        when(approvalMapper.selectById(1L)).thenReturn(record);
        approvalService.doApprove(1L, 2, "approved", 100L);
        verify(notifyService).sendApproved(record);
    }
}

两天内,核心模块测试覆盖率从 0% 提升到约 62%。


三、踩坑实录:Claude Code 让我栽跟头的 5 件事

当然不是什么都顺利。这 5 个坑,希望能帮你少走弯路。

坑1:它引用了一个不存在的方法

重构 ApprovalRecord 的工具类时,Claude Code 生成了这样一行:

String normalizedRemark = StringUtils.trimAllWhitespaceAndNormalize(remark);

我当时以为是 Spring 的 StringUtils 里的方法,版本可能不对,查了半天 Maven 依赖,升了一次 Spring 版本,还是报 NoSuchMethodError

最后去翻源码才发现:这个方法根本不存在。是 Claude Code 自己造出来的,名字听起来非常像官方 API,但实际上从未存在过。

教训: 所有 AI 生成的外部方法调用,必须逐一到源码或官方文档里核实。“看起来像"不等于"确实有”。

坑2:上下文一长就开始"断片"

当我把整个 ApprovalService(约 8000 行)一次性丢给它处理时,它在重构后半段把前面已经定义好的 ApprovalContext 类给"忘了",开始用 ApprovalDTOApprovalRequest 等各种不一致的命名,生成的代码根本无法编译。

教训: 长文件必须拆开处理,每次只投喂一个方法或一个类。我后来的习惯是:每次上下文不超过 300 行,效果稳定很多。

坑3:业务逻辑它根本不懂

审批系统里有一段会签逻辑:多个审批人必须全部通过,审批才能流转到下一节点;但如果某个审批人的级别达到 L5 以上,可以直接一票通过。

Claude Code 生成的代码把这两个条件的判断顺序写反了——先判断了全员通过,再判断 L5 特权,导致 L5 审批人一票通过的逻辑永远不会触发。

代码读起来完全没问题,逻辑也很流畅,但业务跑起来就是不对。这个 bug 在测试环节才被发现,险些上线。

教训: 凡是和具体业务规则强绑定的核心逻辑,不能直接用 AI 生成结果,只能让它提供框架,逻辑本身必须自己写或逐行审查。

坑4:测试用例只测"快乐路径"

Claude Code 自动生成的测试用例,几乎清一色是正常输入、正常返回的场景。

边界条件——比如审批人 ID 为 null、审批记录已被其他人操作导致的并发冲突、会签场景下中途有人撤回——基本没有覆盖。

这些场景我后来自己补了将近 40 个测试用例。

教训: AI 生成测试用例之后,必须自己列出边界清单逐一补充。把异常场景描述清楚告诉它,它也能生成,但得你主动去问。

坑5:有一段代码我没看懂,却通过了 Review

Claude Code 用了一段比较巧妙的 CompletableFuture 链来优化并行通知逻辑。代码写得很优雅,同事 Review 时也说"没问题",我觉得"应该没问题"就合入了。

一周后,压测时发现在高并发场景下这段逻辑存在线程池耗尽的风险。回头再细看,才发现它用的是默认的 ForkJoinPool,没有做任何线程池隔离。

教训: 如果你自己看不懂 AI 生成的代码,就不应该合入。理解是你的责任,不是 AI 的。


四、我总结的人机协作分工边界

用下来,我大概总结出了这张表:

任务类型 建议做法 原因
添加注释 / 生成文档 ✅ 完全交给 AI 不涉及逻辑,风险低,省时间
重复性重构(消除重复代码) ✅ 完全交给 AI 有固定模式,AI 擅长
样板代码(DTO、Builder、测试框架) ✅ 完全交给 AI 规律性强,几乎无需修改
单元测试用例 ⚠️ AI 生成 + 人工补边界 正常路径 OK,边界需人工补充
外部 API 调用 / 第三方依赖 ⚠️ 必须人工验证 幻觉风险高,容易引用不存在的方法
核心业务逻辑 ❌ 只做参考,不直接使用 AI 不理解业务上下文
架构决策 ❌ 不要交给 AI 需要全局视野和业务判断
并发 / 安全相关代码 ❌ 必须人工实现 风险太高,不容出错

五、最终结果

重构耗时从预估的两周缩短到约 9 天,其中 Claude Code 帮我节省了大约 3 天的时间,主要集中在注释生成、样板代码和测试用例初稿上。

核心数据:

  • 代码行数:从 3.2 万行压缩到约 1.9 万行,删除了大量重复逻辑
  • 单元测试覆盖率:从 0% 提升到约 62%
  • 上线后首两周线上 Bug 数:比历史同期减少了约 60%

六、个人判断

Claude Code 确实让我提效了,但它没有让我变成一个更好的程序员——那部分还得靠自己。

它更像一个很快、不知疲倦、但偶尔会一本正经说错话的实习生。你得知道什么能交给他做、做完要检查哪里,而不是直接合并他的 PR。

AI 编程工具的价值,在于把你从重复性劳动里解放出来,让你把精力放在真正需要判断力的地方。但判断力本身,还是你自己的事。

最后问一句:你们在用 AI 编程工具重构项目时,遇到过什么更离谱的坑?评论区见。


本文为个人原创实战记录,首发于 CSDN。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐