用AI重构遗留系统:Java老代码的智能化改造实践
2026/10/6大约 15 分钟遗留系统代码重构AI辅助Java现代化
用AI重构遗留系统:Java老代码的智能化改造实践
开篇故事:林峰的"史前代码"
2025年底,某大型银行IT部门的高级工程师林峰接到了一个让他头皮发麻的任务:
他需要重构一个运行了17年的核心账务系统。这个系统:
- 共计42万行Java代码
- 原始开发者早已离职,没有任何架构文档
- 大量使用了2008年的API(已废弃)
- 包含3.2万行神秘的数字计算逻辑(没有注释)
- 每次修改都可能引发不可预知的连锁反应
- 最近一次改动(3年前),导致了一次¥2800万的账务差错
前任团队评估重构周期:4年,需要15人。
林峰用了不同的方法——他让AI来帮他理解这些代码:
第一步:用Claude分析关键模块,生成了300页自动化技术文档 第二步:用AI识别出了47个重构风险点和3个严重的技术债务 第三步:用AI辅助逐步重构,每次重构后通过AI生成的测试用例验证
最终结果:
- 重构周期:11个月(压缩到原估计的23%)
- 参与人数:5人(减少了67%)
- 账务准确率:重构后98天内零差错
- 代码行数:从42万行降低到18万行(减少57%)
这是AI辅助遗留系统重构的真实案例。本文将带你掌握这套方法论。
TL;DR
- AI理解老代码:用大模型生成模块文档、绘制调用关系、识别业务逻辑
- AI生成文档:从代码逆向生成架构图、接口规范、业务规则
- AI识别重构点:自动发现God Class、循环依赖、死代码、安全漏洞
- AI辅助生成新代码:基于理解的老代码生成现代化的新实现
- 安全验证策略:AI生成测试用例,确保新老代码行为一致
一、遗留系统重构的挑战
1.1 遗留系统的典型特征
典型的"遗留地狱"特征:
├── 文档缺失:代码即文档,而代码看不懂
├── 高耦合:一个类有5000行,修哪里都会影响全局
├── 魔法数字:充斥着 if (type == 3) { ... }
├── 无测试:没有任何单元测试
├── 版本混乱:Spring 3.x + Java 8 + 各种古老库
└── 知识孤岛:核心逻辑只有离职的张大爷知道1.2 传统重构 vs AI辅助重构
| 阶段 | 传统方法 | AI辅助方法 |
|---|---|---|
| 理解代码 | 人工阅读(数周) | AI分析+生成文档(数小时) |
| 发现问题 | 代码审查+经验 | AI静态分析+模式识别 |
| 编写新代码 | 纯手工 | AI生成初稿+人工审核 |
| 编写测试 | 纯手工,通常被跳过 | AI生成测试用例 |
| 验证一致性 | 手动对比 | AI分析差异 |
二、第一步:让AI读懂老代码
2.1 批量代码分析工具
// LegacyCodeAnalyzer.java
@Service
@Slf4j
public class LegacyCodeAnalyzer {
private final ChatClient analysisClient;
// 分析单个Java文件,生成功能摘要
public CodeAnalysisResult analyzeFile(Path filePath) throws IOException {
String code = Files.readString(filePath);
// 超长文件需要分块处理
if (code.length() > 50000) { // 50KB
return analyzeInChunks(filePath, code);
}
String analysisPrompt = buildAnalysisPrompt(code, filePath.getFileName().toString());
String analysis = analysisClient.prompt()
.system("""
你是一个资深Java架构师,专门分析遗留系统代码。
你的任务是理解代码并生成清晰的中文技术文档。
""")
.user(analysisPrompt)
.call()
.content();
return parseAnalysisResult(analysis, filePath);
}
private String buildAnalysisPrompt(String code, String fileName) {
return String.format("""
请分析以下Java文件 [%s],提供:
1. **功能摘要**(100字以内):这个类/接口的主要职责是什么?
2. **核心方法说明**(每个public方法):
- 方法名和参数
- 实现的业务逻辑
- 返回值含义
- 潜在的副作用
3. **依赖关系**:
- 依赖了哪些其他类?
- 被哪些类依赖(如果能从代码推断)?
4. **技术债务识别**:
- 代码坏味道(God Class/Long Method等)
- 已废弃的API使用
- 安全风险
- 性能隐患
5. **业务规则提取**:
- 关键的业务判断逻辑(if/switch中的业务规则)
- 重要的常量含义
- 计算公式的业务含义
代码内容:
```java
%s
```
请用Markdown格式输出分析结果。
""", fileName, code);
}
// 批量分析项目目录
public ProjectAnalysisReport analyzeProject(Path projectRoot) throws IOException {
List<Path> javaFiles = Files.walk(projectRoot)
.filter(p -> p.toString().endsWith(".java"))
.filter(p -> !p.toString().contains("/test/"))
.toList();
log.info("发现 {} 个Java文件,开始分析...", javaFiles.size());
Map<Path, CodeAnalysisResult> results = new ConcurrentHashMap<>();
ExecutorService executor = Executors.newFixedThreadPool(5);
List<Future<?>> futures = new ArrayList<>();
for (Path file : javaFiles) {
futures.add(executor.submit(() -> {
try {
CodeAnalysisResult result = analyzeFile(file);
results.put(file, result);
log.info("已分析: {}", file.getFileName());
} catch (Exception e) {
log.warn("分析失败 [{}]: {}", file, e.getMessage());
}
}));
}
futures.forEach(f -> {
try { f.get(300, TimeUnit.SECONDS); }
catch (Exception e) { log.warn("分析超时"); }
});
executor.shutdown();
// 生成项目级别报告
return buildProjectReport(results, javaFiles.size());
}
}2.2 调用关系图生成
// CallGraphGenerator.java
@Service
@Slf4j
public class CallGraphGenerator {
private final ChatClient graphClient;
// 从代码分析结果生成Mermaid格式的调用关系图
public String generateCallGraph(
Map<String, CodeAnalysisResult> analysisResults) {
// 收集所有依赖关系
StringBuilder dependencies = new StringBuilder();
for (Map.Entry<String, CodeAnalysisResult> entry : analysisResults.entrySet()) {
String className = entry.getKey();
CodeAnalysisResult result = entry.getValue();
for (String dep : result.getDependencies()) {
if (analysisResults.containsKey(dep)) {
dependencies.append(className).append(" → ").append(dep).append("\n");
}
}
}
String prompt = String.format("""
根据以下类依赖关系,生成Mermaid格式的架构图:
依赖关系:
%s
要求:
1. 使用flowchart TD格式
2. 将相关类分组到subgraph中
3. 标注核心类(连接数最多的)
4. 突出显示高耦合区域(红色)
只输出Mermaid代码,不要其他内容。
""", dependencies.toString());
return graphClient.prompt().user(prompt).call().content();
}
// 识别God Class(太复杂的类)
public List<TechDebt> identifyGodClasses(Map<String, CodeAnalysisResult> results) {
List<TechDebt> godClasses = new ArrayList<>();
for (Map.Entry<String, CodeAnalysisResult> entry : results.entrySet()) {
CodeAnalysisResult result = entry.getValue();
// 判断条件:方法数>20 OR LOC>500 OR 依赖数>15
if (result.getMethodCount() > 20 ||
result.getLineCount() > 500 ||
result.getDependencies().size() > 15) {
godClasses.add(TechDebt.builder()
.className(entry.getKey())
.type(TechDebtType.GOD_CLASS)
.severity(calculateSeverity(result))
.description(String.format(
"方法数: %d, 代码行数: %d, 依赖数: %d",
result.getMethodCount(),
result.getLineCount(),
result.getDependencies().size()))
.build());
}
}
return godClasses.stream()
.sorted(Comparator.comparing(TechDebt::getSeverity).reversed())
.toList();
}
}2.3 业务规则提取
// BusinessRuleExtractor.java
@Service
public class BusinessRuleExtractor {
private final ChatClient extractionClient;
// 从老代码中提取隐含的业务规则
public List<BusinessRule> extractRules(String code, String contextDescription) {
String prompt = String.format("""
以下是一段遗留系统的Java代码,该模块负责"%s"。
请提取其中所有的业务规则,以结构化方式描述每条规则:
代码:
```java
%s
```
返回JSON数组,每条规则包含:
{
"ruleId": "BR-001",
"name": "规则名称",
"condition": "触发条件(用自然语言描述)",
"action": "满足条件时执行的操作",
"dataElements": ["涉及的数据字段"],
"priority": "HIGH/MEDIUM/LOW",
"notes": "潜在的风险或注意事项"
}
特别注意:
- 魔法数字的含义(例如:if(type == 3)中的3代表什么?)
- 金融计算的精度规则
- 日期/时区处理逻辑
- 特殊的边界条件
""",
contextDescription, code);
String jsonResponse = extractionClient.prompt()
.system("你是金融业务分析师,专门解读代码中的业务规则。")
.user(prompt)
.call()
.content();
return parseBusinessRules(jsonResponse);
}
}三、第二步:生成重构计划
3.1 AI驱动的重构策略
// RefactoringPlanGenerator.java
@Service
@Slf4j
public class RefactoringPlanGenerator {
private final ChatClient plannerClient;
public RefactoringPlan generatePlan(
ProjectAnalysisReport analysis,
RefactoringGoals goals) {
String analysisJson = serializeToJson(analysis);
String goalsText = formatGoals(goals);
String planPrompt = String.format("""
基于以下遗留系统分析报告,生成详细的重构计划:
## 系统现状
%s
## 重构目标
%s
## 重构计划要求
1. **风险优先**:高风险模块先做最小改动(防御式重构)
2. **可验证性**:每步重构都要有可验证的测试
3. **小步快跑**:每次迭代不超过2周
4. **零停机**:重构不影响生产运行
请输出:
1. 分阶段重构计划(阶段划分、每阶段目标)
2. 每个模块的重构策略(原地重构/Strangler Fig/并行运行)
3. 主要风险点和缓解措施
4. 建议的技术选型(从Java 8迁移到哪个版本?引入哪些框架?)
5. 人员和时间估算
""",
analysisJson, goalsText);
String planText = plannerClient.prompt()
.system("""
你是一个有15年经验的Java架构师,
专门负责大型遗留系统的现代化重构。
提供实际可执行的方案,不要泛泛而谈。
""")
.user(planPrompt)
.call()
.content();
return parseRefactoringPlan(planText);
}
}四、第三步:AI辅助生成新代码
4.1 Strangler Fig模式:逐步替换
// StranglerFigCodeGenerator.java
@Service
@Slf4j
public class StranglerFigCodeGenerator {
private final ChatClient codegenClient;
// 为单个方法生成现代化的新实现
public GeneratedCode generateModernImplementation(
String oldMethodCode,
String businessRules,
String targetTechStack) {
String prompt = String.format("""
请将以下遗留Java方法重构为现代化的实现。
## 原始方法
```java
%s
```
## 业务规则(从代码中提取的)
%s
## 目标技术栈
%s
## 重构要求
1. **保持业务逻辑完全一致**:不要改变任何业务规则
2. **代码风格现代化**:使用Java 21特性(Record/Pattern Matching/Stream等)
3. **增加注释**:每个关键步骤添加业务注释
4. **异常处理**:用适当的自定义异常替代RuntimeException
5. **可测试性**:将外部依赖通过构造器注入
输出:
1. 新的Java代码(完整实现)
2. 与原方法的关键差异说明
3. 需要注意的风险点
""",
oldMethodCode, businessRules, targetTechStack);
String generatedCode = codegenClient.prompt()
.system("""
你是专业的Java重构专家。
确保生成的代码与原代码在功能上完全等价。
任何行为差异都必须明确说明。
""")
.user(prompt)
.call()
.content();
return parseGeneratedCode(generatedCode);
}
// 生成新旧接口的适配器(Strangler Fig模式的关键)
public String generateStranglerFacade(
String oldInterface,
String newImplementation) {
return codegenClient.prompt()
.user(String.format("""
生成一个Facade类,在新旧实现之间提供路由:
旧接口:%s
新实现:%s
Facade要求:
1. 默认调用旧实现
2. 通过Feature Flag控制是否切换到新实现
3. 新实现失败时自动fallback到旧实现
4. 记录新旧实现的结果差异(用于验证)
""", oldInterface, newImplementation))
.call()
.content();
}
}4.2 实战:账务计算模块重构
// 原始遗留代码示例(42万行中的一个方法,真实的代码坏味道)
public double calc(int a, int b, int c, double d, double e, String f) {
double r = 0;
if (a == 1) {
if (c == 0) {
r = d * 1.06;
} else if (c == 1) {
r = d * 1.03;
} else {
r = d;
}
} else if (a == 2) {
if (b > 365) {
r = d + e * 0.055 * b / 365;
} else {
r = d + e * 0.035 * b / 365;
}
if (f != null && f.startsWith("VIP")) {
r = r * 0.95;
}
}
return r;
}
// ============ AI分析后的理解 ============
// a: 账户类型(1=活期,2=定期)
// b: 存款天数
// c: 取款方式(0=柜台,1=ATM,2=网银)
// d: 本金
// e: 额度(定期账户)
// f: 用户等级
// 业务规则:
// - 活期账户:柜台手续费6%,ATM手续费3%,网银免费
// - 定期账户:>365天利率5.5%,≤365天利率3.5%
// - VIP用户:所有费用9.5折
// ============ AI生成的现代化重构代码 ============
public record WithdrawalCalculation(
AccountType accountType,
int holdingDays,
WithdrawalChannel channel,
BigDecimal principal,
BigDecimal amount,
String customerLevel
) {}
public enum AccountType { CURRENT, TERM }
public enum WithdrawalChannel { COUNTER, ATM, ONLINE_BANKING }
@Service
public class WithdrawalCalculationService {
// 手续费率常量(原本是魔法数字)
private static final BigDecimal COUNTER_FEE_RATE = new BigDecimal("0.06");
private static final BigDecimal ATM_FEE_RATE = new BigDecimal("0.03");
private static final BigDecimal ONLINE_BANKING_FEE_RATE = BigDecimal.ZERO;
private static final BigDecimal LONG_TERM_RATE = new BigDecimal("0.055");
private static final BigDecimal SHORT_TERM_RATE = new BigDecimal("0.035");
private static final BigDecimal VIP_DISCOUNT = new BigDecimal("0.95");
private static final int DAYS_IN_YEAR = 365;
private static final int LONG_TERM_THRESHOLD = 365;
/**
* 计算提款金额(含手续费/利息)
*
* @param calc 提款计算参数
* @return 实际到账金额(精确到分)
*/
public BigDecimal calculateWithdrawal(WithdrawalCalculation calc) {
BigDecimal result = switch (calc.accountType()) {
case CURRENT -> calculateCurrentAccountWithdrawal(
calc.principal(), calc.channel());
case TERM -> calculateTermAccountInterest(
calc.principal(), calc.amount(), calc.holdingDays());
};
// VIP折扣
if (isVipCustomer(calc.customerLevel())) {
result = result.multiply(VIP_DISCOUNT);
}
// 金融计算使用HALF_UP四舍五入,精确到分
return result.setScale(2, RoundingMode.HALF_UP);
}
private BigDecimal calculateCurrentAccountWithdrawal(
BigDecimal principal, WithdrawalChannel channel) {
BigDecimal feeRate = switch (channel) {
case COUNTER -> COUNTER_FEE_RATE;
case ATM -> ATM_FEE_RATE;
case ONLINE_BANKING -> ONLINE_BANKING_FEE_RATE;
};
BigDecimal fee = principal.multiply(feeRate);
return principal.add(fee); // 本金 + 手续费(注意:原代码 d * 1.06 等于本金+6%手续费)
}
private BigDecimal calculateTermAccountInterest(
BigDecimal principal, BigDecimal amount, int holdingDays) {
BigDecimal interestRate = holdingDays > LONG_TERM_THRESHOLD
? LONG_TERM_RATE : SHORT_TERM_RATE;
BigDecimal interest = amount
.multiply(interestRate)
.multiply(BigDecimal.valueOf(holdingDays))
.divide(BigDecimal.valueOf(DAYS_IN_YEAR), 10, RoundingMode.HALF_UP);
return principal.add(interest);
}
private boolean isVipCustomer(String customerLevel) {
return customerLevel != null && customerLevel.startsWith("VIP");
}
}五、第四步:AI生成测试用例验证
5.1 等价测试用例生成
// RefactoringTestGenerator.java
@Service
@Slf4j
public class RefactoringTestGenerator {
private final ChatClient testgenClient;
// 为重构前后的方法生成等价性测试
public String generateEquivalenceTests(
String oldCode, String newCode,
List<BusinessRule> businessRules) {
String prompt = String.format("""
为以下新旧两个方法生成JUnit 5测试用例,验证它们的行为完全一致。
## 旧代码
```java
%s
```
## 新代码
```java
%s
```
## 业务规则
%s
## 测试用例要求
1. **正常路径**:覆盖每条业务规则的正常情况
2. **边界值**:测试阈值边界(如holdingDays == 365 vs 366)
3. **空值/null处理**:测试空输入的行为
4. **精度测试**:金融计算需要测试小数精度
5. **总共至少15个测试用例**
每个测试用例:
1. 用同样的输入调用新旧两个方法
2. 断言两者输出完全一致
3. 添加描述性的@DisplayName
直接输出完整的Java测试类代码。
""",
oldCode, newCode, formatRules(businessRules));
return testgenClient.prompt()
.system("你是Java测试专家,为重构生成严格的等价性测试。")
.user(prompt)
.call()
.content();
}
// 生成差异检测工具(在生产环境并行运行新旧代码,比较结果)
public String generateShadowTestingCode(
String serviceInterface,
String oldImpl,
String newImpl) {
return testgenClient.prompt()
.user(String.format("""
生成一个Shadow Testing(影子测试)组件:
接口定义:%s
旧实现:%s
新实现:%s
影子测试要求:
1. 同时调用新旧实现
2. 记录所有结果差异到日志
3. 始终返回旧实现的结果(保证生产安全)
4. 如果结果一致,记录"MATCH";不一致记录"DIFF"
5. 异步执行新实现(不影响旧实现的响应时间)
6. 新实现异常时只记录,不影响主流程
""", serviceInterface, oldImpl, newImpl))
.call()
.content();
}
}5.2 Shadow Testing实现示例
// WithdrawalCalculationServiceShadow.java(AI生成的影子测试)
@Service
@Slf4j
public class WithdrawalCalculationServiceShadow {
private final OldWithdrawalCalculator oldCalculator;
private final WithdrawalCalculationService newCalculator;
private final ExecutorService shadowExecutor;
// 始终返回旧实现结果,异步比较新实现
public double calculate(int a, int b, int c, double d, double e, String f) {
// 主路径:调用旧实现
double oldResult = oldCalculator.calc(a, b, c, d, e, f);
// 影子路径:异步调用新实现并比较
shadowExecutor.execute(() -> {
try {
WithdrawalCalculation params = mapToNewParams(a, b, c, d, e, f);
BigDecimal newResult = newCalculator.calculateWithdrawal(params);
double newResultDouble = newResult.doubleValue();
double diff = Math.abs(oldResult - newResultDouble);
if (diff < 0.001) { // 允许浮点误差
log.debug("SHADOW MATCH: input=({},{},{},{},{},{}), result={}",
a, b, c, d, e, f, oldResult);
} else {
log.warn("SHADOW DIFF: input=({},{},{},{},{},{}), old={}, new={}, diff={}",
a, b, c, d, e, f, oldResult, newResultDouble, diff);
// 记录到差异数据库供分析
recordDifference(a, b, c, d, e, f, oldResult, newResultDouble);
}
} catch (Exception ex) {
log.error("影子测试异常: {}", ex.getMessage());
}
});
return oldResult; // 始终返回旧结果
}
// 差异分析定时任务
@Scheduled(cron = "0 0 9 * * *") // 每天9点分析
public void analyzeDifferences() {
List<ShadowTestDiff> diffs = diffRepository.findLast24Hours();
if (diffs.isEmpty()) {
log.info("影子测试:过去24小时无差异,新实现与旧实现完全一致!");
} else {
log.warn("影子测试:发现 {} 处差异,请人工核查", diffs.size());
notificationService.sendDiffAlert(diffs);
}
}
}六、重构工具链集成
6.1 完整的重构Pipeline
// LegacyModernizationPipeline.java
@Service
@Slf4j
public class LegacyModernizationPipeline {
private final LegacyCodeAnalyzer codeAnalyzer;
private final BusinessRuleExtractor ruleExtractor;
private final RefactoringPlanGenerator planGenerator;
private final StranglerFigCodeGenerator codeGenerator;
private final RefactoringTestGenerator testGenerator;
// 一键分析并生成重构建议
@Async
public CompletableFuture<ModernizationReport> analyze(Path projectRoot) {
log.info("开始遗留系统分析: {}", projectRoot);
// Step 1: 分析代码
ProjectAnalysisReport analysis = codeAnalyzer.analyzeProject(projectRoot);
// Step 2: 生成重构计划
RefactoringPlan plan = planGenerator.generatePlan(analysis,
RefactoringGoals.defaultGoals());
// Step 3: 识别技术债务热点(Top 10最需要重构的)
List<TechDebt> hotspots = identifyHotspots(analysis);
// Step 4: 为每个热点生成重构建议
Map<String, String> refactoringsuggestions = new HashMap<>();
for (TechDebt debt : hotspots.stream().limit(10).toList()) {
String suggestion = generateRefactoringSuggestion(debt, analysis);
refactoringsuggestions.put(debt.getClassName(), suggestion);
}
ModernizationReport report = ModernizationReport.builder()
.projectRoot(projectRoot.toString())
.totalFiles(analysis.getTotalFiles())
.totalLines(analysis.getTotalLines())
.techDebtCount(hotspots.size())
.topHotspots(hotspots.stream().limit(10).toList())
.refactoringPlan(plan)
.refactoringSuggestions(refactoringsuggestions)
.estimatedEffort(plan.getTotalEstimatedDays())
.generatedAt(LocalDateTime.now())
.build();
// 输出报告文件
generateHtmlReport(report, projectRoot.resolve("modernization-report.html"));
return CompletableFuture.completedFuture(report);
}
}七、常见问题 FAQ
Q1:AI生成的代码能直接用于生产吗?
A:不能直接用,必须经过:
- 资深工程师人工审查(重点检查业务逻辑是否一致)
- 完整的测试通过(AI生成的等价性测试 + 业务测试)
- 影子测试在生产并行运行至少2周无差异
- 小流量灰度上线,再逐步切换
Q2:上下文太长,无法把整个文件发给AI怎么办?
A:分治策略:
- 先分析方法签名和类结构(100行摘要)
- 对关键方法单独分析(200-500行为宜)
- 使用RAG将代码分析结果存入向量数据库,按需检索相关部分
- 对于特别长的方法,先让AI拆分成小函数,再逐一分析
Q3:如何处理AI无法理解的代码(如汇编调用/特殊算法)?
A:
- 先让AI识别"无法理解的区域",标注为"需要人工审查"
- 对这些区域保留原样不重构(只做外层包装)
- 联系原开发者(如果还在职)或查找相关专利/文档
- 实在理解不了的,通过大量黑盒测试用例来保护
Q4:重构如何保证不影响性能?
A:
- 在重构前后分别做性能基准测试(JMH)
- AI生成的现代化代码有时比老代码慢(如BigDecimal vs double)
- 性能敏感路径需要专门的性能回归测试
- 对关键计算路径,让AI生成性能优化版本
Q5:团队对AI辅助重构不信任怎么办?
A:建立信任的渐进步骤:
- 先只用AI做文档生成(最低风险)
- 再用AI生成测试用例(只是辅助,不替代人工)
- 再用AI做无业务逻辑的重构(重命名/提取方法)
- 最后才用AI生成新的业务逻辑实现 每一步都要展示AI的准确率和节省的工时数据。
八、总结
AI辅助遗留系统重构的价值主张:
| 阶段 | 传统方法 | AI辅助方法 | 效率提升 |
|---|---|---|---|
| 代码理解 | 1周/人 | 1天/AI | 5-7x |
| 文档生成 | 2周/人 | 2天/AI | 5x |
| 重构计划 | 3天/架构师 | 4小时/AI+审核 | 3x |
| 新代码生成 | 1天/方法 | 2小时/方法 | 4x |
| 测试用例 | 0(通常不写) | AI全覆盖 | ∞ |
林峰的案例不是偶然:AI在处理遗留代码上的能力超过很多人的预期。它不会被17年的代码历史和复杂度吓到,它只是耐心地一行一行阅读,然后告诉你"这段代码的意思是..."。
把这把钥匙交给你的团队,那些"史前代码"的迷宫,终于有了地图。
