代码重构(四):条件表达式重构规则

继续更新有关重构的博客,前三篇是关于类、函数和数据的重构的博客,内容还算比较充实吧。今天继续更新,本篇博客的主题是关于条件表达式的重构规则。有时候在实现比较复杂的业务逻辑时,各类条件各类嵌套。若是处理很差的话,代码看上去会很是的糟糕,并且业务逻辑看上去会很是混乱。今天就经过一些重构规则来对条件表达式进行重构,让业务逻辑更为清晰,代码更以维护和扩展。git

今天博客中的代码示例依然是Swift班,在对条件表达式重构时也会提现出Swift的优雅之处,会用上Swift特有的语法及其特色,好比使用guard来取代if-let语句等。若是你的需求的业务逻辑及其复杂,那么妥善处理条件表达式尤其重要。由于对其妥善处理能够提升代码的可读性,以及提升代码的可维护性。说这么多仍是来些示例来的直观,下方会根据一些Demo来着重分享一些条件表达式的部分重构规则,固然今天博客中没有涵盖全部的条件表达式的重构规则,更详细的部分请参见经典的重构书籍。github

今天所分享的代码段也将会在github上进行分享,分享地址在本篇博文的后方。废话少说了,进入今天的主题。设计模式

一.Decompose Conditional(分解条件表达式)数组

顾名思义,分解条件表达式说白了,就是当你的条件表达式比较复杂时,你就能够对其进行拆分。通常拆分的规则为:经if后的复杂条件表达式进行提取,将其封装成函数。若是if与else语句块中的内容比较复杂,那么就将其提取,也封装成独立的函数,而后在相应的地方进行替换。函数

下方代码段就是咱们将要重构的代码段。由于本篇博客的主题是对条件表达式的重构,因此咱们要对象下方的if-else的代码块进行重构。至于下方代码片断中其余不规范以及须要重构的地方咱们暂且忽略。由于咱们本篇博客的主题是条件表达式的重构。接下来咱们就要对下方代码片断中的条件表达式进行分析了。由于下方这段代码毕竟是一个Demo,在这儿咱们能够作个假设,假设if后边的表达式比较复杂,而后在if语句块和else语句块中都有一些复杂的处理,代码看上去的大致样子以下所示。测试

    

 

基于对上述代码的结构的假设,接下来咱们将要对其进行重构。说白了,就是让将条件表达式中的比较复杂的模块进行拆分与提取。下方代码段就是咱们重构后的结构,就是将咱们假设比较复杂的模块进行封装,而后在条件表达式中使用函数进行替换。这样的话,在看条件表达式就比较清晰。固然,咱们这个Demo的条件表达式不够复杂,而且if和else的逻辑块所作的东西很少。不过咱们能够假设一下,若是在比较复杂的状况下,这种重构手法是比较实用的。具体的你们就看重构前与重构后的区别吧。spa

    

 

2、Consolidate Conditional Expression(合并条件表达式)设计

“合并条件表达式”这条规则也是比较好理解的,由于有时候会存在这样的状况,也就是一些条件表达式后的语句体执行的代码块相同。说白了也就是不一样的条件有着一样的返回结果。固然通常在你程序设计之初不会出现此问题,由于在咱们设计程序时,若是不一样的条件返回相同的结果,咱们确定会将其合并的。不过当你在多个版本迭代,多个需求要增长,或者在别人的代码上进行需求迭代的时候,该状况是颇有可能发生的。3d

说这么多,也许有些抽象,那么就直接看下方须要重构的Demo了。固然,下方的Demo中,咱们为了测试,其中的条件比较简单。咱们假设每一个条件表达式是在不一样的需求迭代中或者修改bug时添加的,从而形成了下方这种状况(固然下方的状况有些夸张,这也是为了突出要合并条件的状况)。对象

 

在上述夸张的Demo中一眼就能看出来如何进行重构了(在平常开发迭代中,由于业务逻辑的复杂性或者屡次迭代的缘由,每每不是那么一目了然)。接下来咱们就要对不一样条件,但返回相同结果的部分进行合并。下方就是咱们合并后的结果,重构手法就是讲不一样的条件表达式使用&&或者||等布尔运算进行合并。

   

合并后,若是条件比较复杂,那么咱们就可使用本片博客中的第一部分使用的重构规则进行再次重构。下方代码段是进行第二次重构,就是对比较复杂的表达式进行函数封装,具体以下所示。仍是那句话,Demo有些夸张,不过用来演示该重构规则也是不错的,思想就这个思想,具体在平常开发中的使用场景仍是须要进行琢磨和推敲的。

   

 

