重构技术导读-《重构 改善既有代码的设计》

18 篇文章 0 订阅
10 篇文章 0 订阅

《重构技术导读》 李超利 2018.03.28

任何一个傻瓜都能写出计算机可以理解的代码。唯有写出人类容易理解的代码,才是优秀的程序员。 《重构 改善既有代码的设计》重构序言

所谓的重构其实是这样的一个过程,在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构。重构是一种经千锤百炼形成的有条不紊的程序整理方法,可以最大限度地减少整理过程中引入的错误的几率。本质上说,重构就是在代码写好之后改进它的设计。

一、代码中散发的坏味道

image.png

改良代码中散发的坏味道,其实我们在做的是一件面向代码编程的过程,我们看到的是字段、变量、类、方法通过继承多态等概括关系组成的一块代码包。其中类与方法、类与字段、方法与方法等这些元素组成的笛卡尔集合,在某种程度上进行了过度耦合或不良设计,引发了在编程价值感方面降低的问题。而这些问题潜伏在我们的代码中挥之不去,所以为了去解决这个些问题,首先需要按场景能够识别出这个坏味道。比如重复代码、过长函数、过大的类等,我们根据这些场景会有哪些行动或者说是做法进行补救,当然这些补救不一定都要进行实施,这个需要根据我们在代码设计方面找到平衡点,遵循平衡中进行前进的原则进行有效改良。这是我们导读本篇的传达的核心价值点。

  • Duplicated Code(重复代码)

    • 【场景】同一个类或多个类中有重复的代码。
    • 【做法】
      • 1.同一类里出现多次相同的代码块,使用“提炼方法”。
      • 2.两个互为兄弟的子类含有相同的代码块,使用“提炼函数”,然后针对提炼的代码使用“函数上移”,将它推入超类内。如果是两个不相干的类出现相似代码,使用“提炼函数”单独放置一个合适的独立类中。
      • 3.在使用“提炼函数”技术时,将相似部分与差异部分割开,构成独立的函数,通过“塑造模板函数”达到“模板设计模式”。
      • 4.不同算法做相同的事,使用Subsititute Algorithm将其他函数算法替换掉。
  • Long Method(过长函数)

    • 【场景】函数过长
    • 【做法】通过Extract Method(提炼方法)聚合函数逻辑。
      • 1.期间产生的临时元素,可以通过Replace Temp with Query(以查询代替临时)来消除
      • 2.过长参数可以通过Introduce Parameter Object(引入参数对象)和Preserve Whole Object(保存对象完成)处理
    • 【问题】如何聚合提炼逻辑
      • 1.条件表达式使用decompose Conditional(分解条件)解决
      • 2.循环可以与其内在的代码提炼到一个独立的函数中。
  • Large Class(过大的类)

    • 【场景】类过大
    • 【做法】
      • 1.通过Extract Class(提炼函数)、Extract Subclass(提炼子类)解决
      • 2.先通过Extract Interface(提炼接口)提炼一个接口,用于区分类。
      • 3.两个类之间需要保留一些重复数据,使用Duplicate Observed Data(抽象数据)。
  • Long Parameter List(过长参数列)

    • 【场景】方法参数过长
    • 【做法】
      • 1.Replace Parameter with Method,从对象的方法中获取参数数据
      • 2.Preserve Whole Object(保存对象完成)
      • 3.Introduce Parameter Object,数据缺乏合理归属,构造一个新的“参数对象”
  • Divergent Change(发散式变化)

    • 【场景】如果每遇到某种变化,你都需要在该类中很多函数进行修改。
    • 【做法】找到某些特定的原因引起的所有的变化,运用Extract Class(提炼类)去解决
  • Shotgun Surgery(散弹式修改)

    • 【场景】如果每遇到某种变化,你都必须在不同的类做出许多小的修改。
    • 【做法】运用Move Method或者Move Field去解决
  • Feature Envy(依恋情结)

    • 【场景】函数对某个类的兴趣高过对自己所在类的兴趣。
    • 【做法】先进行Extract Method,后Move Method移动到他应有的地方。
  • Data Clumps(数据泥团)

    • 【场景】两个类中有相同的字段及函数签名参数。
    • 【做法】找出共性,运用Extract Class提炼到独立对象中,然后注意力转移到函数签名上,运用Introduce Parameter Object(引入参数对象)或Preserve Whole Object(保存对象完成)为其减肥。
  • Primitive Obsession (基本类型偏执)

    • 【做法】使用Replace Data Value with Object(175)将原本单独存在的数据值替换为对象。
      如果想要替换的数据值是类型码,而它并不影响行为,则可以运用Replace Type Code with Class(218)换掉他。
      如果你有与类型码相关的条件表达式,运用Replace Type Code with Subclass(213)或Replace Type Code with State/Strategy(227)加以处理。
  • Switch Statements(switch惊悚现身)

    • 【场景】面向对象程序最明显的特征就是少用switch(或case)语句,从本质上说,switch语句的问题在于重复。利用多态替换switch。
    • 【做法】
      • 首先“提炼函数”将switch语句提炼到一个独立的函数。
      • 再以“移动函数”将它搬移到多态性的那个类里。
      • 此时,需要决定是否使用“用子类代替类型码”或“以状态/策略代替类型码”手法来完成继承结构。
      • 之后,使用“多态代替条件”完成switch重构。
      • 其他的,如果分支条件不复杂,则可以通过单一函数收敛来处理。如果选择条件是null,可以试试“引用空对象”。
  • Parallel Inheritance Hierarchies(平行继承体系)

    • 【场景】其实是“霰弹式修改”的特殊情况,多个类属于多个继承体系的,他们各自类的前缀具有相似性。
    • 【做法】为了消除这种重复性,可以让一个继承体系的实例引用另一个继承体系的实例。如果在运用“移动方法”与“移动字段”,就可以完全消除无形。
  • Divergent Change(发散式变化)

    • 【场景】在一个类中修改多处。霰弹式修改(Shotgun Surgery) 指的是同时对多个类进行单一的修改。 特征你发现你想要修改一个函数,却必须要同时修改许多不相关的函数。
  • Lazy Class(冗赘类)

    • 【场景】如果超类与子类没有显示出继承带来的价值
    • 【做法】则可以通过Collapse hierarchy(344)折叠继承体系来解决。如果该组件几乎没用。则直接Inline Class(154)内联类对付它们。
  • Speculative Generality(夸夸其谈未来性)

    • 【场景】在代码中出现较多的过度设计
    • 【做法】可以通过Collapse Hierarchy(344)继承体系、Inline Class内敛类,无用的参数移除,名字过度的抽象可以重新命名。
  • Temporary Field(令人迷惑的暂时字段)

    • 【场景】某个实例变量为了某种特定情况而设,比如实现一个复杂的算法时不希望传递一长串参数,但是这些字段只在使用该算法时才有效。
    • 【做法】这时可以通过Extract Class提炼到一个独立的类中。如果想避免写出条件式代码,则可以使用Introduce Null Object(260)引入空对象手法。
  • Message Chains(过度耦合的消息链)

    • 【场景】使用Hide Delegate(隐藏委托关系)策略,先观察Message Chains最终的对象是用来干什么的,看看能否以提炼函数把该对象的代码提炼到一个独立的函数中,再运用搬移函数将这个函数推入Message Chains。
  • Middle Man (中间人)

    • 对象的基本特征之一就是封装—-对外部世界隐藏其内部细节。封装往往伴随着委托。
    • 【场景】过度委托导致一个类的绝大多数函数依赖到其他类。
    • 【做法】移除中间人,然后将少量的函数通过内联函数放到调用端。如果对象还有其他行为,运用“以继承取代委托”把它变成负责对象的子类,这样既可以扩展原对象的行为,又不必负担那么多委托动作。
  • Inappropriate Intimacy (狎昵xia ni关系)

    • 【场景1】如果两个类过于狎昵那就需要拆散,可以采取“移动函数”与“移动字段”帮它们划清界线,也可以使用“”让其中一个类对另一个斩断情丝。
    • 【场景2】如果两个类实在是情投意合,可以“提炼类”将两者的共同点提炼到一个安全地点。让它们坦荡地使用这个新类。或者也可以尝试运用“隐藏代理关系”让另一个类来为它们传递相思情。
    • 【建议】继承往往会造成多度亲密,如果子类对超类的了解总是超过后者的主观愿望。可以运用“以委托取代继承”让它离开继承体系。
  • Alternative Classes with Different Interfaces(异曲同工的类)

    • 【场景】两个函数做同一件事,却有着不同的方法签名。
    • 【做法】如果方法名字取意不恰当,可以运用“重新命名方法”。反复使用“移动方法”将某些行为移入合适的类,直到两者的协议一致。如果需要消除重复而赘余的代码,可以使用“提炼父类”来解决。
  • Incomplete Library Class(不完美的库类)

    • 【场景】复用常被视为对象的终极目的,但是类库的构建者没有未卜先知的能力,所以针对基础的类库也常常会被修改。
    • 【做法】如果你只想修改库类的一两个函数,可以运用“引入外加函数”。如果想要添加一大堆额外行为,可以运用“引入本地扩展”。
  • Data Class(纯稚的数据类)

    • 【场景】他们拥有一些字段,以及拥有访问(读写)这些字段的函数,除此之外一无长物。这样的类只是一种不会说话的数据容器,它们几乎一定被其他类过分细锁地操控着。
    • 【做法】先整理类,使用”封装字段”、“封装集合”、“移除设值函数”。再处理调用处,尝试运用“移动方法”搬移到这个类中,如果不能搬移整个函数,运用“提炼函数”产生一个可被搬移的函数。然后运用“隐藏函数”将这些取值/设值函数隐藏起来。
    • 【建议】针对确定的数据容器尝试大胆的改造,需要特别注意是否是该类(client-jar)是否已被其他APP依赖使用。
  • Refused Bequest(被拒绝的遗赠)

    • 【场景1】子类应该继承超类的函数和数据,但是如果它们不想或不需要继承,却只从中挑选几样来玩,继承体系设计错误,需要为这个子类建一个兄弟类,通过“函数下移”或“字段下移”把所有用不到的函数下推那个兄弟。而超类继续持有所有子类共享的东西。“所有超类都应该是抽象的”。
    • 【场景2】子类复用了超类的行为(实现),却又不愿支持超类的接口。运用“以委托取代继承”处理这个实现。即该类建立一个方法,该方法实现调用超类的实现接口来完成委托,最后去掉继承关系。
    • 【建议】优先判断父类的函数是否是所有子类都需要的。如果不是,则建立多个子类作为兄弟,将差异化的字段与函数下移到合适的子类中。如果父类存在的接口子类不想实现,则该子类去掉继承关系,采用创建一个该子类的函数去委托调用超类函数。
  • Comments(过多的注释)

    • 【场景】当你感觉需要撰写注释时,请先尝试重构,试着让所有注释都变得多余。
    • 【做法】如果需要注释来解释一块代码做了什么,试试“提炼函数”。如果函数已经提炼,但还是需要注释来解释其行为,试试“重新命名函数”。如果需要注释说明某些系统的需求规格,试试“引入断言”。
    • 【建议】如果业务属性较强的逻辑,可以在方法开头去注释整个方法的设计思路。如果业务属性不强,代码赘余较为突出,按照“代码块->提炼函数->重命名函数->引入断言”思路逐步优化代码结构,使得减少不必要的注释。
