Don’t Repeat Yourself,即“不要重复自己”。在编程中,可以理解为:不要写重复的代码。

重复的代码不一定违反 DRY 原则,而且有些看似不重复的代码也有可能违反 DRY 原则。

三种典型的代码重复情况:实现逻辑重复、功能语义重复、代码执行重复。

实现逻辑重复

尽管从代码实现逻辑上是重复的、相同的,但从语义上并不重复、不相同,则判定为不违反 DRY 原则。所谓“语义不重复”指的是:从功能上来看,两个函数干的是完全不重复的两件事,比如,两个均为校验函数,校验逻辑是一样的,但一个是校验用户名,另一个是校验密码。

对于包含重复代码的问题,可以通过抽象成更细粒度函数的方式来解决。

功能语义重复

就是说,尽管两个函数的命名不同,实现逻辑不同,但语义重复,也就是功能是相同的、重复的。则判定为违反 DRY 原则。在项目中,应该统一一种实现思路。

代码执行重复

代码中存在“执行重复”。比如,login() 登录函数中,校验逻辑对 email 的校验逻辑执行了两次:

public class UserService {
  private UserRepo userRepo;//通过依赖注入或者IOC框架注入

  public User login(String email, String password) {
    boolean existed = userRepo.checkIfUserExisted(email, password);
    if (!existed) {
      // ... throw AuthenticationFailureException...
    }
    User user = userRepo.getUserByEmail(email);
    return user;
  }
}

public class UserRepo {
  public boolean checkIfUserExisted(String email, String password) {
    if (!EmailValidation.validate(email)) {
      // ... throw InvalidEmailException...
    }
    if (!PasswordValidation.validate(password)) {
      // ... throw InvalidPasswordException...
    }
    //...query db to check if email&password exists...
  }

  public User getUserByEmail(String email) {
    if (!EmailValidation.validate(email)) {
      // ... throw InvalidEmailException...
    }
    //...query db to get user by email...
  }
}

也就是说,校验逻辑进行了多次数据库查询,而数据库这类的 I/O 操作是比较耗时的。我们在写代码的时候,应该尽量减少这类 I/O 操作。

代码复用性

代码复用性是评判代码质量的一个非常重要的标准。

什么是代码的复用性。

区分三个概念:代码复用性(Code Reusability)、代码复用(Code Resue)和 DRY 原则。

代码复用表示一种行为:在开发新功能的时候,尽量复用已经存在的代码。

代码的可复用性表示一段代码可被复用的特性或能力:在编写代码的时候,让代码尽量可复用。

DRY 原则是一条原则:不要写重复的代码。

首先,“不重复”并不代表“可复用”。在一个项目代码中,可能不存在任何重复的代码,但也并不表示里面有可复用的代码,不重复和可复用完全是两个概念

其次,“复用”和“可复用性”关注角度不同。代码“可复用性”是从代码开发者的角度来讲的,“复用”是从代码使用者的角度来讲的

尽管复用、可复用性、DRY 原则这三者从理解上有所区别,但实际上要达到的目的都是类似的,都是为了减少代码量,提高代码的可读性、可维护性。“复用”这个概念不仅可以指导细粒度的模块、类、函数的设计开发。

怎么提高代码的复用性?

1. 减少代码耦合

高度耦合的代码会影响到代码的复用性,要尽量减少代码耦合。

2. 满足单一职责原则

如果职责不够单一,模块、类设计得大而全,那依赖它的代码或者它依赖的代码就会比较多,进而增加了代码的耦合。根据上一点,也就会影响到代码的复用性。相反,越细粒度的代码,代码的通用性会越好,越容易被复用。

3. 模块化

这里的“模块”,不单单指一组类构成的模块,还可以理解为单个类、函数。要善于将功能独立的代码,封装成模块。

4. 业务与非业务逻辑分离

越是跟业务无关的代码越是容易复用,越是针对特定业务的代码越难复用。所以,为了复用跟业务无关的代码,将业务和非业务逻辑代码分离,抽取成一些通用的框架、类库、组件等。

5. 通用代码下沉

为了避免交叉调用导致调用关系混乱,一般只允许上层代码调用下层代码及同层代码之间的调用,杜绝下层代码调用上层代码。所以,通用的代码尽量下沉到更下层。

6. 继承、多态、抽象、封装

7. 应用模板等设计模式

一些设计模式,也能提高代码的复用性。比如,模板模式利用了多态来实现,可以灵活地替换其中的部分代码,整个流程模板代码可复用

辩证思考和灵活应用

实际上,除非有非常明确的复用需求,否则,为了暂时用不到的复用需求,花费太多的时间、精力,投入太多的开发成本,并不是一个值得推荐的做法。这也违反 YAGNI 原则。

有一个著名的原则,叫作“Rule of Three”。就是说,第一次编写代码的时候,如果当下没有复用的需求,而未来的复用需求也不是特别明确,并且开发可复用代码的成本比较高,那就不需要考虑复用性;第二次遇到复用场景的时候,再进行重构代码,让其变得更加可复用。需要注意的是,“Rule of Three”中的“Three”并不是真的就指确切的“三”,这里就是指“二”。

相比于代码的可复用性,DRY 原则适用性更强一些。可以不写可复用的代码,但一定不能写重复的代码。