zoukankan      html  css  js  c++  java
  • 重构—改善既有代码的设计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:替换算法

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

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

      

      动机:

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

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

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

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

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

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

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

      做法:

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

      

      

            

        

      

      

       

      

  • 相关阅读:
    生产环境Crontab专业实例
    Linux系统定时任务介绍
    Linux文件属性改变命令chown-chgrp-chattr-lsattr实践
    Linux命令总结
    Linux特殊权限位suid、sgid深度详细及实践
    企业场景-网站目录安全权限深度讲解及umask知识
    shell简介
    Nginx模块及配置虚拟主机
    安装Nginx
    Nginx简介
  • 原文地址:https://www.cnblogs.com/panpanwelcome/p/7507046.html
Copyright © 2011-2022 走看看