二、构筑重构的测试体系

image.png

构筑测试保障体系,以合适的重构时机进行小步迭代改良,崇尚”效果->目标->工具->要诀->实践”的循环,这是我们遵循安全重构的方法论。

  • 【效果】确保所有的测试读完全自动化,让它们检查自己的测试结果。
  • 【目标】一套测试就是一个强大的bug侦测器,能够大大缩短查找bug所需要的时间。
  • 【工具】单元测试-JUnit框架
  • 【要诀】“测试你最担心出错的部分”。
  • 【实践】编写未臻完善的测试并实际运行,好过对完美测试的无尽等待。不要因为测试无法捕捉所有的bug就不写测试,因为测试的确可以捕捉到大多数bug。
三、函数重构技术

image.png

封装一块代码块,提炼具有高内聚低耦合的函数会使得我们工程灵活度更高,形成可复用的工程组件,最终沉淀出工程资产,反哺业务发展

  • Extract Method(110)提炼函数

    • 【场景】遇到过长函数或一段需要注释才能让人理解的用途的代码。
    • 【做法】将这段代码放进一个独立函数中,并让函数名称解释该函数的用途。函数粒度小,意味着复用的机会就越大。
      • 1).先看该函数是否存在局部变量,如果存在,则判断该局部变量在未来的独立函数外是否有用到,如果没用到,定义临时变量后,独立函数内完成即可。
      • 2).如果有用到,就要看下函数调用之后是否用到,如有用到可以定义独立函数的返回值。
  • Inline Method(117)内联函数

    • 【场景】函数的内部代码和函数名称不够清晰明确。
    • 【做法】在函数调用点插入函数本体的代码块,间接的去除无用的中间层。
  • Inline Temp(119)内联临时变量

    • 【场景】临时变量妨碍其他重构手法。
    • 【做法】将这个临时变量的引用动作替换为它赋值的表达式自己。
  • Replace Temp with Query(120)以查询取代临时变量

    • 【场景】程序中存在一个临时变量保存某一表达式的运算结果。
    • 【做法】将这个表达式提炼到一个独立函数中,将这个临时变量的所有引用点替换成为对新函数的调用。此后,新函数就可被其他函数使用。
    • 【建议】如果临时变量只被赋值一次,则可以将该赋值表达式作为一个查询方法去处理。如果该临时变量赋值被超过一次,则考虑使用“分解临时变量”将它分割成多个变量。
  • Introduce Explaining Variable(124)引入解释性变量

    • 【场景】程序中存在复杂的表达式
    • 【做法】将该复杂的表达式(或其中的一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途。
    • 【建议】先“拆分条件表达式”,将各个表达式赋值新引入的变量。这种方案引入了临时变量,然后在优先推荐将表达式通过提炼函数手法去处理。
  • Split Temporary Variable(128)分解临时变量

    • 【场景】程序中存在某个临时变量被赋值超过一次,它既不是循环变量,也不被用于收集计算结果。即一个变量被重复赋值多次且每次应对不同的语义
    • 【做法】分解每次被赋予新语义的表达式到新的临时变量上。
  • Remove Assignments to Parameters(131)移除对参数的赋值

    • 【场景】程序中对方法的参数直接进行赋值
    • 【做法】针对函数参数进行赋值意味着采用了“按引用传递”,但是Java是”按值传递”的,强制的做法可以显示的在函数的参数前面声明final来标记这个“按值传递”。
  • Replace Method with Method Object(135)以函数对象取代函数

    • 【场景】有一个大型函数,其中的局部变量的使用你无法采用提炼函数手法
    • 【做法】将这个函数放进单独对象中,原来的局部变量就成了该对象内的字段。然后在同一个对象里中将这个大型函数分解为多个小型函数。
  • Substitute Algorithm(139)替换算法

    • 【场景】如果发现一段程序有更清晰的方式,就应该以清晰的方式取代复杂的方式。
    • 【做法】将复杂的算法替换成更清晰的算法。例如其中的多个if逻辑根据共性一般是可以提炼出已有的方法去支持它。
四、对象重构技术

image.png

方法与字段元素组成了类,针对类与类、类与方法、类与字段等组合出针对对象搬移特性的重构手法。通常严格意义上,这是一个领域建模的过程,合理的划分类与类之间的关系通常有模块的扇入与扇出为依据进行定量判断,而让合适的字段、方法放置合适的类中也通常有概括归属GRASP的方法论去判定。在既有的代码进行小步重构时,更多采用场景启发式重构,这可能是用的最爽快速试错的方法。

  • Move Method(142)搬移函数

    • 【场景】有函数与其所驻类之外的另一个类进行更多交流,调用后者,或者被后者调用。
    • 【做法】
      • 先检查源类的子类和超类,看看是否有该函数的其他声明。如果出现其他声明,你或许无法进行搬移,除非目标类也同样表现出多态性。
      • 在该函数最常引用的类中建立一个有着类似行为的新函数。
      • 将旧函数变成一个单纯的委托函数或是将旧函数完全移除。
      • 如果目标函数使用了源类中的特性,你的决定在目标函数中如何引用源对象,如果目标类无相应的引用机制,或许你可以把源对象的因引用当做参数传递给新建立的目标函数来解决。如果源函数包含异常处理,需要判断是把异常留在原地还是在新的目标类中处理。
      • 测试通过后,针对是否删除源函数或当做一个委托函数保留下来。简单判定源对象中引用的目标函数是否多,如果多的话,可以选择不删除源函数将他当做委托函数保留下来。如果需要删除源函数,那么通过“查找/替换”改掉所有的引用点即可。
  • Move Field(146)搬移字段

    • 【场景】程序中某个字段被其所驻类之外的另一个类更好的用到。
    • 【做法】在目标类建立一个字段,修改源字段的所有用户,令它们改用新的字段。
  • Extract Class(149)提炼类

    • 【场景】某各类做了应用由两个类做的事
    • 【做法】建立一个新类,将相关的字段和函数从旧类搬移到新类
  • Inline Class(154)将类内联化

    • 【场景】某个类没有做太多事
    • 【做法】将这个类的所有特定搬移到另一个类中,然后移除源类。
  • Hide Delegate(154)隐藏“委托关系”

    • 【场景】客户通过一个委托类来调用另一个对象
    • 【做法】在服务类上建立客户所需的所有函数,用以隐藏委托关系。
  • Remove Middle Man(160)移除中间人

    • 【场景】某个类做了过多的简单委托动作
    • 【做法】让客户直接调用受托类
    • 【建议】度量“合适的隐藏程度”是选择“委托关系”还是“移除中间人”重构手法的重要参考。
  • Introduce Foreign Method(162)引入外加函数

    • 【场景】你需要为提供服务的类增加一个函数,但你无法修改这个类。
    • 【做法】在客户类中建立一个函数,并以第一参数形式传入一个服务类实例。
  • Introduce Local Extension(163)引入本地扩展

    • 【场景】你需要为服务类提供一些额外的函数,但你无法修改这个类。
    • 【做法】
      • 1.建立一个扩展类,将它作为原始类的子类或包装类。
      • 2.在该类中,添加转型构造函数,如果采用子类化的方案,那么该函数应该调用适当的超类构造函数,如果采用包装类方案,那么转型构造函数应该传入参数以实例变量的形式保存起来,用作接受委托的原对象。
      • 3.根据需要,将原对象替换成扩展对象。
      • 4.针对原始类定义的所有外加函数搬移到扩展类中
五、数据重构技术

image.png

在面向对象的程序设计中,数据泥团、散乱、魔法值、过度开放等问题时有存在,穿插在代码的各个角落,也许我们需要坚持“函数和数据应该被统一封装”的思路去整理他们,使这些凌乱不合理的数据被调用时显得更具有可维护性

  • Introduce Foreign Method(引入外加函数)

    • 【场景】你需要为提供服务的类增加一个函数,但是你无法修改这个类。
    • 【做法】在客户类中建立一个函数,并以第一参数形式传入一个服务类实例。并为该函数注释“外加函数”,应用于未来转移到服务类中。
  • Introduce Local Extension(引入本地扩展)

    • 【场景】你需要为服务类提供一些额外的函数,但你无法修改这个类
    • 【做法】建立一个新类,使它包含这些额外函数。让这个扩展品成为源类的子类(subClassing-子类化)或包装类(wrapping-包装),这两类手法称之为“本地扩展”。使用“本地扩展”得以坚持“函数和数据应用被统一封装”的原则,不应该将扩展类中的代码零散到其他类中,使得其他类变得过分复杂难以复用。
  • Replace Data Value with Object(175) 以对象取代数据值

    • 【场景】开发初期,以简单的数据项表示简单的情况,但随着开发的进行,这些简单的数据项不再简单,可能会散发出多个数据含义。
    • 【做法】将数据项变成对象,配合行为(方法)一起使用。将基本类型收敛成一个值对象作为另一个对象属性。值对象应该是不可修改内容的。不可变的意思是如果Money金额发生变化,不是直接在该Money上进行修改,而是用另一个Money对象取代它,你与Money对象之间的关系可以改变,但是Money对象自身是不能变的。
  • Change Value to Reference(179) 将值对象改成引用对象

    • 【场景】你从一个类衍生出许多彼此相等的实例,希望将他们替换为同一个对象。
    • 【做法】将这个值对象变成引用对象,即对象里增加一些可修改的数据(引用对象),并且修改数据后,所有引用该对象的任何地方都会受影响。比如多个订单实例对应同一个客户。
  • Replace Array with Object(186) 有对象取代数组

    • 【场景】你有一个数组,其中的元素各自代表不同的东西
    • 【做法】以对象替换数组,对数组中的每个元素,以一个字段来表示。
  • Change Unidirectional Association to Bidirectional(197)将单向关联改为双向关联

    • 【场景】两个类都需要使用对方特性,但其间只有一条单向连接
    • 【做法】添加一个反向指针,并使修改函数能够同时更新两条连接。
  • Replace Magic Number with Symbolic Constant(204)以字面常量取代魔法数

    • 【场景】程序中存在一个字面数值,带有特别的含义。
    • 【做法】创造一个常量,根据其意义为它命名,并将上述的字面数值替换为这个常量。
  • Encapsulate Field(206)封装字段

    • 【场景】你的类中存在一个public字段,面向对象的首要原则就是封装或称为“数据隐藏”,数据和行为被分开了,这可不是件好事。
    • 【做法】将它声明为private,并提供相应的访问函数
  • Encapsulate Collection(208)封装集合

    • 【场景】有个函数返回一个集合,暴露了对该集合的取值和设值接口,是一种不安全的做法。尽可能去隐藏对象内部数据结构信息。
    • 【做法】让这个函数返回该集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数
  • Replace Record with Data Class(217)以数据类取代记录

    • 【场景】例如一个数组中每个位置上的元素都有特定含义
    • 【做法】建立一个数据对象来代替这个数组。
  • Replace Type Code with Class(218) 以类代替类型码

    • 【场景】类中有一个数值类型码,但它并不影响类的行为
    • 【做法】以一个新的类替换该数值类型码
  • Replace Type Code with Subclass(223) 以子类取代类型码

    • 【场景】如果面对的类型码不会影响宿主类的行为,可以使用以类取代类型码手法解决。如果会影响,则可以借助多态来处理变化行为。即以类型码的宿主类为基类,针对每种类型码建立相应的子类。
    • 【注意】特别的情况不能使用,需要使用Replace Type Code with State/Strategy(227)手法。
      • 1).类型码值在对象创建之后发生改变
      • 2).类型码宿主类已有了子类。以子类取代类型码后,后续将原宿主类的相关的方法与字段进行下移到该子类。
  • Replace Type Code with State/Strategy(227)以State/Strategy取代类型码

    • 【场景1】如果类型码的值在对象生命期中发生变化或其他原因使得宿主类不能被继承。就可以使用该重构手法。
    • 【场景2】如果在重构之后期望使用简化算法,那么使用Strategy模式比较适合。
    • 【场景3】如果打算搬移与状态相关的数据,而你把新建的对象视为一种变迁状态,应该使用State模式。
