《重构》的方法列表

ZhuYuanxiang 2019-01-09 00:00:00
Categories: Tags:

第 5 章 重构列表

5.1 重构的记录格式 P_103

这个格式可以作为自己未来记录重构手法的标准,方便查阅自己常用的和总结的重构方式

5.2 寻找引用点 P_105

引用点 : 就是那些被重构的代码被哪些代码调用了。现在的重构工具已经可以简化这些操作,但是工具永远都是有限的,除了工具还应该熟悉一些基本的操作手段。

5.3 这些重构手法有多成熟 P_106

学习和使用这些重构仅仅是个起点,业务在变、工具在变、语言在变、对事物的理解也在变,因此完全挺有自己的重构手段才是最终的目标。

第 6 章 重新组织函数

6.1 Extract Method ( 提炼函数 ) P_110

动机 : Long Methods 问题或者代码需要 Comments 问题;
方法 : ( 可以使用 IDE 提供的重构工具,下面的具体操作的原理 )

6.2 Inline Method ( 内联函数 ) P_117

动机 : 当函数内部的代码与其名称一样易懂,可以在函数调用点插入函数本体,移除该函数。
补充 : 对有一群不太合理的函数,可以先内联为一个长函数,然后再提炼出合理的小函数

6.3 Inline Temp ( 内联临时变量 ) P_119

动机 : 当临时变量只被一个简单的表达式赋值一次,而且它妨碍其他重构方法时,才需要重构
条件 : Inline Temp 多半是为 Replace Temp with Query ( 以查询取代临时变量 ) 准备
方法 : 将所有对该变量的引用动作替代成对它赋值的表达式本身。

6.4 Replace Temp with Query ( 以查询取代临时变量 ) P_120

动机 : 你的程序以一个临时变量保存一个表达式的计算结果
方法 : 将表达式提炼出独立的函数,然后临时变量的调用替换成新函数的调用。此后新函数也能被调用。
具体方法 : 将提炼出来的函数用 private 修饰,如果独立函数有副作用,那对它进行 Separate Query from Modifier ( 将查询函数和修改函数分离 )

6.5 Introduce Explaining Variable ( 引入解释性变量 ) P_124

将复杂表达式 ( 或者其中一部分 ) 的结果赋值给一个临时变量,用临时变量名称来解释表达式的用途

6.6 Split Temporary Variable ( 分解临时变量 ) P_128

临时变量被赋值超过一次,但是它既不是循环变量也不是被用于收集计算结果
原因 : 一个变量应该承担一个责任,如果被赋值多次很可能承担了多个责任
方法 : 针对每次赋值,创建新的临时变量

6.7 Remove Assignments to Parameters ( 移除对参数的赋值 ) P_131

java 是值传递,对参数的任何修改都不会对调用端产生影响,所以对于用过引用传递的人可能会发生理解错误
参数应该仅表示「被传递过来的东西」

6.8 Replace Method with Method Object ( 以函数对象取代函数 ) P_135

动机 : 在大型函数内,对局部变量的使用导致难以使用 Extract Method ( 提炼函数 ) 进行重构
方法 : 将这个函数放入一个对象里,局部变量变成对象成员变量,然后可以在同一对象中将这个大型函数分解为多个小型函数。
原因 : 局部变量会增加分解函数的困难度

6.9 Substitute Algorithm ( 替换算法 ) P_139

把某个算法替换成更清晰的方法 ( 算法 )。

第 7 章 在对象之间搬移特性 P_141

章的重点是搬移 ( Move )。「决定把责任放在哪里」是面向对象设计中最重要的事情之一,但是刚开始设计时,由于技术和业务知识的不足无法保证做出的决定是正确的,那么后期调整中将「责任」搬移到正确的对象中就是重要的重构手段。而 Move Method ( 142 ) 和 Move Field ( 146 ) 就是搬移「责任」过程中最基本的两个方法。

7.1 Move Method ( 搬移函数 ) P_142

动机 : 类中某个函数与其他类交互过多
方法 : 将该函数搬移到交互最多的类里面,将旧函数变成委托函数或者删除。
实现 :

