阅读《重构2》一书感觉收获颇多,最大的收获莫过于让自己意识到了,高质量的代码不是完成时,而只能是进行时,只要不断的有功能需求提出,代码就会自然的出现腐朽,想要维持高质量的代码,就需要我们在日常的开发中有意识地去优化重构,而不是等到系统一团乱泥后推倒重来。当然,作者在书中也例举了许多典型的代码重构技巧供我们参考,但更重要的是要理解这些重构技巧的使用场景,即懂得在何时何地去合理的应用重构。
本文记录了我对经典书籍《重构2》一书中重构方法的总结概括,方便以后的回顾与查找。
第六章:第一组重构
提炼函数
:需要花时间弄懂的代码,就可以考虑提炼函数,并将其功能作为命名。内联函数
:移除不必要的间接性。也常在重构一群不合理函数时,先使用内联函数来梳理函数之间的关系。提炼变量
:用变量名来更好的表达代码意图。内联变量
:当变量名不比其本身更具有表达性,则内联掉它。改变函数声明
:使用更能表达意图的函数名称;使用更合理的函数参数。封装变量
:建议使用private修饰变量,使用getter、setter函数来访问变量,Java中通常都会这么做。变量改名
:使用更合适的变量名。引入参数对象
:封装有关联关系的参数,后续甚至能添加方法来丰满参数对象。函数组合成类
:封装一组具有关联关系的函数和其操作的数据。函数组合成变换
:当多个函数都是用于计算派生数据,那么就将这些函数放到一个数据变换函数中,便于更新计算逻辑和避免重复代码。拆分阶段
:明确步骤的边界,拆分成多个功能阶段,并且创建中转数据结构传递数据。
第七章:封装
封装记录
:使用private来修饰类的可变数据字段,使用getter/setter方法来访问字段。使用final来修饰不可变字段。封装集合
:如果类中的集合属性内容不能被修改,则不要让集合的取值函数返回原始集合,可以考虑两种方式:一是返回集合的只读代理、二是返回集合的一个副本。以对象取代基本类型
:如果对某个数据的操作不局限于打印,则考虑将其用类来包装,并将操作代码封装为类的方法。以查询取代临时变量
:可以考虑将临时变量的计算抽取到函数中,封装变量的计算逻辑,便于复用,使程序结构更清晰,有助于后续的重构。提炼类
:存在过大的类,类负责的责任过多,就考虑提炼类来划分责任。内联类
:存在无用的类,通常是由于重构导致类萎缩,就使用内联类来消除无用的类。隐藏委托关系
:隐藏不必要的委托关系。好处是如果委托关系发生变化,不会影响到客户端。坏处是新增委托关系时,需要新增转发函数。移除中间人
:移除不必要的转发函数。替换算法
:用更好的算法替换旧算法,即持续优化代码。
第八章:搬移特性
搬移函数
:如果函数不适合当前上下文(例如频繁引用其他上下文),则搬移到更合适的上下文。搬移字段
:如果字段不适合当前类,则搬移到更合适的类中。搬移语句到函数
:1、总是与函数一同出现的语句考虑搬移到函数中;2、某些语句与函数更像一个整体,则搬移语句到函数中。搬移语句到调用者
:当程序演进导致函数分化出多个关注点,则考虑将不合适的语句移动到调用者。以函数调用取代内联代码
:尽量使用库函数或自定义函数来封装代码,使代码更具有简洁性、可读性和复用性。移动语句
:将有关联的代码移动到一起,使代码更利于理解,也更有利于提炼函数。拆分循环
:让一个循环只做一个事情,有利于循环代码的修改,使代码结构更清晰,也有利于后续的重构。拆分后的循环常会被提炼成函数。以管道取代循环
:Java中使用Stream流来处理集合,使集合变换过程的代码可读性更强。移除死代码
:无用的代码会增加额外的阅读负担,请移除掉它。如果以后还需要可以从git中找回。
第九章:重新组织数据
拆分变量
:若一个变量承担了两个责任,则拆分变量,来保证每个变量只承担一个责任。如果可能的话,将变量声明为不可修改(final)。变量改名
:数据结构中的字段名称对于理解程序有很大帮助,如果名称不合适,请修改它。以查询取代派生变量
:如果派生变量可以很容易的计算出来,那么就移除该变量,每次使用时都通过源数据计算来得到。这样可以防止源数据修改时,忘记修改派生变量,消除了可变性。将引用对象改为值对象
:值对象是不可变的,如果类中某个字段是值对象,修改该字段的内部属性时,就需要替换整个该字段对象。值对象通常更容易理解,也不用担心内部属性被修改。如果对象不需要被共享修改,那就尽量使用值对象。将值对象改为引用对象
:如果某些对象需要被共享,那就使用引用对象,便于修改和维护。
第十章:简化条件逻辑
分解条件表达式
:对于复杂的if-else逻辑,可以将每个条件判断和条件分支提炼成函数以增强代码表达性。合并条件表达式
:一串条件检查逻辑中,出现条件各不相同,执行逻辑却相同的代码,可以将其合并。顺序执行的if语句用逻辑或来合并,嵌套执行的if语句用逻辑与来合并。以卫语句取代嵌套条件表达式
:对于特殊条件的处理,尽量使用卫语句形式,能更清楚的表达程序意图。以多态取代条件表达式
:两个典型场景可以使用多态(策略模式+模板模式)来优化代码:1、好几个地方出现相同条件的switch语句。2、有一个基础逻辑,在其之上又有一些变体。引入特例
:如果对某个数据结构经常做特殊字段的处理,并且处理逻辑相同,可以考虑创建一个特例元素,用以表达对这种特例的共用行为的处理,这样就可以用一个函数调用取代大部分特例检查逻辑。这种方式称作特例模式。引入断言
: 如果你发现代码假设某个条件始终为真,可以考虑加入一个断言明确说明这种情况。但是注意断言不能用来替代输入参数校验。
第十一章:重构API
将查询函数和修改函数分离
:如果一个查询函数中含有看的到的副作用(比如修改操作或其他命令),尝试将查询操作和副作用分离开。函数参数化
:如果一组函数逻辑非常相似,只有一些字面量不同,可以将其合并为一个函数,并通过参数来传入不同的值。用以减少代码重复,提高函数复用性。移除标记参数
:识别并删除函数中的标记参数,用多个函数来替代标记参数的作用,使函数功能逻辑更清晰。标记参数是指用字面量值传入并且影响函数内部控制流的参数,如果传入的是程序中流动的数据的参数并不算作标记参数。保持对象完整
:有时候没有必要将对象中的属性取出作为多个参数传入,可以直接传入整个对象保持完整性。但有时也不想让被调函数依赖完整对象,尤其是两者不在同一模块中时。以查询取代参数
:如果函数的某个参数在函数自身中也很容易获得,那就可以在函数中获取而不必要传入该参数,以减少调用者负担。以参数取代查询
:如果需要让函数不再依赖某个元素,则可以把该元素作为参数传入。不过这样会增加函数调用者的复杂度。移除设值函数
:如果对象不可变,则不要提供设值函数,字段值只能在构造函数中初始化。以工厂函数取代构造函数
:构造方法具有一定的局限性,在必要的时候使用工厂方法替代构造方法。以命令取代函数
:有时候将函数封装成对象来操作(即命令对象),可以增强灵活性和表达能力。比如,借助命令对象,可以轻松地将原本复杂的函数拆解为多个方法,彼此之间通过字段共享状态。以函数取代命令
:命令对象所处理的逻辑过于简单时,考虑使用函数来替代。
第十二章:处理继承关系
函数上移
:将不同子类中具有重复性的函数移到父类中。字段上移
:将不同子类中具有重复性的字段移到父类中。构造函数本体上移
:将不同子类构造函数中的重复性代码移动到父类构造函数中。函数下移
:如果超类中的某个函数只与一个(或少数几个)子类有关,那么最好将其放到真正关心它的子类中去。字段下移
:如果某个字段只被一个子类(或者一小部分子类)用到,就将其搬移到需要该字段的子类中。以子类取代类型码
:大多数时候,我们可以使用类型码来表现分类关系。但如果有几个函数都在根据类型码的取值采取不同的行为,那使用子类来取代类型码可能是更好的选择。而且当有些字段或函数只对特定的类型码取值才有意义时,子类的形式能更明确地表达数据与类型之间的关系。移除子类
:如果随着软件的演化,子类失去了价值,则移除子类并将其替换为超类中的一个字段。提炼超类
:两个类在做相似的事情,就可以利用继承机制将相似之处提炼到超类中。当然,我们是可以在程序编写前就设计好继承关系,但有时候这种继承关系是在程序演化过程中才浮现的。折叠继承体系
:随着继承体系的演化,当一个类与其超类已经没有多大差别时,就不值得作为独立的类存在,此时就需要把超类和子类合并。以委托取代子类
:继承有两个很明显的问题。一是只能处理一个方向上的变化;二是給类之间引入了紧密的关系,需要我们去充分理解子类与超类之间的关系,不然在超类上的修改很可能会破坏子类。而使用委托可以解决这两个问题。对于不同的变化原因,我们可以委托给不同的类;而且相比于继承,委托关系时接口更清晰,耦合更少。所以如果在使用继承中遇到了问题,那就运用委托来取代子类。以委托取代超类
:通过继承可以很方便的让子类复用超类的功能,但有时不合理的继承反而会导致混乱和错误,比如经典错误”stack继承list“,实际上stack只需要使用list的部分操作。而合理的继承有两个重要的特征,一是子类用得上超类的所有函数,二是通过超类的接口来使用子类的实例应该完全不出问题。所以面对不合理的继承,可以把继承关系改为将部分职能委托给另一个对象,通过这样的委托关系可以更好的表达”我只是需要另一个东西的部分功能“的含义。