重构手法-重新组织函数

重构手法中,很大一部分是对函数进行处理,问题大部分来源于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、做法

  • 准备好另一个替换用的算法
  • 执行上述新算法,调试,直到结果与原本结果相同



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值