一、熟悉常用的重构技巧

146 阅读12分钟

阅读《重构2》一书感觉收获颇多,最大的收获莫过于让自己意识到了,高质量的代码不是完成时,而只能是进行时,只要不断的有功能需求提出,代码就会自然的出现腐朽,想要维持高质量的代码,就需要我们在日常的开发中有意识地去优化重构,而不是等到系统一团乱泥后推倒重来。当然,作者在书中也例举了许多典型的代码重构技巧供我们参考,但更重要的是要理解这些重构技巧的使用场景,即懂得在何时何地去合理的应用重构。

本文记录了我对经典书籍《重构2》一书中重构方法的总结概括,方便以后的回顾与查找。

第六章:第一组重构

  1. 提炼函数:需要花时间弄懂的代码,就可以考虑提炼函数,并将其功能作为命名。
  2. 内联函数:移除不必要的间接性。也常在重构一群不合理函数时,先使用内联函数来梳理函数之间的关系。
  3. 提炼变量:用变量名来更好的表达代码意图。
  4. 内联变量:当变量名不比其本身更具有表达性,则内联掉它。
  5. 改变函数声明:使用更能表达意图的函数名称;使用更合理的函数参数。
  6. 封装变量:建议使用private修饰变量,使用getter、setter函数来访问变量,Java中通常都会这么做。
  7. 变量改名:使用更合适的变量名。
  8. 引入参数对象:封装有关联关系的参数,后续甚至能添加方法来丰满参数对象。
  9. 函数组合成类:封装一组具有关联关系的函数和其操作的数据。
  10. 函数组合成变换:当多个函数都是用于计算派生数据,那么就将这些函数放到一个数据变换函数中,便于更新计算逻辑和避免重复代码。
  11. 拆分阶段:明确步骤的边界,拆分成多个功能阶段,并且创建中转数据结构传递数据。

第七章:封装

  1. 封装记录:使用private来修饰类的可变数据字段,使用getter/setter方法来访问字段。使用final来修饰不可变字段。
  2. 封装集合:如果类中的集合属性内容不能被修改,则不要让集合的取值函数返回原始集合,可以考虑两种方式:一是返回集合的只读代理、二是返回集合的一个副本。
  3. 以对象取代基本类型:如果对某个数据的操作不局限于打印,则考虑将其用类来包装,并将操作代码封装为类的方法。
  4. 以查询取代临时变量:可以考虑将临时变量的计算抽取到函数中,封装变量的计算逻辑,便于复用,使程序结构更清晰,有助于后续的重构。
  5. 提炼类:存在过大的类,类负责的责任过多,就考虑提炼类来划分责任。
  6. 内联类:存在无用的类,通常是由于重构导致类萎缩,就使用内联类来消除无用的类。
  7. 隐藏委托关系:隐藏不必要的委托关系。好处是如果委托关系发生变化,不会影响到客户端。坏处是新增委托关系时,需要新增转发函数。
  8. 移除中间人:移除不必要的转发函数。
  9. 替换算法:用更好的算法替换旧算法,即持续优化代码。

第八章:搬移特性

  1. 搬移函数:如果函数不适合当前上下文(例如频繁引用其他上下文),则搬移到更合适的上下文。
  2. 搬移字段:如果字段不适合当前类,则搬移到更合适的类中。
  3. 搬移语句到函数:1、总是与函数一同出现的语句考虑搬移到函数中;2、某些语句与函数更像一个整体,则搬移语句到函数中。
  4. 搬移语句到调用者:当程序演进导致函数分化出多个关注点,则考虑将不合适的语句移动到调用者。
  5. 以函数调用取代内联代码:尽量使用库函数或自定义函数来封装代码,使代码更具有简洁性、可读性和复用性。
  6. 移动语句:将有关联的代码移动到一起,使代码更利于理解,也更有利于提炼函数。
  7. 拆分循环:让一个循环只做一个事情,有利于循环代码的修改,使代码结构更清晰,也有利于后续的重构。拆分后的循环常会被提炼成函数。
  8. 以管道取代循环:Java中使用Stream流来处理集合,使集合变换过程的代码可读性更强。
  9. 移除死代码:无用的代码会增加额外的阅读负担,请移除掉它。如果以后还需要可以从git中找回。

第九章:重新组织数据

  1. 拆分变量:若一个变量承担了两个责任,则拆分变量,来保证每个变量只承担一个责任。如果可能的话,将变量声明为不可修改(final)。
  2. 变量改名:数据结构中的字段名称对于理解程序有很大帮助,如果名称不合适,请修改它。
  3. 以查询取代派生变量:如果派生变量可以很容易的计算出来,那么就移除该变量,每次使用时都通过源数据计算来得到。这样可以防止源数据修改时,忘记修改派生变量,消除了可变性。
  4. 将引用对象改为值对象:值对象是不可变的,如果类中某个字段是值对象,修改该字段的内部属性时,就需要替换整个该字段对象。值对象通常更容易理解,也不用担心内部属性被修改。如果对象不需要被共享修改,那就尽量使用值对象。
  5. 将值对象改为引用对象:如果某些对象需要被共享,那就使用引用对象,便于修改和维护。

