C#遗留代码重构:从A到Z
处理遗留代码是任何.NET开发者都无法回避的现实。旧版C#项目通常包含“意大利面条式代码”、魔法数字、庞大的类以及缺乏测试。与其从头重写所有内容(成本高且风险大),明智的重构可以逐步改进代码,降低技术债务。在本文中,我们将通过具体示例,逐步解析C#遗留代码的重构策略。
1. 重构准备:安全第一
重构遗留代码的首要规则是——不要破坏现有的功能。没有测试,任何更改都可能导致生产环境中的错误。因此,第一步是建立“安全网”。
1.1. 添加测试(表征测试)
如果没有测试,就需要编写它们。使用表征测试方法。其核心是:用不同的输入数据运行现有代码,并将当前行为记录为基准。
// 遗留方法示例public class LegacyCalculator{ public int CalculateTotal(int a, int b) { // 我们尚未完全理解的遗留代码 return a + b * 2; // 可疑的逻辑 }}
// 表征测试[Test]public void CalculateTotal_WhenCalled_ReturnsCurrentBehavior(){ var calc = new LegacyCalculator(); int result = calc.CalculateTotal(3, 4); // 将当前结果记录为预期值 Assert.AreEqual(11, result); // 3 + 4*2 = 11}编写此类测试后,您就可以放心地进行重构,因为测试会捕获回归问题。
1.2. 隔离依赖关系
遗留代码通常与数据库、文件系统或外部服务紧密耦合。使用提取接口和依赖注入(DI)进行隔离。
// 之前:紧密耦合public class OrderProcessor{ private readonly SqlDatabase _db = new SqlDatabase(); public void Process(Order order) { _db.Save(order); }}
// 之后:接口和DIpublic interface IOrderRepository{ void Save(Order order);}
public class OrderProcessor{ private readonly IOrderRepository _repository; public OrderProcessor(IOrderRepository repository) { _repository = repository; } public void Process(Order order) { _repository.Save(order); }}2. 重构C#遗留代码的核心技巧
准备工作完成后,就可以开始改进代码了。以下是在.NET项目中有效的关键技巧。
2.1. 提取方法
超过200行的巨型方法是遗留代码的典型特征。将它们拆分为更小、更易读的方法。
// 重构前public void ProcessInvoice(Invoice invoice){ // 50行折扣计算代码 decimal discount = 0; if (invoice.Total > 1000) discount = 0.1m; else if (invoice.Total > 500) discount = 0.05m; // ... 还有很多代码 // 30行发送邮件代码 // ... }
// 重构后public void ProcessInvoice(Invoice invoice){ decimal discount = CalculateDiscount(invoice.Total); SendInvoiceEmail(invoice);}
private decimal CalculateDiscount(decimal total){ if (total > 1000) return 0.1m; if (total > 500) return 0.05m; return 0;}
private void SendInvoiceEmail(Invoice invoice){ // 发送逻辑}2.2. 用常量替换魔法数字
魔法数字使代码难以理解。用常量替换它们。
// 糟糕的写法if (user.Role == 3) { /* admin */ }
// 好的写法public enum UserRole{ Guest = 1, User = 2, Admin = 3}
if (user.Role == (int)UserRole.Admin) { /* admin */ }