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 原则