7.2 Move Field ( 搬移字段 ) P_146

动机 : 类中某个字段被其他类频繁使用 ( 包括 : 传参数、调用取值函数、调用设值函数 )
方法 : 将该字段搬移到目标类
具体方法 :

7.3 Extract Class ( 提炼类 ) P_149

动机 : 一个类做了两个类的事
方法 :

7.4 Inline Class ( 将类内联化 ) P_154

动机 : 某个类功能太少,与 Extract Class ( 提炼类 ) 相反
方法 : 将这个类的所有特性搬移到另一类中,移除该类。
原因 : 多次 Extract Class 后,原类大部分功能被移走,将这个萎缩类与其他相近的类合并

7.5 Hide Delegate ( 隐藏「委托关系」) P_157

动机 : 客户端通过委托类来取得另一个对象的信息
方法 : 在服务类上建立客户端所需数据的函数,然后隐藏委托关系
依据 : 符合「封装」的特性。当委托类发生变化不会对客户端造成影响,减少客户端与调用者之间的耦合性。

7.6 Remove Middle Man ( 移除中间人 ) P_160

动机 : 某个类做了过多的委托动作
方法 : 让客户端直接调用委托类,与 Hide Delegate ( 隐藏「委托关系」) 相反
依据 : 当原委托类的特性越来越多,服务类的委托函数将越来越长,需要让客户端直接调用,避免服务类沦为中间人。

7.7 Introduce Foreign Method ( 引入外加函数 ) P_162

动机 : 使用的类无法提供某个功能,但是又不能修改该类
方法 : 新建函数,并将服务类的对象实例作为参数传入。
具体动机 : 如果需要为服务类增加大量的方法,请考虑使用 Introduce Local Extension ( 引入本地扩展 )

7.8 Introduce Local Extension ( 引入本地扩展 ) P_164

动机 : 使用的类无法提供多个功能,但是又不能修改该类
方法 : 建立新的类,在新类中建立需要的功能函数,可以作为服务类的子类实现新的类,也可以包装服务类实现新的类。
具体情况 :

第 8 章 重新组织数据

本章重点是如何更好地封装各种类型的数据。最常用的手段就是 Self Encapsulate Field ( 171 )

8.1 Self Encapsulate Field ( 自封装字段 ) P_171

动机 : 直接访问一个字段,但是字段之间的耦合关系逐渐变得笨拙。
方法 : 自封装就是在对于类内部的字段也封装一个设值取值的函数。
争论 : 字段访问方式是直接访问还是间接访问一致争论不断
间接访问的好处 :

8.2 Replace Data Value with Object ( 以对象取代数据值 ) P_175

动机 : 假如一个数据项需要与其他数据一起使用才有意义。数据已经不仅仅由一条简单的数据项组成,例如 : 电话号码
方法 : 将数据变成对象。

8.3 Change Value to Reference ( 将值对象改为引用对象 ) P_179

动机 : 一个类有许多相等的实例,希望把这些相等的实例统一为一个对象,方便统一修改或者进行相等性比较
方法 : 将值对象变成引用对象
「引用对象」与「值对象」的区别 :

8.4 Change Reference to Value ( 将引用对象改为值对象 ) P_183

动机 : 引用对象,很小且不可变,而且不易管理

8.5 Replace Array with Object ( 以对象取代数组 ) P_186

动机 : 如果数据存储的值代表不同的东西。
方法 : 将数组变成对象,数组的每个元素用字段表示

8.6 Duplicate Observed Data ( 复制「被监视数据」) P_189

动机 : 有业务数据置身于 GUI 控件中,而与业务相关的函数需要访问这些数据
方法 : 将业务数据复制到业务类中。建立 Observer 模式,同步 UI 和业务类的数据。

8.7 Change Unidirectional Association to Bidirectional ( 将单向关联改为双向关联 ) P_197

动机 : 两个类相互之间都需要对方的数据,但是相互之间只有一条单向的连接
这个重构需要添加测试,因为「反向指针」很容易造成混乱。
具体方法 :

8.8 Change Bidirectional Association to Unidirectional ( 将双向关联改为单向关联 ) P_200

