代码重构(一):函数重构规则

重构是项目做到 一定程度后必然要做的事情。代码重构,可以改善既有的代码设计,增强既有工程的可扩充、可维护性。随着项目需求的不断迭代,需求的不断更新,我们在项目中 所写的代码也在时时刻刻的在变化之中。在一次新的需求中,你添加了某些功能模块,但这些功能模块有可能在下一次需求中不在适用。或者你因为需求迭代与变 更,使你原有的方法或者类变得臃肿,以及各个模块或者层次之间耦合度增加。此时,你要考虑重构了。

重构,在《重构,改善既有代码的设计》这本经典的书中给出了定义,大概就是:在不改变代码对外的表现的情况下,修改代码的内部特征。说白了,就是我们的测试用例不变,然后我们对既有的代码的结构进行修改。重构在软件开发中是经常遇到的,也是非常重要的。在需求迭代,Debug,Code Review时,你都可以对你既有的代码进行重构。

在接下来的几篇博文中,我想与大家一块去窥探一下代码重构的美丽,学习一下代码重构的一些规则。当然在每个规则中都有小的Demo, 在本篇博客以及相关内容的博客是使用Swift语言实现的。当然,和设计模式相同,重构重要的是手法和思想,和使用什么样的语言关系不大。经典的重构书籍中是使用Java语言来实现的,如果你对PHP, Python等其他语言比较熟悉,完全可以使用这些语言来测试一些重构手法。

本篇博客的主题就是通过一些列的重构手法,对既有的需要重构的函数或者方法进行重 构。并且会将每个示例在GitHub上进行分享,感兴趣的小伙伴可以对其进行下载。有的小伙伴说了,我没有Mac,怎么对你写的Swift代码进行编译 呢?这个问题好解决,你可以看我之前发表的这篇博客《窥探Swift之使用Web浏览器编译Swift代码以及Swift中的泛型》。你可以将相关代码进行拷贝,在浏览器中观察结果。因为在线编译的网站是国外的,访问起来也许会有一些卡顿,不过是可以用的。好前面扯了这么多了,进入今天的主题。

目录:

一、将大函数按模块拆分成几个小的函数

二、将微不足道的小函数通过inline进行整合

三、将一些临时变量使用函数替换(以函数查询替代临时变量)

四、将复杂表达式拆分成多个表达式(以多个解释性临时变量拆分复杂表达式)

五、将在不同语义下具有不同含义的临时行变量进行拆分

六、移除对函数参数的赋值(引入另一个临时性变量)

七、以对象取代函数

一、Extract Method(提取函数)-------将大函数按模块拆分成几个小的函数

