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 原则适用性更强一些。可以不写可复用的代码,但一定不能写重复的代码。