第二部分:理论七
DRY 原则,英文描述为:Don’t Repeat Yourself。中文直译为:不要重复自己。将它应用在编程中,可以理解为:不要写重复的代码。
三种典型的代码重复情况,它们分别是:实现逻辑重复、功能语义重复和代码执行重复。
文中举例,用户身份验证类 UserAuthenticator,其中有两个方法:检查用户名 isValidUsername() 和密码 isValidPassword() 合法性。
其中 isValidUsername() 和 isValidPassword() 内部逻辑相同,分别检查了输入是否为空、字符串长度、是否小写、是否只包含合法字符。
以上两方法的内部代码完全一致,重复代码十分明显,此为实现逻辑重复。
但是没有违背 DRY 原则,也不可以合并成一个方法。因为两个方法的语义不重复,一个是校验用户名一个是校验密码,以后很可能校验逻辑不一致,不可合二为一。
此处代码的优化倒是可以将方法内部的逻辑做更细粒度的封装,将每种校验都封装成方法,然后分别在两个方法 isValidUsername() 和 isValidPassword() 中调用。
isValidUserName() 函数和 isValidPassword() 函数重复代码示例:
重构后的代码如下所示:
文中举例,两个判定 IP 地址是否合法的函数:isValidIp() 和 checkIfIpValid(),尽管两个函数的命名不同,实现逻辑不同,但功能是相同的,都是用来判定 IP 地址是否合法的。
之所以在同一个项目中会有两个功能相同的函数,那是因为这两个函数是由两个不同的同事开发的,后来者不知道有前者。
尽管两段代码的实现逻辑不重复,但语义重复,也就是功能重复,我们认为它违反了 DRY 原则:
项目中有的地方调用 isValidIp(),有的地方调用 checkIfIpValid(),代码既看起来奇怪又给后期维护的同事“埋坑”,增加阅读理解难度。同事觉得功能一样,又不敢轻易合并怕有什么高深的考量,浪费大家时间。
另外,如果以后判定 IP 地址是否合法的规则变了,我们只修改了其中一个方法,忘记了修改另一个方法或者压根不知道有另一个方法存在,导致出现一些莫名其妙的 bug。
用来校验 IP 地址是否合法的两个功能相同的函数:
优化前举例
类 UserRepo 中有方法:检查用户是否存在 checkIfUserExisted() 和根据邮箱获取用户信息 getUserByEmail()。
checkIfUserExisted() 中先校验用户邮箱,再校验用户密码
getUserByEmail() 中校验了用户邮箱
类 UserService 中 login() 方法用来校验用户登录是否成功。先调用了userRepo.checkIfUserExisted(),如果用户不存在则返回异常,如果用户存在调用userRepo.getUserByEmail() 返回用户信息。
问题分析
以上代码重复执行最明显的一个地方,就是在 login() 函数中,email 的校验逻辑被执行了两次。这个问题解决起来比较简单,我们只需要将校验逻辑从 UserRepo 中移除,统一放到 UserService 中就可以了。
另外,login() 函数并不需要调用 checkIfUserExisted() 函数,只需要调用一次 getUserByEmail() 函数,从数据库中获取到用户的 email、password 等信息,然后跟用户的输入对比即可。
以上这种减少数据库查询的优化,是十分有必要。
优化后整理
类 UserService 中 login() 方法中,先校验用户邮箱,再校验用户密码。然后调用userRepo.getUserByEmail() 返回用户信息,再与用户输入对比。
两处问题:第一处 login() 函数中 email 的校验逻辑被执行了两次,一次是在调用 checkIfUserExisted() 函数的时候,另一次是调用 getUserByEmail() 函数的时候。第二处 login() 函数并不需要调用 checkIfUserExisted() 函数,只需要调用一次getUserByEmail() 函数,代码示例如下:
区分三个概念
代码复用(Code Resue):表示一种行为,我们在开发新功能的时候,尽量复用已经存在的代码。
代码复用性(Code Reusability):表示一段代码可被复用的特性或能力,我们在编写代码的时候,让代码尽量可复用。
DRY 原则:是一条原则:不要写重复的代码。
总结三者区别
首先,“不重复”并不代表“可复用”。
其次,“复用”和“可复用性”关注角度不同。代码“可复用性”是从代码开发者的角度来讲的,“复用”是从代码使用者的角度来讲的。
减少代码耦合
满足单一职责原则
模块化
业务与非业务逻辑分离
通用代码下沉
继承、多态、抽象、封装
应用模板等设计模式
如果我们在编写代码的时候,已经有复用的需求场景,那可能还不算难,如果要编写未来某个功能可复用的代码就比较有挑战了。
为了暂时用不到的复用需求,花费太多的时间、精力,投入太多的开发成本。
Rule of Three”原则:第一次编写代码的时候,我们不考虑复用性;第二次遇到复用场景的时候,再进行重构使其复用。