在软件开发领域,代码复用始终是开发者们反复探讨的核心议题。无论是面试中被问及“如何避免重复代码”,还是日常工作中为提炼公共逻辑而费尽心思,这几乎已成为每位程序员的必修技能。合理运用代码复用,确实能大幅提升开发效率、降低后期维护成本,然而若不加节制,也容易陷入“过度复用”的陷阱。

正如园友@码农札记 所言:“复用是银弹,但银弹也会误伤队友。”
为何要追求代码复用?
在深入探讨具体方法之前,不妨先简要回顾一下代码复用的核心价值:
- 提升开发效率:借助已有成果,避免重复造轮子
- 降低维护成本:修复一处漏洞,所有使用处同时受益
- 增强代码质量:经过充分测试的复用模块通常更稳定可靠
- 保持行为一致:相同的业务逻辑不会在不同位置“走样”
常用的代码复用方式
函数 / 方法复用
这是最基础也最直接的复用形式,但在实现时也有讲究:
// 不推荐的做法:存在重复的验证逻辑
public void CreateUser(string email, string phone)
{
if (string.IsNullOrEmpty(email) || !email.Contains("@"))
throw new ArgumentException("邮箱格式错误");
// 业务逻辑...
}
public void UpdateUser(string email, string phone)
{
if (string.IsNullOrEmpty(email) || !email.Contains("@"))
throw new ArgumentException("邮箱格式错误");
// 业务逻辑...
}
// 推荐的做法:抽取统一验证函数
private void ValidateEmail(string email)
{
if (string.IsNullOrEmpty(email) || !email.Contains("@"))
throw new ArgumentException("邮箱格式错误");
}
public void CreateUser(string email, string phone)
{
ValidateEmail(email);
// 业务逻辑...
}
继承(Inheritance)
作为 OOP 三大特性之一,继承适合表示 is-a 关系。但需要特别警惕:继承属于强耦合机制,滥用容易导致“脆弱的基类”问题。在实践中应优先考虑组合而非继承。
public abstract class DataExporter
{
public abstract byte[] Export();
public void LogExport() { Console.WriteLine($"导出 {this.GetType().Name} 数据"); }
}
public class PdfExporter : DataExporter
{
public override byte[] Export()
{
// PDF 导出逻辑
}
}
组合(Composition)
更为灵活且推荐的复用方式。通过接口与依赖注入,可以自由组合不同的实现:
public interface ILogger
{
void Log(string message);
}
public class FileLogger : ILogger { /* 实现 */ }
public class DbLogger : ILogger { /* 实现 */ }
public class OrderService
{
private readonly ILogger _logger;
// 通过构造函数注入,灵活组合不同的日志实现
public OrderService(ILogger logger) { _logger = logger; }
}
泛型(Generics)
提供类型安全的复用,避免为不同数据类型重复编写相同逻辑。例如通用 Repository 接口:
// 无需为每个实体类型单独编写 Repository
public interface IRepository where T : class
{
T GetById(int id);
IEnumerable GetAll();
void Add(T entity);
}
模板方法模式
定义算法骨架,让子类实现具体步骤:
public abstract class DataProcessor
{
// 模板方法
public void Process()
{
LoadData();
ProcessData();
Sa veResult();
}
protected abstract void LoadData();
protected abstract void ProcessData();
protected virtual void Sa veResult() { }
}
复用的陷阱:何时不该复用?
这才是本文的重点。并非所有重复代码都值得复用,踩过坑的人对此深有体会。
陷阱一:过早复用
“Don't write code for tomorrow, write code for today.” 如果对未来的需求不确定,就不要轻易将两段“看起来相似”的代码强行合并。它们未来可能朝不同方向演化,强行复用只会让后续修改变得异常痛苦。
陷阱二:上下文耦合的复用
// 看似公共的验证方法
public void ValidateAndProcess(string input, bool isAdmin, bool isNewUser)
{
// 根据各种标志位执行不同的逻辑
// 天哪,这个方法内部已经充满了 if-else
}
当公共方法需要越来越多的“模式切换”参数时,说明它已经不再是真正的公共逻辑。一个方法依赖多个布尔值来区分行为,本身就是代码坏味道。
陷阱三:跨越边界的复用
不同模块、不同服务、不同限界上下文(bounded context)之间的复用必须格外谨慎。看似相同的“用户”概念,在订单上下文中与权限上下文中可能包含截然不同的业务规则。强行复用只会导致系统耦合度急剧攀升。
复用的原则与最佳实践
DRY 的正确理解
DRY(Don't Repeat Yourself)并非“不写重复代码”,而是“每一片知识在系统中只有单一、明确、权威的表达”。即使两段代码字面相同,若其代表不同的业务含义,就不应强行复用。就像两个人穿着同款衣服,但一个是因为工作需要,一个是因为个人喜好,不能因为穿着相同就把他们视为同一人。
复用三问
在抽取公共逻辑之前,先问自己三个问题:
- 变化方向是否一致? 如果未来修改的原因不同,就不要放在一起。
- 是否存在真正的业务共性? 还是仅仅目前巧合地相似?
- 复用的边界是否清晰? 公共模块的职责是否单一?
实践建议
| 场景 | 推荐方式 | 避免方式 |
|---|---|---|
| 通用工具函数 | 静态方法类 | 继承 |
| 业务逻辑复用 | 组合 / 依赖注入 | 深层继承链 |
| 跨项目复用 | NuGet / Package | 复制粘贴 |
| UI 组件 | 组件化(组合) | 多重继承 |
案例分享:一个过度复用的“受害者”
去年团队接手了一个遗留系统,发现一个名为 CommonHelper 的类竟然有 3000 多行代码。邮件发送、日志记录、数据校验、加密解密、Excel 导出……几乎所有业务都依赖它。结果每次修改这个类,都会引发一连串意想不到的问题。重构方案很简单:按职责拆分为 EmailService、LoggingService、ValidationService 等独立服务,并通过接口进行组合。经过两周的拆分,系统的可测试性与可维护性都得到了大幅提升。
教训深刻:复用 ≠ 大杂烩。一个类被过多地方依赖,恰恰说明它可能承担了过重的职责。
总结
代码复用是一把双刃剑,用好了事半功倍,用滥了反受其害。行业共识可归纳为以下几点:
- Rule of Three:同一段代码出现三次再考虑抽象
- 组合优于继承:保持低耦合
- 明确边界:不同上下文不要强行复用
- 保持简单:如果复用带来的复杂性超过了它的价值,那就别复用
最后,借用园友的一句话结束本文:
“代码复用的目标不是写更少的代码,而是让每行代码都更容易理解和维护。”
