第1796篇:遗留系统现代化的AI辅助——分析老代码并生成重构路线图
第1796篇:遗留系统现代化的AI辅助——分析老代码并生成重构路线图
「这块代码没人敢动」——这句话在多少团队里是日常。
我接手过一个运行了8年的Java项目,Spring 2.x,没有任何单元测试,有个Service类有3000行,注释里有大量「//TODO 后来再改」的内容,这些TODO的时间戳最早的是2015年。
最刺激的是,这套系统每天处理几十万笔交易,停机维护只有每周日凌晨2点到4点这2个小时窗口。改一行代码,谁也说不清楚会影响哪里。
遗留系统现代化是个真实的工程挑战,不是简单的「重写一遍」就能解决的。我见过太多「推倒重来」最后失败的项目:新系统做了两年,比老系统还不稳定,最后不了了之,新老系统同时维护,团队被拖垮。
这篇文章想聊的是:用AI帮你看懂老代码,然后制定一个能真正落地的渐进式重构路线图。
遗留系统现代化的几种错误姿势
先说几种我见过的失败模式,以免重蹈覆辙:
失败模式一:全部重写 「反正老代码那么烂,不如重写。」结果:新系统不知道老系统里藏了多少业务逻辑,漏了一堆,上线后故障不断。老代码里有些看似多余的判断,其实是当年填过的坑,没有文档,只有代码本身记得。
失败模式二:边跑边改 「就改这一个功能,不会有事的。」结果:牵一发动全身,改了A出了B的问题,改完B又出了C的问题,最后连做了什么都搞不清楚。
失败模式三:技术驱动而非业务驱动 「我们要上微服务/容器/响应式编程。」结果:团队学习成本极高,系统更复杂了,业务需求反而被耽误了。
正确的姿势应该是:先理解,再规划,小步快跑,有测试保护。AI在「先理解」这一步能提供极大帮助。
AI辅助理解老代码
老代码最大的问题是没有文档。代码本身就是唯一的真相,但理解它需要大量上下文。
代码理解提示工程
@Service
public class LegacyCodeAnalyzer {
private final ClaudeApiClient claudeClient;
public CodeUnderstandingResult understandCode(String sourceCode, String contextInfo) {
String prompt = String.format("""
你是一位代码考古专家,专门分析遗留系统代码。
背景信息:
%s
请分析以下代码,从业务角度理解它在做什么:
```java
%s
```
请提供:
## 1. 业务功能描述
用非技术语言描述这段代码实现了什么业务功能(假设你要向产品经理解释)
## 2. 核心逻辑流程
用有序步骤描述主要的业务流程
## 3. 关键业务规则
从代码中提取出隐含的业务规则(这些规则可能在需求文档里没有被明确写出来)
## 4. 边界情况处理
代码中处理了哪些特殊情况?这些情况可能有什么业务含义?
## 5. 依赖关系
这段代码依赖哪些外部系统/服务/数据?
## 6. 可能的隐藏bug
代码中有没有看起来有问题但不敢轻易改的地方?
## 7. 重构难度评估
如果要重写这段代码,需要注意哪些「坑」?
请诚实回答,如果代码逻辑不清晰,直接说「这段逻辑不透明,需要进一步确认」。
""", contextInfo, sourceCode);
String response = claudeClient.complete(prompt);
return parseUnderstandingResult(response);
}
public DependencyMap analyzeDependencies(List<String> sourceFiles) {
// 构建代码依赖图
DependencyMap dependencyMap = new DependencyMap();
for (String file : sourceFiles) {
try {
CompilationUnit cu = StaticJavaParser.parse(file);
String className = extractClassName(cu);
Set<String> dependencies = extractDependencies(cu);
dependencyMap.addNode(className, dependencies);
} catch (Exception e) {
log.warn("依赖分析失败: {}", e.getMessage());
}
}
// 检测循环依赖
List<List<String>> cycles = detectCycles(dependencyMap);
dependencyMap.setCycles(cycles);
return dependencyMap;
}
private Set<String> extractDependencies(CompilationUnit cu) {
Set<String> deps = new HashSet<>();
// 提取import
cu.getImports().forEach(imp -> {
if (!imp.isAsterisk() && !imp.isStatic()) {
String importName = imp.getNameAsString();
// 只关注项目内部类
if (importName.startsWith("com.yourcompany")) {
deps.add(importName.substring(importName.lastIndexOf('.') + 1));
}
}
});
// 提取字段注入(@Autowired)
cu.findAll(FieldDeclaration.class).forEach(field -> {
boolean isAutowired = field.getAnnotations().stream()
.anyMatch(a -> a.getNameAsString().equals("Autowired")
|| a.getNameAsString().equals("Resource"));
if (isAutowired) {
field.getVariables().forEach(v -> deps.add(v.getTypeAsString()));
}
});
return deps;
}
}批量分析代码库
@Service
public class LegacySystemMapper {
private final LegacyCodeAnalyzer analyzer;
private final ClaudeApiClient claudeClient;
public SystemMap buildSystemMap(Path codebasePath) {
SystemMap systemMap = new SystemMap();
// 第一步:识别系统边界
List<Path> sourceFiles = collectJavaFiles(codebasePath);
log.info("找到{}个Java文件", sourceFiles.size());
// 第二步:分类文件
Map<ComponentType, List<Path>> categorized = categorizeFiles(sourceFiles);
// 第三步:分析每个组件
for (Map.Entry<ComponentType, List<Path>> entry : categorized.entrySet()) {
List<ComponentInfo> components = entry.getValue().stream()
.map(file -> analyzeComponent(file, entry.getKey()))
.collect(Collectors.toList());
systemMap.addComponents(entry.getKey(), components);
}
// 第四步:构建依赖图
DependencyMap depMap = analyzer.analyzeDependencies(
sourceFiles.stream()
.map(this::readFile)
.collect(Collectors.toList()));
systemMap.setDependencyMap(depMap);
// 第五步:AI生成系统全景理解
String systemOverview = generateSystemOverview(systemMap);
systemMap.setOverview(systemOverview);
return systemMap;
}
private Map<ComponentType, List<Path>> categorizeFiles(List<Path> files) {
Map<ComponentType, List<Path>> result = new EnumMap<>(ComponentType.class);
for (Path file : files) {
String fileName = file.getFileName().toString();
String content;
try {
content = Files.readString(file);
} catch (IOException e) {
continue;
}
ComponentType type = detectComponentType(fileName, content);
result.computeIfAbsent(type, k -> new ArrayList<>()).add(file);
}
return result;
}
private ComponentType detectComponentType(String fileName, String content) {
if (content.contains("@Controller") || content.contains("@RestController")) {
return ComponentType.CONTROLLER;
} else if (content.contains("@Service")) {
return ComponentType.SERVICE;
} else if (content.contains("@Repository") || content.contains("extends JpaRepository")) {
return ComponentType.REPOSITORY;
} else if (content.contains("@Entity") || fileName.endsWith("DO.java")
|| fileName.endsWith("PO.java")) {
return ComponentType.ENTITY;
} else if (fileName.endsWith("Util.java") || fileName.endsWith("Utils.java")
|| fileName.endsWith("Helper.java")) {
return ComponentType.UTILITY;
}
return ComponentType.OTHER;
}
private String generateSystemOverview(SystemMap systemMap) {
String prompt = String.format("""
基于以下遗留系统的代码结构分析,请生成一份系统全景描述。
系统组件统计:
- Controller类:%d个
- Service类:%d个
- Repository类:%d个
- Entity类:%d个
Controller列表(部分):
%s
Service列表(部分):
%s
发现的循环依赖:
%s
请描述:
1. 这个系统可能是做什么业务的?
2. 系统有哪些主要的业务域?
3. 架构上有哪些明显的问题?
4. 重构的优先建议方向?
""",
systemMap.getCount(ComponentType.CONTROLLER),
systemMap.getCount(ComponentType.SERVICE),
systemMap.getCount(ComponentType.REPOSITORY),
systemMap.getCount(ComponentType.ENTITY),
formatComponentNames(systemMap.getComponents(ComponentType.CONTROLLER), 20),
formatComponentNames(systemMap.getComponents(ComponentType.SERVICE), 20),
systemMap.getDependencyMap().getCycles().isEmpty() ? "无" :
systemMap.getDependencyMap().getCycles().toString());
return claudeClient.complete(prompt);
}
}生成重构路线图
理解了系统之后,是生成路线图的关键步骤。
@Service
public class RefactoringRoadmapGenerator {
private final ClaudeApiClient claudeClient;
public RefactoringRoadmap generateRoadmap(
SystemMap systemMap,
RefactoringConstraints constraints) {
String prompt = buildRoadmapPrompt(systemMap, constraints);
String response = claudeClient.complete(prompt);
return parseRoadmap(response);
}
private String buildRoadmapPrompt(
SystemMap systemMap, RefactoringConstraints constraints) {
return String.format("""
你是一位经验丰富的系统架构师,专门做遗留系统现代化。
请基于以下信息,制定一个可落地的遗留系统重构路线图。
## 系统现状
%s
## 约束条件
- 团队规模:%d人
- 每周可用于重构的时间:%d天(扣除维护和新需求)
- 停机窗口:%s
- 不可停服要求:%s
- 目标架构:%s
- 时间预算:%s
## 已知的高风险模块
%s
## 路线图要求
请按以下维度规划:
### 阶段一:为重构做准备(建立安全网)
这个阶段不改任何业务逻辑,只是为后续重构创造条件
- 哪些关键路径需要先补测试
- 哪些外部依赖需要先梳理清楚
- 哪些配置需要外置
### 阶段二:降低风险(隔离高风险区域)
识别并隔离最危险的部分,让它变得可替换
- 建议先抽象哪些外部依赖
- 哪些God Class需要先拆分
- 哪些循环依赖需要先解开
### 阶段三:渐进迁移
基于绞杀者模式,新老并行,逐步迁移
- 建议从哪个模块开始
- 如何做新老系统的流量切换
- 如何验证迁移后功能一致性
### 阶段四:清理遗留代码
当新系统稳定后,安全删除旧代码
对每个阶段,请提供:
1. 具体的可执行任务列表
2. 预估工时
3. 成功指标
4. 风险和应对措施
请以JSON格式返回:
{
"phases": [
{
"phase_number": 1,
"name": "阶段名称",
"goal": "阶段目标",
"duration_weeks": 预估周数,
"tasks": [
{
"task": "任务描述",
"estimated_days": 预估天数,
"priority": "HIGH/MEDIUM/LOW",
"dependencies": ["依赖的前置任务"],
"success_criteria": "完成标准"
}
],
"risks": [
{
"risk": "风险描述",
"probability": "HIGH/MEDIUM/LOW",
"mitigation": "缓解措施"
}
],
"milestone": "里程碑描述"
}
],
"quick_wins": ["能在一周内完成且效果明显的改进"],
"no_go_zones": ["建议暂时不要碰的区域及原因"],
"success_metrics": ["整体重构成功的度量指标"]
}
""",
formatSystemMap(systemMap),
constraints.getTeamSize(),
constraints.getWeeklyRefactoringDays(),
constraints.getMaintenanceWindow(),
constraints.getZeroDowntimeRequirement() ? "必须零停机" : "可以有计划停机",
constraints.getTargetArchitecture(),
constraints.getTimeBudget(),
formatHighRiskModules(systemMap));
}
}绞杀者模式的实现
路线图确定之后,核心技术是绞杀者模式(Strangler Fig Pattern)——不是一次性重写,而是让新系统像绞杀植物一样逐渐替代老系统。
// 示例:对遗留服务用代理模式做流量切换
@Service
public class StranglerProxyService {
@Autowired
private LegacyOrderService legacyOrderService;
@Autowired
private NewOrderService newOrderService;
@Value("${migration.order.new-system-percentage:0}")
private int newSystemPercentage; // 0-100,新系统承担的流量比例
@Value("${migration.order.shadow-mode:false}")
private boolean shadowMode; // 影子模式:新老都执行,但只用老系统结果
private final Random random = new Random();
public OrderResult createOrder(CreateOrderRequest request) {
if (shadowMode) {
return executeShadowMode(request);
}
if (shouldRouteToNew()) {
return executeWithFallback(request);
}
return legacyOrderService.createOrder(request);
}
private OrderResult executeShadowMode(CreateOrderRequest request) {
// 老系统处理,新系统并行处理但结果不使用
OrderResult legacyResult = legacyOrderService.createOrder(request);
CompletableFuture.runAsync(() -> {
try {
OrderResult newResult = newOrderService.createOrder(request);
// 比较结果差异,记录到日志
compareAndLog(legacyResult, newResult, request);
} catch (Exception e) {
log.error("影子模式新系统执行失败", e);
}
});
return legacyResult;
}
private OrderResult executeWithFallback(CreateOrderRequest request) {
try {
OrderResult result = newOrderService.createOrder(request);
// 记录成功使用新系统
Metrics.counter("order.new_system.success").increment();
return result;
} catch (Exception e) {
log.error("新系统处理失败,自动降级到老系统", e);
Metrics.counter("order.new_system.fallback").increment();
return legacyOrderService.createOrder(request);
}
}
private boolean shouldRouteToNew() {
return random.nextInt(100) < newSystemPercentage;
}
private void compareAndLog(OrderResult legacy, OrderResult newResult,
CreateOrderRequest request) {
if (!legacy.getOrderId().equals(newResult.getOrderId())) {
log.warn("新老系统结果不一致!请求: {}, 老系统: {}, 新系统: {}",
request, legacy, newResult);
}
}
}通过 migration.order.new-system-percentage 这个配置项,可以在配置中心随时调整新老系统的流量比例:从0%(纯老系统)逐步增加到100%(纯新系统)。每次提升比例之前,先观察影子模式的日志,确认新老系统结果一致。
给遗留代码添加测试保护网
重构之前,先要给高风险路径补测试。AI可以帮你快速生成测试用例:
@Service
public class TestGeneratorService {
private final ClaudeApiClient claudeClient;
public String generateTests(String sourceCode, String className) {
String prompt = String.format("""
为以下遗留Java代码生成JUnit5测试用例。
这是遗留代码,可能有以下特点:
1. 存在复杂的if-else逻辑和边界情况
2. 可能有外部依赖需要Mock
3. 业务规则可能只在代码里体现,没有文档
代码:
```java
%s
```
请生成:
1. 测试类的基本结构(包含@ExtendWith等必要注解)
2. 覆盖主要业务场景的测试方法
3. 边界情况测试(null值、空集合、极端值)
4. 异常场景测试
5. 对每个测试方法写清楚「Given-When-Then」注释
重要提示:
- 如果代码逻辑中有不确定的业务规则,在测试的注释里标注「TODO: 需要和业务方确认」
- 对有副作用的操作使用Mock
- 测试方法名使用中文描述,让测试即文档
只返回完整的测试类代码,不要其他内容。
""", sourceCode);
return claudeClient.complete(prompt);
}
}一个真实的重构路线图案例
针对前面提到的8年老项目,AI给出的路线图摘要(精简版):
阶段一(4周):建立安全网
- 在3000行的OrderService上补集成测试(优先覆盖支付、退款、取消3个核心流程)
- 把散落在代码里的数据库连接字符串、第三方API密钥全部外置到配置中心
- 用日志追踪完整记录每个订单的状态流转,方便后续验证
阶段二(6周):解耦高风险区
- 把OrderService中的「发送通知」逻辑抽取出来(这部分代码最容易影响其他逻辑但被频繁修改)
- 解开Order和User之间的双向循环依赖
- 把历史遗留的「大对象」DTO拆分,减少字段污染
阶段三(12周):渐进迁移
- 从「退款查询」接口开始(只读,无副作用,最安全)
- 按照绞杀者模式,新老接口并行,先走10%流量验证
- 每2周提升一次新系统流量比例,直到100%
- 同样方式处理「订单创建」(最高风险,留到最后)
Quick Wins(马上能做):
- 删掉代码里15年前加的已被注释的代码块(没有任何风险,减少认知负担)
- 统一异常处理,防止业务异常被吞掉(2天内可完成)
- 把3个重复的「计算运费」工具方法合并(1天)
坚决不动的区域:
- 2016年加的那段「神奇」的库存扣减逻辑——注释里说「请勿修改此处,否则会有坑」,询问了当时的同事,他说已经不记得为什么这样写了,但改过一次出了大问题。先把这段逻辑用测试固定下来,等彻底理解后再动。
这个路线图最关键的价值是:它把「重构」这件大而虚的事,分解成了具体的、有优先级的、有成功标准的任务,让团队在日常迭代中可以持续推进,而不是需要单独拉一个「重构sprint」。
遗留系统现代化是一场马拉松,不是短跑。AI能帮你更快看清楚路,但跑还是要靠你自己。