3、Consolidate Duplicate Conditional Fragments(合并重复的条件片断)

第二部分合并的是条件表达式,本部分是合并的是重复的条件片断。什么叫合并重复的条件片断呢?这种状况也是通常不会在设计程序之初所出现,可是随着时间的推移,项目不断迭代更新,或者需求变动和迭代更新等等,在项目后期维护时比较容易出现重复的条件片断。在开发中是比较忌讳重复的代码的,若是出现重复的代码,那么说明你的代码应该被重构了。   

下方代码片断中if与else中有着相同的语句,就是这个print语句。固然这个示例也是比较夸张的,可是足以说明问题。若是你在开发业务逻辑比较复杂的条件表达式时,要谨慎的检查一下有没有下方这种状况。也就是出现了重复的条件片断。这种状况在需求迭代或者变动中是及其容易出现的。固然下方只是咱们这儿列举的一个夸张的示例。

   

对于这个示例而言,咱们不难看出,去代码的重复化。将print语句移到条件以外。可是要学会触类旁通呢,重要的是重构手法和思想。在真正的项目中,若是你要提取重复的代码段通常还要结合着其余重构手法,好比将重复的部分先提取成一个独立的模块(独立的类或者方法),而后在条件中使用,最后再去重复话。这样一来,重构的思路就比较清晰了。虽然今天的示例比较简单,可是足以表达这个思路。下方是重构后的代码。若是你对下方代码看着不爽的话,彻底能够根据以前咱们介绍的重构手法“使用查询来替代临时变量”,将下方的代码继续重构,在本章博客中就不作过多介绍了。

    

 

4、Remove Control Flag(移除控制标记)

“移除控制标记”这一点仍是比较重要的,我平时在代码开发中有时候也会使用到标记变量,来标记一些事物的状态。使用标记变量最直观的感觉就是不易维护,不易理解。由于在需求变动或者迭代中,你还得维护这标记变量。若是维护一个标记变量简单的话,那么维护多个标记变量就没这么容易了。并且在你的程序中使用标记变量时,不易理解,而且会显得逻辑混乱。固然这是个人直观感觉,在写程序时,我尽可能会避免使用标记变量。

固然,下方又是一个有点夸张的例子,可是该例子能够说明问题。下方代码中咱们使用了一个flag标记变量,固然下方代码没有什么意义了。在平时开发中咱们会使用一些标记变量来标记一个或者一些数据的状态,或者一些控件的状态,再次为了简化示例,咱们就简单的引入了一个flag标记变量。下方代码不难理解,当i为20时,咱们就翻转标记变量的状态,而后if中的语句块就不被执行了。

虽然下方代码片断是我写的,可是我我的看着超级的不舒服。引入的这个flag增长了代码的逻辑复杂度,让代码变得不那么直观。我我的建议,在平时开发中尽可能的要少使用标记变量。不到万不得已,不要在你的代码中引入标记变量。若是有,尝试着去除标记变量。

   

标记变量通常是可使用其余语句进行替换的,可使用break、return、continue等等,这个要根据具体状况而定。总之,代码中有标记变量不是什么好的事情。下方代码段就是对上述代码去除标记变量的重构。重构后的代码以下所示,固然还有好多其余去除的方法,此处仅仅给出了一种。

   

 

5、Replace Nested Condition with Guard Clauses(以卫语句取代嵌套的条件)

条件表达式的嵌套是使人讨厌的东西。代码中有多层if-else嵌套会下降代码的可读性以及可维护性,若是此时在加上for循环等等其余逻辑语句,想一想均可怕。这种业务逻辑较强的代码要慎重对待。尽可能不要将if-else进行嵌套,由于嵌套的if-else确实很差理解,若是在出现bug时,更是很差定位bug。要记住,你写的代码不是给机器看的,而是给人看的,这一点很是重要。不光是代码编写规范,也尽可能不要使用理解起来比较费劲的语句来实现你的逻辑。

下方咱们将建立一种场景,人为的建立多个if嵌套的状况。下方的demo理解起来应该不难,第一个数组中存储的是第二个字典的key,第二个字典中存储的value是下一个字典也就是第三个字典的key,以此类推。将咱们在使用从相应的字典中取出的value作为key再次取值时,咱们要保证该值不为nil,因此咱们要进行if-let判断。if-let所表示的意思是在取值时,若是当前取出的值不为nil,那么就执行if后的语句体,若是为nil,那么就不执行。这样一来,就会出现多层if-let嵌套的状况。