第十章:简化条件逻辑

  1. 分解条件表达式:对于复杂的if-else逻辑,可以将每个条件判断和条件分支提炼成函数以增强代码表达性。
  2. 合并条件表达式:一串条件检查逻辑中,出现条件各不相同,执行逻辑却相同的代码,可以将其合并。顺序执行的if语句用逻辑或来合并,嵌套执行的if语句用逻辑与来合并。
  3. 以卫语句取代嵌套条件表达式:对于特殊条件的处理,尽量使用卫语句形式,能更清楚的表达程序意图。
  4. 以多态取代条件表达式:两个典型场景可以使用多态(策略模式+模板模式)来优化代码:1、好几个地方出现相同条件的switch语句。2、有一个基础逻辑,在其之上又有一些变体。
  5. 引入特例:如果对某个数据结构经常做特殊字段的处理,并且处理逻辑相同,可以考虑创建一个特例元素,用以表达对这种特例的共用行为的处理,这样就可以用一个函数调用取代大部分特例检查逻辑。这种方式称作特例模式。
  6. 引入断言: 如果你发现代码假设某个条件始终为真,可以考虑加入一个断言明确说明这种情况。但是注意断言不能用来替代输入参数校验。

第十一章:重构API

  1. 将查询函数和修改函数分离:如果一个查询函数中含有看的到的副作用(比如修改操作或其他命令),尝试将查询操作和副作用分离开。
  2. 函数参数化:如果一组函数逻辑非常相似,只有一些字面量不同,可以将其合并为一个函数,并通过参数来传入不同的值。用以减少代码重复,提高函数复用性。
  3. 移除标记参数:识别并删除函数中的标记参数,用多个函数来替代标记参数的作用,使函数功能逻辑更清晰。标记参数是指用字面量值传入并且影响函数内部控制流的参数,如果传入的是程序中流动的数据的参数并不算作标记参数。
  4. 保持对象完整:有时候没有必要将对象中的属性取出作为多个参数传入,可以直接传入整个对象保持完整性。但有时也不想让被调函数依赖完整对象,尤其是两者不在同一模块中时。
  5. 以查询取代参数:如果函数的某个参数在函数自身中也很容易获得,那就可以在函数中获取而不必要传入该参数,以减少调用者负担。
  6. 以参数取代查询:如果需要让函数不再依赖某个元素,则可以把该元素作为参数传入。不过这样会增加函数调用者的复杂度。
  7. 移除设值函数:如果对象不可变,则不要提供设值函数,字段值只能在构造函数中初始化。
  8. 以工厂函数取代构造函数:构造方法具有一定的局限性,在必要的时候使用工厂方法替代构造方法。
  9. 以命令取代函数:有时候将函数封装成对象来操作(即命令对象),可以增强灵活性和表达能力。比如,借助命令对象,可以轻松地将原本复杂的函数拆解为多个方法,彼此之间通过字段共享状态。
  10. 以函数取代命令:命令对象所处理的逻辑过于简单时,考虑使用函数来替代。

第十二章:处理继承关系

  1. 函数上移:将不同子类中具有重复性的函数移到父类中。
  2. 字段上移:将不同子类中具有重复性的字段移到父类中。
  3. 构造函数本体上移:将不同子类构造函数中的重复性代码移动到父类构造函数中。
  4. 函数下移:如果超类中的某个函数只与一个(或少数几个)子类有关,那么最好将其放到真正关心它的子类中去。
  5. 字段下移:如果某个字段只被一个子类(或者一小部分子类)用到,就将其搬移到需要该字段的子类中。
  6. 以子类取代类型码:大多数时候,我们可以使用类型码来表现分类关系。但如果有几个函数都在根据类型码的取值采取不同的行为,那使用子类来取代类型码可能是更好的选择。而且当有些字段或函数只对特定的类型码取值才有意义时,子类的形式能更明确地表达数据与类型之间的关系。
  7. 移除子类:如果随着软件的演化,子类失去了价值,则移除子类并将其替换为超类中的一个字段。
  8. 提炼超类:两个类在做相似的事情,就可以利用继承机制将相似之处提炼到超类中。当然,我们是可以在程序编写前就设计好继承关系,但有时候这种继承关系是在程序演化过程中才浮现的。
  9. 折叠继承体系:随着继承体系的演化,当一个类与其超类已经没有多大差别时,就不值得作为独立的类存在,此时就需要把超类和子类合并。
  10. 以委托取代子类:继承有两个很明显的问题。一是只能处理一个方向上的变化;二是給类之间引入了紧密的关系,需要我们去充分理解子类与超类之间的关系,不然在超类上的修改很可能会破坏子类。而使用委托可以解决这两个问题。对于不同的变化原因,我们可以委托给不同的类;而且相比于继承,委托关系时接口更清晰,耦合更少。所以如果在使用继承中遇到了问题,那就运用委托来取代子类。
  11. 以委托取代超类:通过继承可以很方便的让子类复用超类的功能,但有时不合理的继承反而会导致混乱和错误,比如经典错误”stack继承list“,实际上stack只需要使用list的部分操作。而合理的继承有两个重要的特征,一是子类用得上超类的所有函数,二是通过超类的接口来使用子类的实例应该完全不出问题。所以面对不合理的继承,可以把继承关系改为将部分职能委托给另一个对象,通过这样的委托关系可以更好的表达”我只是需要另一个东西的部分功能“的含义。