主页

索引

模块索引

搜索页面

DRY principle

  • DRY: Don’t Repeat Yourself

三种代码重复的情况:

1. 实现逻辑重复
2. 功能语义重复
3. 代码执行重复

1. 实现逻辑重复,但功能语义不重复的代码,并不违反 DRY 原则
2. 实现逻辑不重复,但功能语义重复的代码,也算是违反 DRY 原则
3. 代码执行重复也算是违反 DRY 原则

备注

复用意识非常重要。在设计每个模块、类、函数的时候,要像设计一个外部 API 一样去思考它的复用性。

备注

我们在第一次写代码的时候,如果当下没有复用的需求,而未来的复用需求也不是特别明确,并且开发可复用代码的成本比较高,那我们就不需要考虑代码的复用性。在之后开发新的功能的时候,发现可以复用之前写的这段代码,那我们就重构这段代码,让其变得更加可复用。

实现逻辑重复

例1-实现逻辑重复:

public class UserAuthenticator {
  private boolean isValidUsername(String username) {
    // check not null, not empty
    if (StringUtils.isBlank(username)) {
      return false;
    }
    // check length: 4~64
    int length = username.length();
    if (length < 4 || length > 64) {
      return false;
    }
    ...
    return true;
  }
  private boolean isValidPassword(String password) {
    // check not null, not empty
    if (StringUtils.isBlank(password)) {
      return false;
    }
    // check length: 4~64
    int length = password.length();
    if (length < 4 || length > 64) {
      return false;
    }
    ...
    return true;
  }
}

说明:

有两处非常明显的重复的代码片段:isValidUserName () 函数和 isValidPassword () 函数。
重复的代码被敲了两遍,看起来明显违反 DRY 原则

例2-合并重复代码:

public class UserAuthenticatorV2 {
  public void authenticate(String userName, String password) {
    if (!isValidUsernameOrPassword(userName)) {
      // ...throw InvalidUsernameException...
    }
    if (!isValidUsernameOrPassword(password)) {
      // ...throw InvalidPasswordException...
    }
  }
  private boolean isValidUsernameOrPassword(String usernameOrPassword) {
    // 省略实现逻辑
    // 跟原来的 isValidUsername () 或 isValidPassword () 的实现逻辑一样...
    return true;
  }
}

说明:

合并之后的 isValidUserNameOrPassword () 函数,负责两件事情:
  验证用户名和验证密码,违反了 “单一职责原则” 和 “接口隔离原则”
虽然从代码实现逻辑上看起来是重复的,但是从语义上并不重复

尽管原始代码的实现逻辑是相同的,但语义不同,我们判定它并不违反 DRY 原则。
对于包含重复代码的问题,我们可以通过抽象成更细粒度函数的方式来解决。
比如将校验只包含 a~z、0~9、dot 的逻辑封装成 boolean onlyContains (String str, String charlist)

功能语义重复

例1:

public boolean isValidIp(String ipAddress) {
  if (StringUtils.isBlank(ipAddress)) return false;
  String regex = "^(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[1-9])\\."
          + "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\."
          + "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\."
          + "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)$";
  return ipAddress.matches(regex);
}
public boolean checkIfIpValid(String ipAddress) {
  if (StringUtils.isBlank(ipAddress)) return false;
  String[] ipUnits = StringUtils.split(ipAddress, '.');
  if (ipUnits.length != 4) {
    return false;
  }
  for (int i = 0; i < 4; ++i) {
    int ipUnitIntValue;
    try {
      ipUnitIntValue = Integer.parseInt(ipUnits[i]);
    } catch (NumberFormatException e) {
      return false;
    }
    if (ipUnitIntValue < 0 || ipUnitIntValue > 255) {
      return false;
    }
    if (i == 0 && ipUnitIntValue == 0) {
      return false;
    }
  }
  return true;
}

说明:

代码实现逻辑重复,但语义不重复,我们并不认为它违反了 DRY 原则

代码执行重复

例:

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...
  }
}

说明:

在 login () 函数中,email 的校验逻辑被执行了两次
login () 函数并不需要调用 checkIfUserExisted () 函数,只需要调用一次 getUserByEmail () 函数

代码重复执行也是一种重复,也违反 DRY 原则

参考

主页

索引

模块索引

搜索页面