重构—改善既有代码的设计5——重新组织函数

问题源于:long method

  包含太多信息,而信息又被函数错综复杂的逻辑掩盖,不易鉴别。

  解决:

    extract method:一段代码提取出来,放进一个单独函数中

      最大困难:处理局部遍历。临时变量则是其中一个主要的困难源头

      解决:

        repalce temp with query:去掉所有可去掉的临时变量

        split temporary variable:使临时变量变得比较容易替换

        replace method with method object:临时变量太混乱,可分解哪怕最混乱的函数,代价则是引入一个新的类

        remove assignments to parameters:在函数内赋值给参数

    inline method:相反,将一个函数调用动作替换为该函数本体。

    substitute algorithm:引入更清晰的算法

1. extract method:提炼函数

  一段代码可以被组织在一起并独立出来

  针对:

    一个过长的函数

    一段需要注释,才能让人理解用途的代码

  简短、命名良好的函数:

    函数粒度小,被复用机会大

    高层函数读起来就像一系列注释

    函数的细粒度,覆写也更容易。

    只有能给小型函数很好地命名时,它们才能真正起作用=》需要在函数名称上下点功夫。

  函数的长度:关键在于函数名称、函数本体之间的语义距离。

    如果提炼可以强化代码的清晰度,那就去做。就算函数名称比提炼出来的代码还长,也无所谓。

  做法:

  • 创造一个新函数,根据函数的意图来命名:以“做什么”来命名,而不是以“怎样做”命名。

      即使想要提炼的代码非常简单(一条消息,一个函数调用),只要新函数的名称能够以更好方式昭示代码意图,也应该提炼它。如果想不出一个更有意义的名称,就别动

  • 将提炼出的代码从源函数复制到新建的目标函数中
  • 仔细检查提炼出的代码,看是否引用了“作用域限于源函数”的变量,包括局部变量、源函数参数
  • 检查被提炼的代码段,看看是否有任何局部变量的值被它改变。

      如果一个临时变量值被修改了,看是否可以将被提炼的代码段处理为一个查询,并将结果赋值给相关变量

      如果很难这样做,或被修改的变量不止一个,就不能仅仅将这段代码原封不动地提炼出来。使用 split temporary variable,再尝试提炼;或使用 replace temp with query 将临时变量消灭掉

  • 将被提炼代码段中需要读取的局部变量,当作参数传给目标函数
  • 处理完所有局部变量之后,进行编译
  • 在源函数中,将被提炼代码段替换为对目标函数的调试

      如果将任何临时变量移到目标函数中,请检查它们原本的声明式是否在被提炼代码段的外围。如果是,则可以删除这些声明式了

  • 编译、测试

 2.inline method:内联函数

3.inline temp:内联临时变量

  

  有一个临时变量,只被一个简单表达式赋值一次,妨碍了其他重构手法。

  解决:将所有对该变量的引用动作,替换为对它赋值的那个表达式自身

  情境:

    多半作为replace temp with query的一部分使用,所以真正的动机出现在后者那儿

    唯一单独使用inline temp,发现某个临时变量被赋予某个函数调用的返回值。一般这样的临时变量不会有任何危害,可以放心地把它留在那儿。如果这个临时变量妨碍了其他的重构手法,可以使用extract method内联化。

  做法:

    1.检查给临时变量赋值的语句,确保等号右边的表达式没有副作用

    2.如果临时变量未被声明为final,就将它声明为final,然后编译。(可以检查该临时变量是否真的只被赋值一次)

    3.找到该临时变量的所有引用点,将其替换为“为临时变量赋值”的表达式

    4.每次修改后,编译并测试

    5.修改完所有的引用点之后,删除该临时变量的声明、赋值语句

    6.编译、测试

4.replace temp with query:以查询取代临时变量

  以一个临时变量保存某一表达式的运算结果

  解决:将这个表达式提炼到一个独立函数中。将这个临时变量的所有引用点替换为对新函数的调用。此后,新函数就可被其他函数使用

  

  动机:

    临时变量的问题:它们是暂时的,而且只能在所属函数内使用。

    由于临时变量只在所属函数内可见,会驱使你写出更长的函数。

    如果将临时变量替换为一个查询,那么同一个类中的所有函数都将获得这份信息。有助于为此类编写更为清晰的代码

    replace temp with query 往往是运用extract method之前必不可少的一个步骤。局部变量会使代码难以被提炼,应尽可能将其替换为查询式

    简单:临时变量只被赋值一次,或赋值给临时变量的表达式不受其他条件影响

    复杂:需要先运用split temporary variable、separate query from modifier使情况变得简单一些,然后再替换临时变量。

    如果想替换的临时变量是用来收集结果的,需要将某些程序逻辑复制到查询函数去

  做法:

    如果某个临时变量被赋值超过一次,使用split temporary variable将其分割成多个变量

    确保提炼出的函数无副作用。即函数并不修改任何对象内容,如果有副作用,进行seperate query from modifler

    性能:不要担心性能问题,9/10不会有任何影响。真有影响,可以再优化时期解决。代码组织良好,往往可以发现更有效的优化方案,如果没有进行重构,好的优化方案就可能与你失之交臂。如果性能实在太糟,将临时变量放回去也是很容易的