动机 : 两个类有双向关联,但是一个类不再需要另一个类的特性
原因 :

8.9 Replace Magic Number with Symbolic Constant ( 以字面常量取代魔法数 ) P_204

动机 : 有一个字面常量 ( 除了 0 和 1 之外 )
方法 : 创建常量赋值以该字面常量,给予命名。

8.10 Encapsulate Field ( 封装字段 ) P_206

动机 : 一个类有 public 字段
将它声明为 private,并提供相应的访问函数

8.11 Encapsulate Collection ( 封装集合 ) P_208

动机 : 类中使用集合,但是集合不能提供给用户直接操作,而是提供函数操作集合,降低用户与集合之间的耦合度
方法 : 提供函数返回集合的只读副本,并提供增加和删除集合元素的函数
具体方法 :

8.12 Replace Record with Data Class ( 以数据类取代记录 ) P_217

动机 : 面对旧程序中 Record 数据结构,新建类取代它
方法 : 为该记录创建一个「哑」数据对象。

Type Code ( 类型码 )

常见于过去的 C 语言编程中,因为没有枚举,所以采用类型码的方式标注。这个重构遇到的机会比较小

8.13 Replace Type Code with Class ( 以类取代类型码 ) P_218

动机 : 类中的数值类型码不影响类的行为
方法 : 以一个新类替代类型码

8.14 Replace Type Code with Subclasses ( 以子类取代类型码 ) P_223

动机 : 有一个不可变的类型码且影响类的行为
标志 : switch 或者 if-then-else 类的条件表达式,这个重构是 Replace Conditional with Polymorphism 的准备工具
方法 : 以子类取代这个类型码

8.15 Replace Type Code with State/Strategy ( 以 State/Strategy 取代类型码 ) P_227

动机 : 有一个类型码且影响类的行为,但是无法通过继承消除 ( 类型码可变化 )
方法 : 以状态对象取代。

8.16 Replace Subclass with Fields ( 以字段取代子类 ) P_232

动机 : 各个子类唯一区别只在「返回常量的数据」的函数上
方法 : 修改这些函数使它们返回超类的某个 ( 新增 ) 字段,然后销毁子类。

第 9 章 简化条件表达式 P_237

条件逻辑非常复杂,也非常难以理解,通过重构将复杂的逻辑展现为简单的逻辑块。
有些重构方法看起来非常简单,因为重构最重要的思想不是方法有多精妙,而是传达了一个小步快走的理念。就是一次只完成一个小重构,然后测试确保没有错误。然后,再进行下一个小重构和测试。从而整个大重构通过多个简单的小重构完成,避免大重构出错后需要全部回滚的问题。

9.1 Decompose Conditional ( 分解条件表达式 ) P_238

动机 : if-then-else 语句,不同分支做不同事动机成大型函数,本身就难以阅读,尤其在带有复杂条件的逻辑中。
方法 :

9.2 Consolidate Conditional Expression ( 合并条件表达式 ) P_240

动机 : 有一系列条件判断都服务于共同的目标
方法 : 将这些条件判断合并为同一个表达式,再将这个表达式提炼为独立函数
原因 :

9.3 Consolidate Duplicate Conditional Fragments ( 合并重复的条件片段 ) P_243

动机 : 在条件表达式的不同分支中存在相同的代码
方法 : 将这些重复代码搬移到条件表达式之外,多行代码还可以再提炼为独立函数。
例如 : 当 try 和 catch 执行相同代码,可以将代码移到 final 区段。

9.4 Remove Control Flag ( 移除控制标记 ) P_245

动机 : 在循环执行的程序段中,某个变量定义为判断条件中的控制标记 ( control flag ),增加了代码理解的复杂度
方法 :

9.5 Replace Nested Conditional with Guard Clauses ( 以卫语句取代嵌套条件表达式 ) P_250

卫语句 : 如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回,这样的单独检查被称为「卫语句」( guard clauses )
动机 : 函数中的条件逻辑使人难以看清正确的执行路径。
方法 : 使用卫语句表现所有的特殊情况

9.6 Replace Conditional with Polymorphism ( 以多态取代条件表达式 ) P_255