六、简化条件表达式

image.png

无论是面向过程语言,还是面向对象语言,在他们用来判断的路由的逻辑中通常是以“条件表达式”的结果进行后续的操作的,这也使得条件表达式在不同的开发者手中演绎出多样性的特征,简化条件表达式可能是这个问题空间下的最有效的实践,但这并不意味着是唯一可操作的重构手法

  • Decompose Conditional(238)分解条件表达式

    • 【场景】你有一个复杂的条件语句
    • 【做法】从if/then/else三个段落中分别提炼出独立函数
  • Consolidate Conditional Expression(240)合并条件表达式

    • 【场景】你有一系列条件测试、都得到相同结果
    • 【做法】将这些测试合并为一个条件表达式,并将这个条件表达式提炼成为一个独立函数。
  • Consolidate Duplicate Conditional Fragments(243)合并重复的条件片段

    • 【场景】在条件表达式的每个分支上有着相同的一段代码
    • 【做法】将这段重复代码搬移到条件表达式之外。
  • Remove Control Flag(245)移除控制标记

    • 【场景】在一系列布尔表达式中,某个变量带有“控制标记”的作用
    • 【做法】以break语句或Return语句取代控制标记。
  • Replace Nested Conditional with Guard Clauses(250)以卫语句取代嵌套条件表达式

    • 【场景】函数中条件逻辑使得难以看清正常的执行路径
    • 【做法】使用卫语句(如果某个条件极其罕见,就应该单独检查该条件并在该条件为真时立刻从函数中返回)表现所有的特殊情况
  • Replace Conditional with Polymorphism(255)以多态取代条件表达式

    • 【场景】你手上有个条件表达式,它根据对象类型的不同而选择不同的行为
    • 【做法】将这个条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明成抽象函数。
  • Introduce Null Object(260)引入Null对象

    • 【场景】你需要再三检查某对象是否为Null。
    • 【做法】将Null值替换成NUll对象。
  • Introduce Assertion(267)引入断言

    • 【场景】某一段代码需要对程序状态做出某种假设
    • 【做法】建立一个Assert类,用于处理各种你确定的程序断言。如果有重复代码的发生,大胆使用“提炼函数”处理。
