重构手法中,很大一部分是对函数进行处理,问题大部分来源于Long Methods(过长函数)。
下面介绍一下针对在函数中使用到的重构手法
一、Extract Method(提炼函数)
1、定义
把一段代码从原先函数中提取出来,放进一个单独函数中,并让函数名称解释该函数的用途。
2、动机
有过长的函数或者一段需要注释才能让人理解用途的代码,这时候需要将这段代码放进一个独立函数中。
- 如果每个函数的粒度都很小,那么函数被复用的机会就更大;
- 使高层函数读起来就像一系列注释;
- 如果函数都是细粒度,那么函数的覆写就会更容易些。
3、做法
Extract Method最大的困难就是处理局部变量,包括传进源函数的参数和源函数所声明的临时变量,而临时变量则是其中一个主要的困难源头。
- 创造新函数,根据这个函数的意图命名;
- 将提炼出的代码从源函数复制到新建的目标函数中去;
- 查看提炼出来的代码是否引用了“作用域限于”源函数的变量;
- 若被提炼代码段,有局部变量被修改,看看是否可以将被提炼代码处理成一个查询,并将结果赋值给相关变量,或需要将临时变量消灭掉;
- 将被提炼代码段中需要读取的局部变量,当做参数传给目标函数;
- 处理完所有局部变量,在源函数中,将被提炼代码段替换为对目标函数的调用。
被提炼代码段:
case1:无局部变量->直接提炼
case2:有局部变量,被提炼代码段只是读取这些变量,并不修改->局部变量作为参数传给目标函数
case3:对局部变量赋值
- 变量只在被提炼代码中使用->将临时变量的声明移到被提炼代码段中,一起提炼
- 被提炼代码段之外的代码也使用了这个变量,之后未使用->直接在目标函数中修改;之后使用->目标函数返回该变量改变后的值
4、其他问题
如果返回的变量不止一个,安排多个函数,每个函数只返回一个值;
如果临时变量很多,会使提炼工作举步维艰,应该先减少临时变量,使用方法(Replace Temp with Query、Replace Method with Method Object)
二、Inline Method(内联函数)
1、定义
在函数调用点插入函数本体,然后移除该函数。
2、动机
- 内部代码和函数名称同样清晰易读
- 一群组织不甚合理的函数。可以将它们都内联到一个大型函数中,再从中提炼出组织合理的小型函数
3、做法
- 检查函数,确定不具有多态性:如果子类继承了这个函数,就不要将此函数内联,否则导致子类无法覆写
- 找出这个函数的所有被调用点,并替换为函数本体
- 删除该函数的定义
4、其他问题
对于递归调用、多返回点、内联至另一个对象中而该对象并无提供访问函数。。。对于这些复杂情况,不要使用这种重构手法。
三、Inline Temp(内联临时变量)
1、定义
将所有对该变量的引用动作,替换为对它赋值的那个表达式自身
2、动机
临时变量,只被一个表达式赋值一次,妨碍了其他重构手法。
3、做法
- 检查给临时变量赋值的语句,确保等号右边的表达式没有副作用
- 如果这个临时变量并未被声明为final,那就将它声明为final,检查该临时变量是否真的只被赋值一次
- 找到临时变量的所有引用点,替换为为临时变量赋值的表达式
- 删除临时变量的声明和赋值语句
四、Replace Temp with Query(以查询取代临时变量)
1、定义
以一个临时变量保存某一表达式的运算结果。将这个表达式提炼到一个独立函数中,将这个临时变量的所有引用点替换为对新函数的调用。此后,新函数就可被其他函数调用。
2、动机
- 临时变量是暂时的,只能在所属函数内使用,并且为了访问临时变量,需要写出更长的函数
- 局部变量会使代码难以被提炼,所以尽可能替换为查询式
3、做法
- 找出只被赋值一次的临时变量。如果被赋值多次,考虑使用Split Temporary Variable将它分割成多个变量
- 将临时变量声明为final,检查该临时变量是否真的只被赋值一次
- 将对该临时变量赋值等号的右侧提炼到一个独立函数中。 1、可先将函数声明为private,以后有更多类需要,再放开访问限制。2、确保该函数无副作用,不修改任何对象
4、其他问题
临时变量常常被用来保存循环中的累加信息,如果在一个循环中累加好几个值,存在好几个临时变量。这时候,应该针对每个累加值重复一遍循环,这样就可以将所有变量都替换为查询。
当然,这种手法可能会引起性能问题。和其他性能问题一样,现在不管它,因为十有八九根本不可能造成任何影响,如果真的是性能真的出了问题,可以在优化时解决,代码组织良好,你往往能够发现更有效的优化方案。
五、Introduce Explaining Variable(引入解释性变量)
1、定义
对于一个复杂表达式,可以将该复杂表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途
2、动机
- 复杂的表达式难以阅读,临时变量可以将表达式分解为比较容易管理的形式
- 可以将每个条件子句提炼出来,以一个良好命名的临时变量来解释对应条件子句的意义。在较长算法中,可以运用临时变量来解释每一步运算的意义
3、做法
- 声明一个final临时变量,将待分解的复杂表达式中的一部分动作的运算结果赋值给它
- 将表达式的运算结果这一部分,替换为上述临时变量
4、其他问题
一般情况会使用Extract Method,因为同一对象中的任何部分,都可以根据自己的需要取用这些提炼出来的函数。
在Extract Method需要花费更大工作量时,在需要处理一个拥有大量局部变量的算法时,使用Introduce Explaining Variable比较好。
六、Split Temporary Variable(分解临时变量)
1、定义
若某个临时变量被赋值超过一次,它既不是循环变量,也不被用于收集运算结果,针对每次赋值,创造一个独立、对应的临时变量
2、动机
赋值多次的临时变量,就意味着在函数中承担了一个以上的责任,会另代码阅读者糊涂,随意尽量每个变量只承担一个责任
3、做法
- 在待分解临时变量的声明及其第一次被赋值处,修改其名称
- 将新的临时变量申明为final
- 找到第二次赋值处,修改此前对该临时变量的引用点,让它们引用新的临时变量,并重新申明原先那个临时变量
- 重复上述过程
七、Rmove Assignments to Parameters(移除对参数的赋值)
1、定义
对参数赋值,以一个临时变量取代该参数的位置
2、动机
- 降低代码清晰度,混用了按值传递和引用传递这两种参数传递方式
- 按值传递,对参数的任何修改,都不会对调用端造成任何影响。但是按引用传递,可能会有问题
3、做法
- 建立一个临时变量,把待处理的参数值赋予它
- 将多有对此参数的引用点,全部替换为对此临时变量的引用
- 修改赋值语句,改为对新建的临时变量赋值
4、其他问题
java的按值传递和按引用传递
八、Replace Method with Method Object(以函数对象取代函数)
1、定义
有一个大型函数,对局部变量的使用使你无法采用Extract Method,将这个函数放进一个单独对象中,如此一来局部变量就成了对象内的字段。然后你可以在同一个对象中将这个大型函数分解为多个小型函数
2、动机
- 局部变量的存在会增加函数分解难度
- Replace Method with Method Object会将所有局部变量都变成函数对象的字段,然后就可以对这个新对象使用Extract Method创造储新函数,从而将大型函数拆解变短
3、做法
- 建立一个新类,根据待处理函数的用途,为这个类命名
- 在新类中建立一个final字段,用以保存原先大型函数所在的对象。针对原函数的每个临时变量和每个参数,在新类中建立一个对应的字段保存
- 在新类中建立一个构造函数,接收源对象及原函数的所有参数作为参数
- 在新类中建立一个compute()函数
- 将原函数的代码复制到compute()函数中,如果需要调用源对象的任何函数,通过源对象字段调用
九、Substitute Algorithm(替换算法)
1、定义
想要某个算法替换为另一个更清晰的算法,将函数本体替换为另一个算法
2、动机
- 如果做一件可以有更清晰的方式,就应该以比较清晰的方式取代复杂的方式
- 重构可以把一些复杂东西分解为比较简单的小块,删除整个算法,代之以比较简单的算法
3、做法
- 准备好另一个替换用的算法
- 执行上述新算法,调试,直到结果与原本结果相同