5.introduce explaining variable:引入解释性变量

  有一个复杂的表达式

  将该表达式(或其中一部分)的结果放进一个临时变量,以此临时变量名称来解释表达式用途

  

  动机:

    表达式非常复杂,难以阅读。临时变量可以帮助将表达式分解为较为容易管理的形式

    条件逻辑中,特别有价值:将每个条件子句提炼出来,用良好命名的临时变量来解释对应条件子句的意义

    较长的算法中,用临时变量来解释每一步运算的意义

    不常用,尽量使用extract method来解释一段代码的意义。临时变量只有再所处的那个函数中才有意义,局限性较大,函数则可以在对象的整个声明周期都有用,且可被其他对象使用

    当局部变量使用extract method难以进行时,使用introduce explaining variable

  做法:

    如果被替换的这一部分在代码中重复出现,可以每一次一个,逐一替换

6.split temporary variable:分解临时变量

  某个临时变量被赋值超过一次,既不是循环变量,也不被用于收集计算结果。

  解决:针对每次赋值,创造一个独立、应对的临时变量

  

  动机:

    临时变量有各种不同用途,某些用途会很自然地导致临时变量被多次赋值。“循环变量”、“结果收集变量”

    临时变量用于保存一段冗长代码的运算结果,以便稍后使用。这种临时变量应只被赋值一次。对超过一次,意味着在函数中承担了一个以上的责任。

    如果临时变量承担了多个责任,应该被替换、分解为多个临时变量,每个变量只承担一个责任。否则会令代码阅读者糊涂

  做法:

    如果稍后的赋值语句【i=i+某表达式】。意味着是个“结果收集变量”=>不要分解它。“结果收集变量”的作用通常是累加、字符串接合、写入流、向集合添加元素

7.remove assignments to parameters:移除对参数的赋值

  代码对一个参数进行赋值。

  以一个临时变量取代该参数的位置。

  

  动机:

    对参数赋值,意味着改变参数,使其指向另一个对象的引用。

    如果在“被传入对象”身上进行操作,则不是问题

    使用“out 参数”的,可以不必遵循这条规则。但应尽力避免

    缺点:

    • 降低了代码的清晰度,混用了按值传递、按引用传递,这两种参数传递方式。

        按值传递,对参数的任何修改,不会对调用端造成任何影响;按引用传递,会产生影响

    • 在函数本体内,只以参数表示:被传递进来的东西,代码会清晰很多。此用法在所有语言中都表现出相同语义

    做法:

    • 不要对参数赋值:可使用remove assignments to parameters来避免
    • 如果代码是“按引用传递”的,请在调用端检查调用后是否还使用了这个参数
      • 要检查有多少个按引用传递的参数被赋值后又被使用
    • 请尽量以return方式返回一个值。如果需返回的值不止一个,看是否可把需返回的大堆数据变成一个单一对象,或干脆为每个返回值设计对应的一个独立函数
    • 可为参数加上final关键词,使之遵循“不对参数赋值”,这一惯例。
      • 不建议使用,对于提高函数清晰度没有太大的帮助。
      • 通常用在较长的函数中,帮助检查参数是否被修改

8.replace method with method object:以函数对象取代函数

  有一个大型函数,其中对局部变量的使用,使人无法采用extract method

  做法:

    将这个函数放进一个单独对象中,这样局部变量就成了对象内的字段。然后可以在同一个对象中,将这个大型函数分解为多个小型函数

  

  动机:

    小型函数优美动人。只要将相对对立的代码从大型函数中提炼出来,可以大大提高代码的可读性

    局部变量的存在会增加函数的分解难度。如果一个函数中局部变量泛滥成灾,想分解这个函数是非常困难的

    replace temp with query 可以帮助减轻这一负担。但有时候会发现根本无法拆解一个需要拆解的函数

    replace method with method object 将所有局部变量都变成函数对象的字段=》对这个新对象使用extract method 创造出新函数,从而将原来的大型函数拆解变短

  做法:

    建立一个新类,根据待处理函数的用途为此类命名

    在新类中建立一个final字段,用以保存原先大型函数所在的对象。即“源对象”。针对源函数的每个临时变量,每个参数在新类中建立一个对应的字段保存

    在新类中建立一个构造函数,接受源对象、源函数的所有参数作为参数

    在新类中建立一个compute()函数

    将原函数中的代码赋值到compute()函数,对需要调用源对象的任何函数,通过源对象字段调用

    编译

    将旧函数的函数本体替换为这样一条语句“创建上述新类的一个新对象,而后调用其中的compute()函数”

    所有的局部变量都变成了字段,可以任意分解这个大型函数,不必传递任何参数

9.substitute algorithm:替换算法

  把某个算法替换为另一个更清晰的算法

  将函数本体替换为另一个算法

  

  动机:

    解决问题有好几种方法,某些方法会比另一些简单,算法也是如此

    如果做一件事儿,可以有更清晰的方式,应该以比较清晰的方式取代复杂的方式  

    随着对问题有更多理解,往往发现在原先的做法之外,有更简单的解决方案,就需要改变原先的算法

    如果开始使用程序库,而其中提供的某些功能、特性与你自己的代码重复,则需要改变原先的算法

    “重构”可以将一些复杂的东西分解为较简单的小块,但有时必须壮士断腕,删掉整个算法,代之以较为简单的算法

    有时想要修改原先的算法,让其做一件与原先略有差异的事。可以先把原先的算法替换为一个较易修改的算法,后续的修改会轻松许多

    使用此项重构手法之前,先确定自己已经尽可能分解了原先函数。替换一个巨大、复杂的算法是非常困难的,只有先将它分解为较简单的小型函数,然后才可很有把握的进行算法替换工作

  做法:

    对于每个测试用例,分别以新旧两种算法执行,并观察两者结果是否相同。可以帮助看到哪个测试用例出现麻烦,以及出现了怎样的麻烦

  

  

        

    

  

  

   

  

原文地址:https://www.cnblogs.com/panpanwelcome/p/7507046.html