动机 : 存在条件表达式根据对象的类型不同选择不同的行为
方法 : 将表达式分支放进不同子类,然后重写方法,将原始函数提炼为抽象函数。

9.7 Introduce Null Object ( 引入 Null 对象 ) P_260

动机 : 需要再三检查对象是否为 null
方法 : 将 null 值替代为 null 对象,如果原始类不允许修改可以使用 Null 接口来检查「对象是否为 Null」。

9.8 Introduce Assertion ( 引入断言 ) P_267

动机 : 某段代码需要对程序状态显式地表明某种假设
方法 : 以断言明确表现这种假设
具体方法 : 断言在 发布的时候统统 被跳过

第 10 章 简化函数调用

使接口变得更加简洁易用的重构方法。

10.1 Rename Method ( 函数改名 ) P_273

动机 : 函数的名称不能说明函数的用途
方法 : 将旧函数代码搬移到新函数,旧函数跳转到新函数。

10.2 Add Parameter ( 添加参数 ) P_275

动机 : 被调用的函数需要从调用函数中得到更多的信息
方法 : 为被调用的函数添加参数
抉择 :

10.3 Remove Parameter ( 移除参数 ) P_277

动机 : 函数不需要某个参数 ( 不需要了就放弃,保留也需要付出代价 )
方法 :

10.4 Separate Query from Modifier ( 将查询函数和修改函数分离 ) P_279

动机 : 某个函数既修改对象状态,又返回对象状态值。( 使调用者担心误操作修改了不应该修改的数据,增加调用者的操作负担 )
本质 : 函数功能简洁、明确,如果一个函数具备多个功能,就把它们分离成多个函数。
方法 : 建立两个不同的函数,其中一个负责查询,另一个负责修改。
原则 :

10.5 Parameterize Method ( 令函数携带参数 ) P_283

动机 : 几个函数,做了类似的工作,只是代码中的系数不同
方法 : 建立单一函数,以参数作为系数

10.6 Replace Parameter with Explicit Methods ( 以明确函数取代参数 ) P_285

动机 : 函数依赖于参数值的不同而采取不同的行为
方法 : 针对该参数的每个可能值,建立独立函数。
对比 : 与 Parameterize Method ( 令函数携带参数 ) 相反,但是目的都是把复杂的逻辑判断消除
目的 : 提供清晰的入口。
如果参数值对函数行为影响不大,不应该采用此方法。

10.7 Preserve Whole Object ( 保持对象完整 ) P_288

动机 : 从某个对象取若干个值,把他们作为参数传给函数
方法 : 改为调用整个对象
目的 : 避免过长参数列表
缺陷 : 如果传递的是值,那么函数只依赖那些值;如果传递的是对象,函数则依赖对象,会导致耦合
注意 : 有时候函数使用了很多来自某个对象的数据,那么应该考虑使用 ( Move Method ) 将这个函数移到关系密切的对象中

10.8 Replace Parameter with Methods ( 以函数取代参数 ) P_292

动机 : 对象调用某个函数,并将所得结果作为参数传递给另一个函数,而接受该参数的函数本身也能够调用前一个函数
方法 : 让参数接受者去除该项参数,并直接调用前一个函数

10.9 Introduce Parameter Object ( 引入参数对象 ) P_295

动机 : 有些参数总是自然地同时出现
方法 : 用一个对象把这些参数包装起来进行传递
目的 :

10.10 Remove Setting Method ( 移除设值函数 ) P_300

动机 : 类的某个字段应该对象创建的时候被设置,然后不再改变
方法 : 去掉该字段的设置函数

10.11 Hide Method ( 隐藏函数 ) P_303

动机 : 有一个函数,从来没有被任何类调用
方法 : 将该函数设为 private
补充 : 函数可见度不够,在编译的时候就可以发现;而函数过见度过高,则需要通过一些工具 ( Lint ) 来辅助检查。

10.12 Replace Constructor with Factory Method ( 以工厂函数取代构造函数 ) P_304

动机 : 创建对象时不仅仅是做简单的构建动作
方法 : 将构造函数替换为工厂模式
范例 :

