引言:当代码变成遗留代码
每个在大型公司工作的C#开发者,迟早都会遇到那些有5年、10年甚至15年历史的项目。这种代码被称为遗留代码。它能运行,能赚钱,但每次修改都伴随着风险。缺乏测试、紧密耦合(tight coupling)、数千行的巨型类、魔法字符串和复制粘贴——这就是遗留代码的现实。
简单的“凭感觉”重构在这里行不通。需要高级策略,才能在不破坏业务逻辑的情况下逐步改进代码。在本文中,我们将深入探讨4种用于高级别遗留C#(.NET)系统重构的关键方法。
1. “绞杀者无花果”策略:不停机替换模块
Strangler Fig(绞杀无花果)模式是逐步替换遗留组件的经典方法。其核心是:你不是一次性重写整个系统,而是逐步拦截对旧模块的调用,并将其重定向到新模块。
如何在C#中实现
最简单的方法是使用接口和工厂。假设你有一个在整个项目中使用的旧类OldOrderProcessor。
// 旧接口(已存在)public interface IOrderProcessor{ void Process(Order order);}
// 旧实现(遗留代码)public class OldOrderProcessor : IOrderProcessor{ public void Process(Order order) { // 运行多年但混乱的代码 }}创建一个新的实现和带有功能切换的工厂:
public class NewOrderProcessor : IOrderProcessor{ public void Process(Order order) { // 新的、干净的代码 }}
public class OrderProcessorFactory{ public IOrderProcessor Create() { // 示例:根据配置切换 if (ConfigurationManager.AppSettings["UseNewProcessor"] == "true") return new NewOrderProcessor(); return new OldOrderProcessor(); }}现在,你可以为5%的用户启用新处理器,监控错误,并逐步提高百分比。当旧代码不再需要时,将其删除。
2. 核心提取:通过依赖注入实现关注点分离
遗留代码常常受困于上帝类(God-classes)以及对数据库、文件系统、外部API的直接依赖。引入依赖注入(DI)是实现模块化的第一步。
实践示例
假设你有一个类ReportGenerator,它在内部创建SqlConnection:
public class ReportGenerator{ public void Generate() { using (var conn = new SqlConnection("...")) { // 查询 } }}没有真实的数据库就无法测试。重构后:
public interface IDataRepository{ IEnumerable<DataRow> GetReportData();}
public class ReportGenerator{ private readonly IDataRepository _repository;
public ReportGenerator(IDataRepository repository) { _repository = repository; }
public void Generate() { var data = _repository.GetReportData(); // 生成报告 }}现在,你可以在单元测试中将仓库替换为模拟对象。使用标准的.NET Core DI容器(Microsoft.Extensions.DependencyInjection)或Autofac来注入依赖。
3. 遗留代码测试:黄金主测试与特征测试
没有测试的重构就像没有备用伞的跳伞。对于遗留代码,特征测试(Characterization Tests)或黄金主测试(Golden Master Testing)方法非常理想。
工作原理
你运行