Extract Method被翻 译成中文就是提取函数的意思,这一点在代码重构中用的非常非常的多。在重构时提倡将代码模块进行细分,因为模块越小,可重用度就越大。不要写大函数,如果 你的函数过大,那么这意味着你的函数需要重构了。因为函数过大,可维护性,可理解性就会变差。并且当你实现类似功能的时候就容易产生重复代码。写代码时, 最忌讳的就是代码重复。这也就是经常所说的DRY(Don`t Repeat Yourself)原则。所以当函数过长时,你需要将其细分,将原函数拆分成几个函数。

下 方将会通过一个示例来直观的感受一下Extract Method,当然这些示例不是我原创的,是《重构:改善既有代码的设计》中Java示例演变的Swift版,在写Swift代码时,对原有的示例进行了 一些修改,算是伪原创吧。不过目的只有一个:希望与大家交流分享。实在是没有必要再找其他的例子说明这些重构规则,因为《重构:改善既有的代码的设计》这 本书真的是太经典了。

1.需要重构的代码如下所示。下方代码中的MyCustomer类中有两个常量属性,并且该类提供了一个构造器。该类还提供了一个输出方法,就是第一该类中的属性进行打印说明,其实该类中没有什么功能。

  

在写好需要重构的类后,我们要为该类写一个测试用例。这便于在我们重构时对重构的
正确性进行验证,因为每次重构后都要去执行该测试用例,以保证我们重构是正确的。下方截图就是为上方示例写的测试用例以及该测试用例的打印结果。当然重构
后我们也需要调用该测试用例,并观察打印结果是否与之前的一致。当然如果你不想自己观察,你可以为上面的类添加相应的单元测试,这也是在常规项目中经常使
用的。至于如果添加测试用例,我们会在后面的博客中进行详细介绍。下方就是上述类的测试用例和输出结果:

2.接下来我们对上面类中的printOwning函数进行分析。上述类可以正常
工作,这是肯定的。但是printOwning()函数写的不够好。因为它干了太多的事情,也就是说该函数包括了其他子模块,需要对其进行拆分。由上面截
图中的红框可以看出,每个红框就代表着一个独立的功能模块,就说明这一块代码可以被拆分成独立的函数。在拆分子函数时,我们要为该函数起一个与改代码块功
能相符合的名字。也就是说当你看到该函数名字时,你就应该知道该函数是干嘛的。

下方代码段就是我们重构后的类。说白的,就是对函数中可以进行独立的模块进行提
取,并为提取的新的函数命一个合适的名称。经过重构后printOwing()函数就只有两行代码,这样看其中调用的函数名也是很容易理解其作用的。下方
拆分出来的三个函数也是一个独立的模块,因为函数短小,所以易于理解,同时也易于重用。经过Extract
Method,当然好处是多多的。经过重构后的代码,我在调用上述的测试用例,输出结果和原代码是一直的,如果不一致的话,那么说明你的重构有问题呢,需
要你进行Debug。

二. Inline Method ---- 内联函数:将微不足道的小函数进行整合

看过《周易》的小伙伴应该都知道,《周易》所表达的思想有一点就是“物极必反”。
《周易》中的六十四卦中的每一卦的“上九”(第六位的阳爻)或者“上六”(第六位的阴爻)都是物极必反的表现。其实《周易》其实就是计算机科学中二进制的
表象,因为太极生两仪(2进制中的2),两仪生四象(2的平方为4),四象生八卦(4 x 2 =
8),八卦有演变出六十四卦。六十四卦的就是2进制中的0-1排列。九五至尊,九六就物极必反了。wo kao,
扯远了,言归正传,当然这提到《周易》不是说利用周易如何去算卦,如何去预测,本宝宝不信这东西。不过《周易》中的哲学还是很有必要学习一下的。有所取,
有所不取。

回到本博客的主题,Inline Method其实是和Extract
Method相对的。当你在重构或者平时编程时,对模块进行了过度的封装,也就是使用Extract
Method有点过头了,把过于简单的东西进行了封装,比如一个简单的布尔表达式,而且该表达式只被用过一次。此时就是过度的使用Extract
Method的表现了。物极必反,所以我们需要使用Inline
Method进行中和,将过度封装的函数在放回去,或者将那些没有必要封装的函数放回去。也就是Extract Method相反的做法。

至于Inline Method规则的示例呢,在此就不做过多的赘述了,因为只需要你将Extract Method的示例进行翻转即可。

三.Replace Temp with Query----以查询取代临时变量: 将一些临时变量使用函数替代

1.Replace Temp with
Query说白了就是将那些有着复杂表达式赋值并且多次使用的临时变量使用查询函数取代,也就是说该临时变量的值是通过函数的返回值来获取的。这样一来在
实现类似功能的函数时,这些复杂的临时变量就可以进行复用,从而减少代码的重复率。下方就是Replace Temp with
Query规则的一个特定Demo,接下来我们要对getPrice()函数使用Replace Temp with Query规则进行重构。

对上面的小的demo创建对应的测试用例是少不了的,因为我们要根据测试用例还测试我重构后的代码是否一致,下方截图就是该代码的测试用例以及输出结果,具体如下所示。

2.接下来就是对Procut类中的getPrice()函数进行分析并重构了。在getPrice()函数中的第一个红框中有一个
basePrice临时常量,该常量有一个较为复杂的赋值表达式,我们可以对其使用Replace Temp with
Query进行重构,可就是创建一个函数来返回该表达式的值。第二个红框中的discountFactor临时变量被多次使用,我们可以对其通过
Replace Temp with Query规则进行重构,具体重构后的代码如下所示。

由重构后的代码容易看出,上面我们提到的临时常量或者变量都不存在了,取而代之的是两个查询方法,对应的查询方法返回的就是之前消除的临时变量或常量的值。

四、Inline Temp ---内联临时变量:与上面的Replace Temp with Query相反

当临时变量只被一个简单的表达式赋值,而该临时变量妨碍了其他重构手法。此时我们就不应
该使用Replace Temp with Query。之所以有时我们会使用到Inline Temp规则,是因为Replace Temp with
Query规则使用过度造成的情况,还是物极必反,使用Replace Temp with Query过度时,就需要使用Inline
Temp进行修正,当然Inline Temp的示例与Replace Temp with Query正好相反,在此就不做过多的赘述了。

五、Introduce Explaining Variable---引入解释性变量:将复杂的表达式拆分成多个变量

当一个函数中有一个比较复杂的表达式时,我们可以将
表达式根据功能拆分成不同的变量。拆分后的表达式要比之前未拆分的表达式的可读性更高。将表达式拆分成相应的临时变量,也就是Introduce
Explaining Variable,如果临时变量被多次使用的话,我们还可以尝试着使用Replace Temp with
Query规则去除临时变量,也就是将临时变量换成查询函数。

1.在下方Product类中的getPrice()方法中返回了一个比较长的表达式,第一眼看这个函数感觉会非常的不舒服。因为它返回的表达式太长了,而且可读性不太好。在这种情况下就很有必要将该表达式进行拆分。

2.接下来就可以使用Introduce Explaining
Variable规则,引入解释性变量。顾名思义,我们引入的变量是为了解释该表达式中的一部分的功能的,目的在于让该表达式具有更好的可读性。使用
Introduce Explaining Variable规则,就相当于为该表达式添加上相应的注释。下方截图就是使用 Introduce
Explaining Variable规则进行重构后的结果。

3.引入临时变量是为了更好的可读性,如果临时变量所代表的表达式多次使用,我们就可以对上述函数在此使用Replace Temp with
Query规则进行重构。也就是去除经常使用而且表达式比较复杂的临时变量,下方代码段是对上述函数进行Replace Temp with
Query重构,去掉临时变量,再次重构后的结果如下所示。

六、Split Temporary Variable-----分解临时变量:一心不可二用

什么叫分解临时变量的,具体说来就是在一个函数中一个临时变量不能做两种事
情,也就是一个临时变量不能赋上不同意义的值。如果你这么做了,那么对不起,请对该重复使用的临时变量进行分解,也就是说你需要创建一个新的临时变量来接
收第二次分配给第一个临时变量的值,并为第二个临时变量命一个确切的名字。

下方第一个函数是重构前的,可以看出temp被重复的赋值了两次的值,如果
这两个值关系不大,而且temp不足以对两个值的意思进行说明。那么就说明该段代码就应该被重构了。当然,重构的做法也是非常简单的,只需要术业有专攻即
可,各司其职,并且为每个临时变量命一个合适的名字即可。具体做法如下所示。

 

七、Remove Assignments to Parameters----移除对参数的赋值

“移除对参数的赋值”是什么意思呢?顾名思义,就是在函数中不要对函数参数
进行赋值。也就是说你在函数的作用域中不要对函数的参数进行赋值(当然,输入输出参数除外),当直接对函数的参数进行修改时,对不起,此时你应该对此重
构。因为这样会是参数的原始值丢失,我们需要引入临时变量,然后对这个临时变量进行操作。

1.下方这个discount()函数就做的不好,因为在
discount()函数中直接对非inout参数inputVal进行了修改并且返回了,我们不建议这样做。遇到这种情况,我们需要使用Remove
Assignments to Parameters规则对下方的函数进行重构。

2.当然重构的手法也特别简单,就是需要将上面的inputVal使用函数的临时变量进行替代即可,下方就是重构后的函数。

八.Replace Method with Method Object----以函数对象取代函数

当一个特别长的函数,而且函数中含有比较复杂的临时变量,使用上述那些方法
不好进行重构时,我们就要考虑将该函数封装成一个类了。这个对应的类的对象就是函数对象。我们可以将该场函数中的参数以及临时变量转变成类的属性,函数要
做的事情作为类的方法。将函数转变成函数类后,我们就可以使用上述的某些方法对新的类中的函数进行重构了。具体做法请看下方示例。

1.下方示例中的discount函数有过多的参数(当然,现实项目工程中参数比这个还要多),并函数中含有多个临时变量,假设函数功能比较复杂,而且比较长。下方示例对该函数使用上述那些规则进行重构会比较复杂,此时我们就可以将该函数抽象成一个类。

2.重构的第一步就是将上述discount()函数抽象成Discount类。在Discount类中有六个属性,这六个属性分别对应着
discount()函数的不同参数。除了添加参数属性外,我们在函数类提取时还添加了一个Account的委托代理对象。该委托代理对象是为了在
Discount类中访问Account类中依赖的数据,下方是第一次重构后的代码。

3.接着,我们就可以在新的Discount类中的compute()方法中使用
我们上述介绍的规则进行重构。对compute()方法进行分析,我们发现importandValue等属性是可以通过Replace Temp
with Qurey 规则进行消除的。所为我们可以再次对上述方法进行重构,重构后的具体代码如下:

今天的博客主要讲了如何对既有代码中的函数进行重构,在本篇博客中提到了8大规
则。这8大规则在函数代码重构时时非常实用的,并且也是非常重要的。还是那句话,虽然代码是使用Swift语言实现的,但是代码重构的手法和思想和语言无
关。接下来还会继续更新关于代码重构的博客,敬请期待吧。

时间: 2024-08-03 23:23:51

代码重构(一):函数重构规则的相关文章

重构摘要2_重构原则

何谓重构 对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提交其可理解性,降低其修改成本. 重构的目的是软件更容易理解和修改: 重构不会改变软件可观察的行为. 两顶帽子比喻 添加新功能 不修改既有代码,只管添加新功能,并通过测试 重构 不添加功能,只管改进程序结构 为何重构 重构改进软件设计 改进的重要方向就是消除重复代码. 重构使软件更容易理解 准确说出我所要的 利用重构来协助我理解不熟悉的代码 随着代码渐趋简洁,发现可以看到一些以前看不到的设计层面的东西. 重构帮助找到BUG

重构摘要5_重构列表

寻找引用点,很多重构都要求你找到对于某个函数.字段或某个类的所有引用点. 使用编译器查找注意的问题 覆写多次的函数.继承 编译器太慢 使用了反射 重构的基本技巧--小步前进.频繁测试 说明 <重构-改善既有代码的设计>Martin Fowler 摘要: 第五章 重构列表 重构摘要5_重构列表

【Java重构系列】重构31式之搬移方法

重构第二式:搬移方法 (Refactoring 2: Move Method) 毋容置疑,搬移方法(Move Method)应该是最常用的重构手段之一,正因为太常用而且较为简单,以至于很多人并不认为它是一种很有价值的重构,但事实并非如此,在最初的代码诞生之后,有些方法可能会被放在一些不合适的地方,例如,一个方法被其他类使用比在它所在的类中的使用还要频繁或者一个方法本身就不应该放在某个类中时,我们应该考虑将它移到更合适的地方.搬移方法,顾名思义就是将方法搬移至合适的位置,如将方法搬移到更频繁地使用

PCL系列——三维重构之泊松重构

PCL系列 PCL系列--读入PCD格式文件操作 PCL系列--将点云数据写入PCD格式文件 PCL系列--拼接两个点云 PCL系列--从深度图像(RangeImage)中提取NARF关键点 PCL系列--如何可视化深度图像 PCL系列--如何使用迭代最近点法(ICP)配准 PCL系列--如何逐渐地配准一对点云 PCL系列--三维重构之泊松重构 PCL系列--三维重构之贪婪三角投影算法 PCL系列--三维重构之移动立方体算法 说明 通过本教程,我们将会学会: 如果通过泊松算法进行三维点云重构.

【Java重构系列】重构31式之封装集合

2009年,Sean Chambers在其博客中发表了31 Days of Refactoring: Useful refactoring techniques you have to know系列文章,每天发布一篇,介绍一种重构手段,连续发文31篇,故得名“重构三十一天:你应该掌握的重构手段”.此外,Sean Chambers还将这31篇文章[即31种重构手段]整理成一本电子书, 以下是博客原文链接和电子书下载地址: 博客原文:http://lostechies.com/seanchamber

第五章 代码重用与函数编写(1)

****************************** 第五章 代码重用与函数编写 ********************************* 代码重用的好处:使用require()和include()函数:函数介绍:定义函数:使用参数:理解作用域: 返回值:参数的引用调用和值调用:实现递归:使用命名空间 *************** 5.1 代码重用的好处 1.成本低:2.可靠性:3.一致性:系统的外部接口是一致的,其中包括用户接口和系统的外部接口. *************

自定义属性之图片切换实例——代码简化、函数合并——JS学习笔记2015-5-30(第43天)

鉴于for循环的重要性,今天再来回顾下什么时候想到使用for循环: 1.重复执行某些代码:2.每次执行的时候有个数字在变化: 说道代码简化,函数合并 这里要去观察自己的代码,当发现自己写的代码,在功能上存在相似的代码段时,看看他们能不能合并 也就是函数的使用思想,就是被用来重复调用:让程序的整体代码变得简洁: 和合并的过程中,注意调试效果,看看有没有影响到原来效果的执行: 1 <!DOCTYPE HTML> 2 <html> 3 <head> 4 <meta ht

PHP代码重用与函数编写

代码重用与函数编写 1.使用require()和include()函数 这两个函数的作用是将一个文件爱你载入到PHP脚本中,这样就可以直接调用这个文件中的方法.require()和include()几乎是一样的,唯一的区别就是函数失败后前者给出一个致命错误,后者给出一个警告变体:require_once()和include_once()确保一个包含的文件只能被引入一次,多用这个 2.在PHP中使用函数 2.1调用函数 如果一个函数已经被定义了,且该函数在这个脚本里面,则可以直接调用,类似调用函数

C++代码注释行和函数个数统计

问题来源,在14年的暑假的一次小项目当中遇到了一个这样的问题,要求统计C++代码的注释行数,有效代码行数,代码注释公共行数,以及函数个数. 下面稍微解释一下问题, 1)注释行数:指有注释的行,包括有代码和注释的公共行(如:3,4,15,22...) 2)有效代码行:指有代码的行,包括有代码和注释的公共行(如:1,4,11,15,25....) 3)代码注释公共行:指又有代码又有注释的行(如:4,15...) 4)函数个数:这个不用说明了吧. 以下为注释情况展示代码: 1 #include <st