重构遗留代码:安全策略与技术
遗留代码是没有测试的代码——无论其存在多久。
黄金法则:没有测试就不要重构
// 步骤1:编写表征测试(捕获当前行为)
test('UserProcessor.process - 现有行为', () => {
const processor = new UserProcessor(legacyDb);
const result = processor.process(testUser);
// 记录它做了什么,而不是它应该做什么
expect(result.status).toBe('processed');
expect(result.notificationCount).toBe(1);
});
// 步骤2:在测试的保护下进行重构
// 步骤3:验证测试仍然通过
绞杀者模式(Strangler Fig Pattern)
通过将新请求路由到新代码,逐步替换遗留系统。
// 遗留代码仍然存在
class LegacyPaymentProcessor {
process(order: any) { /* 旧实现 */ }
}
// 新代码并行增长
class ModernPaymentService {
async process(order: Order): Promise<PaymentResult> { /* 新实现 */ }
}
// 外观(Facade)在旧代码和新代码之间路由
class PaymentFacade {
constructor(
private legacy: LegacyPaymentProcessor,
private modern: ModernPaymentService,
private featureFlags: FeatureFlags
) {}
async process(order: Order) {
if (this.featureFlags.isEnabled('modern-payments')) {
return await this.modern.process(order);
}
return this.legacy.process(order);
}
}
提取接缝(Extract Seams)
找到可以在不修改代码的情况下改变行为的“接缝”。
// 难以测试:构造函数创建依赖
class ReportGenerator {
private emailer = new SmtpEmailer(); // 接缝被阻塞
generate(data: ReportData) {
const report = this.buildReport(data);
this.emailer.send(report);
}
}
// 通过构造函数注入暴露接缝
class ReportGenerator {
constructor(private emailer: Emailer) {} // 接缝暴露
generate(data: ReportData) {
const report = this.buildReport(data);
this.emailer.send(report);
}
}
// 现在可测试
const generator = new ReportGenerator(new MockEmailer());
增量改进清单
当处理遗留代码时:
1. 为即将修改的代码添加测试
2. 提取复杂块为方法
3. 重命名变量/方法使其更清晰
4. 删除死代码(先检查 git 历史)
5. 提取常量代替魔法数字
6. 简化嵌套条件
让营地比你发现时更干净。
打破依赖
// 依赖全局状态
class OrderService {
save(order: Order) {
Database.getInstance().save(order); // 全局单例
}
}
// 打破依赖
class OrderService {
constructor(private db: Database) {}
save(order: Order) {
this.db.save(order);
}
}
// 生产代码中
const service = new OrderService(Database.getInstance());
// 测试中
const service = new OrderService(new InMemoryDatabase());
大类的分解
// 上帝类,做所有事情
class UserManager {
register() { /* ... */ }
login() { /* ... */ }
sendEmail() { /* ... */ }
generateReport() { /* ... */ }
updateProfile() { /* ... */ }
checkPermissions() { /* ... */ }
}
// 按职责分解
class AuthService { register() {} login() {} }
class UserNotificationService { sendEmail() {} }
class UserReportService { generateReport() {} }
class UserProfileService { updateProfile() {} }
class AuthorizationService { checkPermissions() {} }
重构不是一次性的重写——它是持续的、测试驱动的改进。