七、简化函数调用

image.png

复杂的函数调用关系会使得不合理的逻辑和不合理的使用方法被“误解”,我们需要一定的重构手法,让这些被“误解”的函数发挥光芒,崇尚见名知意的函数名字,使用便捷的函数出入参,函数优雅的开放策略等

  • Rename Method(273)函数改名

    • 【场景】函数的名称未能揭示函数的用途
    • 【做法】修改函数名称
  • Add Parameter(275)添加参数

    • 【场景】某个函数需要从调用端得到更多信息
    • 【做法】为此函数添加一个对象参数,让该对象带进函数所需信息。
  • Remove Parameter(277)移除参数

    • 【场景】函数本地不再需要某个参数
    • 【做法】将该参数去除
  • Separate Query From Modifier(279)将查询函数和修改函数分离

    • 【场景】某个函数既返回对象状态值,又修改对象状态
    • 【做法】建立两个不同的函数,其中一个负责查询,另一个负责修改。
  • Parameterize Method (283)令函数携带参数

    • 【场景】若干函数做了类似的工作,但在函数本体中却包含了不同的值
    • 【做法】建立单一函数,以参数表达那些不同的值
  • Replace Parameter with Explicit Methods(285)以明确函数取代参数

    • 【场景】你有一个函数,其中完全取决于参数值而采取不同行为
    • 【做法】针对该参数的每一个可能的值,建立一个独立函数
  • Remove Setting Method(300) 移除设值函数

    • 【场景】类中的某个字段应用在对象创建时被设值,然后就不再改变。
    • 【做法】去掉该字段的所有设置函数
  • Hide Method(303) 隐藏函数

    • 【场景】有一个函数,从来没有被其他任何类用到
    • 【做法】将这个函数修改成private
  • Replace Constructor with Factory Method(304) 以工程函数取代构造函数

    • 【场景】你希望在创建对象时不仅仅是做简单的构建动作
    • 【做法】将构造函数替换为工厂函数
  • Encapsulate Downcast(308) 封装向下转型

    • 【场景】某个函数返回的对象,需要由函数调用执行向下转型
    • 【做法】将向下转型动作移动函数中
  • Replace Error Code with Exception(310) 以异常取代错误码

    • 【场景】某个函数返回一个特定的代码,用以表示某种错误情况
    • 【做法】将这块代码改用异常码。
  • Replace Exception with Test(315) 以测试取代异常码

    • 【场景】面对一个调用者可以预先检查的条件,你抛出了一个异常
    • 【做法】修改调用者,使它在调用函数之前先做检查。