10.13 Encapsulate Downcast ( 封装向下转型 ) P_308

动机 : 某个函数返回的对象,需要由函数调用者执行向下转型 ( downcast )
方法 : 将向下转型移到函数中

10.14 Replace Error Code with Exception ( 以异常取代错误码 ) P_310

动机 : 某个函数返回一个特定的代码,表示某个错误的情况
方法 : 取消那个代码判断,改用抛出异常
范例 :

10.15 Replace Exception with Test ( 以测试取代异常 ) P_315

动机 : 本该由调用者自行检查的条件,由被调用者抛出了一个可控异常。
方法 : 修改调用者,使它在调用函数之前做检查。
补充 : 异常就应该放在可能发生异常的地方使用。即可以预测的,可以通过检查避免的,那就是错误,不该发生;不能预测的,无法通过检查避免的,那就是异常。
例如 : 账户余额小于取钱数目,申请取钱这个就是错误;账户余额大于取钱数目,取不出钱来就是异常。

第 11 章 处理概括关系 P_319

概括关系 ( generalization,即继承关系、泛化关系 )

11.1 Pull Up Field ( 字段上移 ) P_320

动机 : 两个子类拥有相同的字段
方法 :

11.2 Pull Up Method ( 函数上移 ) P_322

动机 : 有些函数,在各个子类产生相同的结果。
方法 :

11.3 Pull Up Constructor Body ( 构造函数本体上移 ) P_325

动机 : 你在各个子类拥有一些构造函数,它们的本地几乎完全一致
方法 : 在超类新建一个构造函数,并在子类构造函数中调用它。
具体方法 :

11.4 Push Down Method ( 函数下移 ) P_328

动机 : 超类中的某个函数只与部分而非全部子类有关
方法 : 将这个函数移到相关的子类去。

11.5 Push Down Field ( 字段下移 ) P_329

动机 : 超类中的某个字段只被部分而非全部子类使用
方法 : 将这个字段移到需要它的那些子类去。

11.6 Extract Subclass ( 提炼子类 ) P_330

动机 : 类中的某些特性只被部分实例用到。
方法 : 新建一个子类,将上面所说的那一部分特性移到子类中。
具体情况 :

11.7 Extract Superclass ( 提炼超类 ) P_336

动机 : 两个类有相似特性。
方法 : 为两个类建立一个超类,将相同特性移至超类。
补充 : Extract Class, Extract Subclass, Extract Superclass 对比学习。

11.8 Extract Interface ( 提炼接口 ) P_341

动机 : 多个用户只使用类接口中的同一子集,或者两个类的接口有部分相同。
方法 : 将相同子集提炼到独立的接口中。
区别 : 提炼超类是提炼共同代码,提炼接口时提炼共同接口。
具体动机 : 如果某个类在不同环境下扮演截然不同的角色,使用接口就是个好主意。接口还能帮助类隐藏一些对外的函数接口。

11.9 Collapse Hierarchy ( 折叠继承体系 ) P_344

动机 : 超类和子类之间区别不大。
方法 : 将它们合为一体。

11.10 Form TemPlate Method ( 塑造模板函数 ) P_344

动机 : 你有一些子类,其中相应的函数以相同顺序执行类似的操作,但各个操作的细节有所不同。
方法 : 将这些小操作分别放进独立函数中,并保持它们都有相同的签名,于是原函数也变得相同了。然后将原函数上移至超类,运用多态来避免重复代码。这样的原函数就是 Template Method。
原因 : 虽然使用了继承,但是函数重复应尽量避免。

11.11 Replace inherited with Delegation ( 以委托取代继承 ) P_352

动机 : 某个子类只使用超类接口中一部分,或是根本不需要继承而来的数据
方法 : 在子类中新建一个字段用以保存超类,调整子类函数,令它委托超类,然后去掉两者之间的继承关系。

11.12 Replace Delegation with Inherited ( 以继承取代委托 ) P_352

动机 : 在两个类之间使用委托关系,并经常为整个接口编写许多极简单的委托函数,
方法 : 让委托类继承受托类。
注意 :