天天看点

第二部分:理论七

第二部分:理论七

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”原则:第一次编写代码的时候,我们不考虑复用性;第二次遇到复用场景的时候,再进行重构使其复用。

继续阅读