八、概括关系重构技术

image.png

字段、函数、类在处理概括关系时,其实就是提炼继承、多态的模式。这个其实是我们的一个目标,没有一次是大型阔斧的进行大型重构的,所以我们一定会存在过程阶段,而过程阶段,正是我们在做可复用程序的提炼。有句话说的好“模式是你希望达到的目标,重构则是到达之路”

  • Pull Up Field(320)字段上移

    • 【场景】两个子类拥有相同的字段
    • 【做法】将该字段移至超类
  • Pull Up Method(322) 函数上移

    • 【场景】有些函数,在各个子类中产生完全相同的结果
    • 【做法】将该函数移至超类
  • Pull Up Constructor Body(325)构造函数本体上移

    • 【场景】你在各个子类中拥有一些构造函数,它们的本体几乎完全一致
    • 【做法】在超类中新建一个构造函数,并在子类构造函数中调用它。
  • Push Down Method(328)函数下移

    • 【场景】超类中的某个函数只与部分(而非全部)子类有关
    • 【做法】将这个函数移到相关的那些子类去
  • Push Down Field(329)字段下移

    • 【场景】超类中的某个字段只被部分(而非全部)子类用到。
    • 【做法】将这个字段移到需要它的那些子类去。
  • Extract Subclass(330)提炼子类

    • 【场景】类中的某些特性只被某些(而非全部)实例用到。
    • 【做法】新建一个子类,将上面所说的那一部分特性移到子类中
  • Extract Superclass (336)提炼超类

    • 【场景】两个类有相似特性
    • 【做法】为这两个类建立一个超类,将相同特性移至超类
  • Extract Interface(341)提炼接口

    • 【场景】若干客户使用类接口中的同一个子集,或者两个类的接口有部分相同
    • 【做法】将相同的子类提炼到一个独立接口中。
  • Collapse Hierarchy(344)折叠继承体系

    • 【场景】超类与子类之间无太大区别
    • 【做法】将它们合为一体
  • Form TemPlate Method(345) 塑造模板函数

    • 【场景】你有一些子类,其中相应的某些函数以相同的顺序执行类似的操作,但各个操作的细节上有所不同。
    • 【做法】
      • 1.在各个子类中将这些操作通过“提炼函数”分别放到独立的函数中,并通过“重命名函数”保持他们的都有相同的签名,然后运用“函数上移”将原函数相同名称且实质用途一致的函数上移至超类。
      • 2.通过测试之后,再运用“函数上移”将剩余原函数相同名称但实质用途差异化的函数上移到超类。并在超类中将那些代表各种不同的操作函数定义为抽象函数。
      • 3.逐步删除子类原函数且实质用途一致的函数。
  • Replace Inheritance with Delegation (352)以委托取代继承

    • 【场景】某个子类只使用超类接口中的一部分,或根本不需要继承而来的数据
    • 【做法】,在子类中新建立一个字段用以保存超类,调整子类函数,令它改而委托超类,然后去掉两者之间的继承关系。
  • Replace Delegation with Inheritance(355) 以继承取代委托

    • 【场景】你在两个类之间使用委托关系,并经常为整个接口编写许多极简单的委托函.
    • 【做法】让委托继承受托类
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
第1章 重构,第一个案例 1 1.1 起点 1 1.2 重构的第一步 7 1.3 分解并重组statement() 8 1.4 运用多态取代与价格相关的条件逻辑 34 1.5 结语 52 第2章 重构原则 53 2.1 何谓重构 53 2.2 为何重构 55 2.3 何时重构 57 2.4 怎么对经理说 60 2.5 重构的难题 62 2.6 重构设计 66 2.7 重构与性能 69 2.8 重构起源何处 71 第3章 代码味道 75 3.1 Duplicated Code(重复代码) 76 3.2 Long Method(过长函数) 76 3.3 Large Class(过大的类) 78 3.4 Long Parameter List(过长参数列) 78 3.5 Divergent Change(发散式变化) 79 3.6 Shotgun Surgery(霰弹式修改) 80 3.7 Feature Envy(依恋情结) 80 3.8 Data Clumps(数据泥团) 81 3.9 Primitive Obsession(基本类型偏执) 81 3.10 Switch Statements(switch惊悚现身) 82 3.11 Parallel InheritanceHierarchies(平行继承体系) 83 3.12 Lazy Class(冗赘类) 83 3.13 Speculative Generality(夸夸其谈未来性) 83 3.14 Temporary Field(令人迷惑的暂时字段) 84 3.15 Message Chains(过度耦合的消息链) 84 3.16 Middle Man(中间人) 85 3.17 Inappropriate Intimacy(狎昵关系) 85 3.18 Alternative Classes with Different Interfaces(异曲同工的类) 85 3.19 Incomplete Library Class(不完美的库类) 86 3.20 Data Class(纯稚的数据类) 86 3.21 Refused Bequest(被拒绝的遗赠) 87 3.22 Comments(过多的注释) 87 第4章 构筑测试体系 89 4.1 自测试代码的价值 89 4.2 JUnit测试框架 91 4.3 添加更多测试 97 第5章 重构列表 103 5.1 重构的记录格式 103 5.2 寻找引用点 105 5.3 这些重构手法有多成熟 106 第6章 重新组织函数 109 6.1 Extract Method(提炼函数) 110 6.2 Inline Method(内联函数) 117 6.3 Inline Temp(内联临时变量) 119 6.4 Replace Temp with Query(以查询取代临时变量) 120 6.5 Introduce Explaining Variable(引入解释性变量) 124 6.6 Split Temporary Variable(分解临时变量) 128 6.7 Remove Assignments to Parameters(移除对参数的赋值) 131 6.8 Replace Method with Method Object(以函数对象取代函数) 135 6.9 Substitute Algorithm(替换算法) 139 第7章 在对象之间搬移特性 141 7.1 Move Method(搬移函数) 142 7.2 Move Field(搬移字段) 146 7.3 Extract Class(提炼类) 149 7.4 Inline Class(将类内联化) 154 7.5 Hide Delegate(隐藏“委托关系”) 157 7.6 Remove Middle Man(移除中间人) 160 7.7 Introduce Foreign Method(引入外加函数) 162 7.8 Introduce Local Extension(引入本地扩展) 164 第8章 重新组织数据 169 8.1 Self Encapsulate Field(自封装字段) 171 8.2 Replace Data Value with Object(以对象取代数据值) 175 8.3 Change Value to Reference(将值对象改为引用对象) 179 8.4 Change Reference to Value(将引用对象改为值对象) 183 8.5 Replace Array with Object(以对象取代数组) 186 8.6 Duplicate Observed Data(复制“被监视数据”) 189 8.7 Change Unidirectional Association to Bidirectional(将单向关联改为双向关联) 197 8.8 Change Bidirectional Association to Unidirectional(将双向关联改为单向关联) 200 8.9 Replace Magic Number with Symbolic Constant(以字面常量取代魔法数) 204 8.10 Encapsulate Field(封装字段) 206 8.11 Encapsulate Collection(封装集合) 208 8.12 Replace Record with Data Class(以数据类取代记录) 217 8.13 Replace Type Code with Class(以类取代类型码) 218 8.14 Replace Type Code with Subclasses(以子类取代类型码) 223 8.15 Replace Type Code with State/Strategy(以State/Strategy取代类型码) 227 8.16 Replace Subclass with Fields(以字段取代子类) 232 第9章 简化条件表达式 237 9.1 Decompose Conditional(分解条件表达式) 238 9.2 Consolidate Conditional Expression(合并条件表达式) 240 9.3 Consolidate Duplicate Conditional Fragments(合并重复的条件片段) 243 9.4 Remove Control Flag(移除控制标记) 245 9.5 Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件表达式) 250 9.6 Replace Conditional with Polymorphism(以多态取代条件表达式) 255 9.7 Introduce Null Object(引入Null对象) 260 9.8 Introduce Assertion(引入断言) 267 第10章 简化函数调用 271 10.1 Rename Method(函数改名) 273 10.2 Add Parameter(添加参数) 275 10.3 Remove Parameter(移除参数) 277 10.4 Separate Query from Modifier(将查询函数和修改函数分离) 279 10.5 Parameterize Method(令函数携带参数) 283 10.6 Replace Parameter with Explicit Methods(以明确函数取代参数) 285 10.7 Preserve Whole Object(保持对象完整) 288 10.8 Replace Parameter with Methods(以函数取代参数) 292 10.9 Introduce Parameter Object(引入参数对象) 295 10.10 Remove Setting Method(移除设值函数) 300 10.11 Hide Method(隐藏函数) 303 10.12 Replace Constructor with Factory Method(以工厂函数取代构造函数) 304 10.13 Encapsulate Downcast(封装向下转型) 308 10.14 Replace Error Code with Exception(以异常取代错误码) 310 10.15 Replace Exception with Test(以测试取代异常) 315 第11章 处理概括关系 319 11.1 Pull Up Field(字段上移) 320 11.2 Pull Up Method(函数上移) 322 11.3 Pull Up Constructor Body(构造函数本体上移) 325 11.4 Push Down Method(函数下移) 328 11.5 Push Down Field(字段下移) 329 11.6 Extract Subclass(提炼子类) 330 11.7 Extract Superclass(提炼超类) 336 11.8 Extract Interface(提炼接口) 341 11.9 Collapse Hierarchy(折叠继承体系) 344 11.10 Form Tem Plate Method(塑造模板函数) 345 11.11 Replace Inheritance with Delegation(以委托取代继承) 352 11.12 Replace Delegation with Inheritance(以继承取代委托) 355 第12章 大型重构 359 12.1 Tease Apart Inheritance(梳理并分解继承体系) 362 12.2 Convert Procedural Design to Objects(将过程化设计转化为对象设计) 368 12.3 Separate Domain from Presentation(将领域和表述/显示分离) 370 12.4 Extract Hierarchy(提炼继承体系) 375 第13章 重构,复用与现实 379 13.1 现实的检验 380 13.2 为什么开发者不愿意重构他们的程序 381 13.3 再论现实的检验 394 13.4 重构的资源和参考资料 394 13.5 从重构联想到软件复用和技术传播 395 13.6 小结 397 13.7 参考文献 397 第14章 重构工具 401 14.1 使用工具进行重构 401 14.2 重构工具的技术标准 403 14.3 重构工具的实用标准 405 14.4 小结 407 第15章 总结 409
一直很喜欢重构这本书,但是由于自己记性不太好,书看过之后其中的方法总是记不住,于是想如果有电子版的重构书就好了,工作中遇到重构的问题可以随时打开查阅。在网上搜索了许久,发现重构这本书有英文chm版本的,而中文版的电子书只有扫描的PDF版本,用起来非常不方便。于是萌生想做一本重构工具书的想法,本来打算自己重新将重构书的内容再整理归类一下,后来发现原书的目录编排就很适合做工具书,包括味道分类,重构手法归类等,都有了一个比较系统的整理。因此,我利用空余时间制作了这样的一本中文的chm版重构,希望对大家有所帮助,也算对中国软件业做出一点小小的贡献。 本书基本上是取自”重构”中文版一书的内容,但格式上参照的是chm英文版的格式,还有一些格式小修改,比如第一章的重构前后代码对比。因为时间匆促,个人能力有限,本书难免存在一些缺漏,如果大家发现有问题,随时可以给我发邮件,我会尽快更新错误的内容。 最后再次感谢几位大师 Martin Fowler、Kent Beck等,还有翻译的侯捷和熊节先生,为我们带来这么精彩的一本书。谢谢。 免责声明:本书仅供个人学习研究之用,不得用于任何商业目的,不得以任何方式修改本作品,基于此产生的法律责任本人不承担任何连带责任。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值