固然,在一些业务逻辑比较复杂的需求中,嵌套的每层if后都跟着不一样的表达式,而不只仅是if-let。由于为了建立这个if嵌套的场景,再次咱们使用了if-let嵌套。这么多的if-let嵌套显然不是什么好的事情,因此咱们要对此重构。

   

若是多层if嵌套,会出现一种叫作“厄运金字塔”的现象,由于在if左边会出现一个三角号的空间。这可不是什么好的标志,这样的代码结构通常理解起来会比较困难,维护起来也不是那么的理想。因此下方咱们要对上述代码进行结构。要去除上面的嵌套模式,咱们能够将if后的条件进行翻转,根据具体需求再引入return、break、continue等卫语句。下方是讲条件进行翻转而后引入了continue语句,代码以下:

   

该部分的第二段代码要比第一段代码容易理解的多。通过条件翻转+continue,将上述嵌套的条件语句进行了拆分。拆分红了三个独立的if语句,虽然代码结构不一样,可是其实现功能都是同样的。不过上面的解决方案在Swift中并不完美。由于Swift语言是很是优雅的,Swift语言在设计的时候就考虑到了这种状况,因此在Swift 2.0时推出了guard语句。在这种状况下使用guard语句再合适不过了,下方代码段就是使用guard语句进行了重构。

使用guard let声明的变量与guard自己同在一个做用域,也就是说下方代码在guard let中声明的变量能够在for循环中直接使用。guard语句的用法就是若是guard 后方的赋值语句所取出的值为nil,那么就会执行else中的语句,不然就会继续往下执行。在else中通常是break、return、continue等卫语句。这种语法形式很好的对上述糟糕的形式进行了解决,并且还易于理解。

   

 

6、Replace Condition with Polymorphism(以多态取代条件表达式)

在介绍“以多态取代条件表达式”以前呢,首先要理解面向对象中多态是什么,也就是说多态是干吗的。顾明思议,多态就是类的不一样类型的对象有着不一样的行为状态。若是在你的条件表达式中条件是对象的类型,也就是根据对象的不一样类型而后作不一样的事情。在这种状况下使用多态在合适不过了。若是该部分在设计模式中,应该对应着状态模式这一部分。这就是以多态来取代条件表达式。

下方是一个比较简单的示例,这也正是咱们要进行重构的示例。在Book类中有三中类型,也就是咱们的书有三种,具体每种书是什么这不是该示例的重点。在Book类实例化时,须要为书的对象指定该书的类型(三种类型中的一种)。在Book类中,还有一个核心方法,那就是计算书的价格。在charge()函数中,根据不一样的书的种类,给出了不一样的价格。固然在Switch中的分支的计算方法在本例中很是简单,可是咱们要假设每一个分支的计算很是复杂,并且有着多行代码

在这种假设的状况下,下方的条件语句是很是糟糕的,由于庞大的业务逻辑增长了代码维护的成本。在这种状况下咱们就可使用多态来取代复杂的条件表达式。

     

 

 

若是想使用多态,引入其余类是必不可少的,并且每一个类中也必须有相应的对应关系。“以多态取代条件表达式”的作法的本质是将不一样状态的业务逻辑的处理的代码移到相应的类中。在本示例中,咱们要建立三种书籍的价格类,而且将上述case中的“复杂”计算移入到相应的书籍类中。由于每一个书籍价格中都会有相应的计算方法,也就是charge()方法,因此咱们为这三个书籍价格定义了一个协议(接口或者抽象类),在协议中就给出了charge()函数。而后咱们就能够将不一样种类的书籍实现该协议,在相应的方法中给出价格计算的代码。具体作法以下所示:

    

引入上述几个类后,在咱们的Book中就可使用多态了。在Book类中添加了一个price字段,这个字段的类型就是咱们的Price协议。也就是只要是符合咱们的Price协议的对象均可以。而后在Book中也添加了一个charge()方法,在Book中的charge方法作的一件事情就是调用price对象的charge方法。关键的是根据不一样的书籍类型建立不一样的书籍价格对象。这样一来,咱们就把每一个分支中的业务逻辑进行了分离,并使用了多态来获取价格。重构后的优势不言而喻。

   

 

今天关于“条件表达式的重构”的规则,固然这不是所有的,只是列举了一些常见的,并且常用重构规则。篇幅有限,今天的博客就先到这儿,还会继续更新其余的重构规则。

今天博客中的Demo在github上的分享地址为:https://github.com/lizelu/CodeRefactoring-Swift

相关文章
相关标签/搜索