本书针对单进程软件,书中的重构方法不一定适用于分布式软件
第一章 ,重构——第一个案例
- 代码结构使得添加新特性异常困难,那么需要重构代码,使特性的添加容易进行,然后再添加新特性。
- 重构的第一步是编写可靠的测试机制,这些测试必须有自我检查能力。
- 重构时最好小步前进,降低犯错几率。重构的节奏:小修改,测试,小修改,测试......
第二章,重构原则
1.何谓重构
对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
重构和添加新功能是两种不同的行为,不能同时进行。重构后再添加新功能。
2.为何重构
- 重构使软件更容易理解
- 重构帮助找到bug
- 重构提高编程速度
3.何时重构
- 三次法则
事不过三,三则 重构 - 添加功能时重构
- 修补错误时重构
- 复审代码时重构
4.重构的难题
- 数据库
在对象模型和数据库模型之间插入分隔层 - 修改接口
保留旧接口,让旧借口调用新接口。
不要过早发布接口。 - 何时不该重构
重写:现有代码无法正常运作。
重构大块头为各小组件,重写小组件。
项目最后期限时,避免重构。 - 重构与设计
重构减轻了设计的压力,可以不断优化原定设计方案。 - 重构与性能
重构优化代码结构,使得在性能优化阶段对软件的调整更容易。
针对10%的热点进行性能优化。
第三章,代码的坏味道
1.重复代码
Extract method/class
Form Template Method
2.过长函数
Extract method
Replace Temp with Query消除临时变量
Introduce Parameter Object,Preserve whole Object缩减参数
Replace Method with Method Object
Decompose Conditional
3.Large Class 大类
Extract Class/SubClass/Interface
4.Long Parameter Lists过长的参数列
Introduce Parameter Object
Preserve whole Object
5.Divergent Change发散式变化
一个类受多种变化的影响
Extract Class将变化提炼到另一个类中
6.Shotgun Change
一种变化引发多个类的修改
Move Method/Field将需要修改的代码放到一个类中
7.Feature Envy
函数同时依赖几个类的数据
Extract Method分解函数
8.Data Clumps数据泥团
总是同时出现的数据对
Introduce Parameter Object
Preserve whole Object
9.Primitive Obsession基本类型偏执
Replace DataValue with Object
Replace Type Code with Class/Strategy/State
10.Switch Statements
Replace Conditional with Polymorphism
Replace Parameter with Explicit Methods
Introduce Null Object
11.Parallel Inheritance Hierarchies平行继承体系
相似类
让一个类引用另一个类的实例,Move Method/Field
12.Lazy Class冗余类
Collapse Hierarchy, Inline Class
13.Speculative Generality
删除多余的函数、参数、类、接口
Collapse Hierarchy, Inline Class
14.临时字段
Extract Class将变量和相关函数提炼到一个类中
15.Message Chains过度耦合的消息链
Hide Delegate, Extract Method, Move Method
16.Middle Man
Inline Method, Remove Middle Man, Replace Delegation with Inheritance
17.Inappropriate Intimacy
耦合类
Move Methods/Field
Extract Class
Replace Inheritance with Delegation
18.Alternative Classes with Different Interfaces
Rename/Move Method
Extract SuperClass
19.Incomplete Library Class
修改类库函数
Introduce Foreign Method
Introduce Local Extension
20.Data Class
封装、隐藏数据类字段、容器、函数
21.Refused Bequest子类拒绝部分父类接口/实现
Replace Inheritance with Delegation
22.Comments
通过命名、调用关系代替注释
Extract Method
Rename Method
Introduce Assertion
第四章,构筑测试体系
- 自动化测试,自动化检查结果
- JUnit测试框架
- 针对容易出错的地方编写测试用例
- 频繁运行测试
- 花合理的时间找出大部分bug
第六章,重新组织函数
1.Extract Method
- 动机:精简过长函数Long Method
- 方法:提炼代码,组织新函数,测试
- 要点:
1.局部变量的处理
局部变量作为新函数的传入参数
局部变量作为新函数的返回值
2.函数命名
2.Inline Method
- 动机:函数代码与函数名称同样清晰易懂; 重新整理大型函数前使用该方法
- 方法:在函数调用点插入函数本体,然后移除该函数
- 要点:函数不具有多态性
3.Inline Temp内联临时变量
- 动机:作为其他重构方法的一部分
- 方法:将所有对该变量的引用动作,替换为对它赋值的那个表达式自身
4.Replace Temp with Query以查询取代临时变量
- 适用情况:代码中以一个临时变量保存某一表达式的运算结果结果,该表达式被多次调用
- 方法:将表达式提炼到一个独立函数中。在调用临时变量的地方调用新函数。
5.Introduce Explaining Variable引入解释性变量
- 使用情况:复杂表达式
- 方法:将复杂表达式的一部分结果放入final临时变量,以此变量名解释表达式用途
- 要点:当使用Extract Method需要花费大量工作时使用此方法
6.分解临时变量
- 使用情况:临时变量被多次赋值,它不是循环变量,也用于手机计算结果
- 方法:针对每次赋值,创造一个独立的、对应的临时变量
7.Remove Assignments to Parameters
- 适用情况:代码对传入参数赋值
- 方法:以临时变量取代该参数的位置
- 要点:针对被传入对象被修改为另一个对象,而非修改传入对象属性的情况。Java采用按值传递。
Java 对象的引用本质上是按值传递的。因此可以修改参数对象的内部状态,但对参数对象重新赋值是不没有意义的。
8.Replace Method with Method Object
- 适用情况:大型函数局部变量无法拆解
- 方法:将函数包装成对象,将局部变量变成对象中的字段,将大函数在对象中拆解成多个小函数。
9.替换算法
- 方法:将某个算法替换为另一个更清晰的算法
第七章,在对象之间搬移特性
1.Move Method
- 适用情况:类函数与另外的类有许多交流
- 方法:将函数搬移至另一个类或建立委托函数
2.Move Field
- 适用情况:类字段在别的类中多次使用
- 方法:将字段搬移至另一个类
3.Extract Class
- 适用情况:类做了两件事
- 方法:建立一个新类
4.Inline Class
- 使用情况:类没有做太多事情
- 方法:将类的所有特性搬移到另一个类中
5.Hide Delegate 隐藏委托关系
- 使用情况:客户通过一个委托类调用另一个对象的方法
- 方法:在服务类上建立客户所需的所有函数,用以隐藏委托关系
原:
manager = john.getDepartment().getManager();
新:
public Persion getManager(){
reutrn _department.getManager()
}
6. Remove Middle Man移除中间件
- 适用情况:某个类做了过多简单委托动作
- 方法:让客户直接调用受委托类
7. Introduce Foreign Method
- 适用情况:修改库函数
- 方法:将对库函数的操作封装成函数
8. Introduce Local Extension引入本地扩展
- 适用情况:修改库函数
- 方法:建立新类扩展库函数
- 要点:子类化或包装
第八章,重新组织数据
1.Self Encapsulate Field自封装字段
- 使用情况:如何访问字段,直接访问或取值函数
- 方法:为字段添加取值/设置函数
2.Replace Data Value with Object
- 使用情况:简单数据项的扩展
- 方法:创建新类,用对象取代数据
3.Replace Value to Reference值对象改为引用对象
- 使用情况:多个类对象共享同一个对象,每个类包含一个值对象
- 方法:使用工厂函数、注册表或静态字典管理应用对象
4.Replace Reference to Value
- 适用情况:引用对象不可改变,很小
- 要点:将不可变得引用对象重构为值对象
- 方法:定义类的equal(),hashCode()
5.Replace Array with Object
- 使用情况:数组包含多种不同的对象
- 方法:新建类,数组元素对应新类字段
6.Duplicate Observed Data
- 使用情况:数据与GUI分离
- 方法:MVC模式,观察者模式,事件监听器
7.单向关联到双向关联
- 使用情况:两个类都需要使用对方的特性
- 方法:添加反向指针,使修改函数能同时更新两条关联线。
- 要点:选择控制关系的类
1.一对多:单一引用类承担控制角色
2.多对多:都可以
3.部件类:主类控制
8.双向关联到单向关联
- 使用情况:一个类不再使用另一个类的特性
9.字面常量取代魔法数
- 使用情况:字面数值
- 方法:为数值创建一个常量,并命名
10.封装字段
- 适用情况:类中存在public字段
- 方法:将字段声明为private,设置取值/设置函数
11.封装集合
- 适用情况:函数返回一个集合
- 方法:返回集合的只读副本,在类中提供添加/移除集合元素的函数
12.数据类取代记录
- 方法:新建类表示记录,类字段对应记录元素
13.以类取代类型码
- 使用情况:类中有数值类型码,不影响类的行为
- 动机:编译器类型检查
- 方法:以新类取代类型码
14.以子类取代类型码
- 适用情况:类型码影响类的行为,switch/if else
- 不适用情况:类型码宿主类已经有了子类,类型码值在对象创建之后发生了改变
- 动机:改条件判断为多态的铺垫
15.以State/Strategy取代类型码
- 适用情况:无法通过继承消除的类型码
- 方法:新建状态抽象类,及每个类型码对应的子类
16.以字段取代子类
- 适用情况:子类的差别只在“返回常量数据”的函数上
- 方法:删除子类,在基类中添加字段表示各子类区别
第九章,简化条件表达式
1.分解条件表达式
- 适用情况:复杂的条件表达式
- 方法:提炼独立函数
- 动机:突出条件逻辑,通过函数名说明逻辑
2.合并条件表达式
- 适用情况:不同的条件指向相同结果
- 方法:合并条件
3.合并重复的分支片段
4.移除控制标记
- 方法:使用break,return取代控制flag
5.Replace Nested Conditional with Guard Clauses
- 适用情况:嵌套的条件判断
- 方法:单独检查条件并返回
6.多态取代条件表达式
- 适用条件:不同的对象类型对应不同的行为
- 方法:创建子类代表不同类型的对象,重写行为函数
7.引入Null对象
- 试用情况:需要多次检查对象是否存在
- 方法:将null值替换为null对象。创建Null子类或实现nullalbe接口
8.引入断言
- 适用条件:代码的执行建立在某种假设之上。如果假设不成立,说明程序运行出现问题
- 方法:使用断言明确表现这种假设
- 要点:断言作为调试的辅助,在正常情况下,断言不影响程序运行。
第十章,简化函数调用
1.Rename Method
- 要点:函数名取代注释
2.添加参数
3.移除参数
4.Seperate Query from Modifier
- 适用情况:某个函数既返回对象状态值,又修改对象状态
- 方法:建立两个不同的函数,一个负责查询,一个负责修改
5.Parameterize Method
- 使用情况:不同的函数作用相同,只有代码中的部分参数不同
- 方法:建立一个函数,以参数表示那些不同的值
6.以明确函数取代参数
- 适用情况:函数参数影响函数行为
- 方法:针对不同参数创建不同函数
7.保持参数中的完整对象
- 适用情况:参数表的几个值来自同一对象
- 方法:传递整个对象
8.函数取代参数
- 使用情况:函数传入参数是另一个函数的结果,可以将参数取代为函数,放入调用函数中。
9.引入参数对象
- 适用情况:参数总是成对出现
- 方法:将同时出现的参数包装成对象
10.去除设值函数
- 适用情况:某个字段只有对象初始化时被设值
- 方法:删除字段的设值函数
11.隐藏函数
- 适用情况:函数没有被其他类用到
12.工厂函数替代构造函数
- 动机:派生子类的过程中以工厂函数取代类型码
- 方法: 新建工厂函数,调用构造函数
13.封装向下转型
- 适用条件:使用函数返回结果时,进行向下转型
- 方法:将向下转型动作搬移到函数中
14.异常取代错误码
- 方法:try..catch , throw
15.测试取代异常
- 要点:预料之内可以检测的异常使用条件判断避免
第十一章,处理概括关系
1.字段上移
- 条件:不同的子类使用相同字段
- 方法:字段移动到父类中
2.函数上移
- 条件:不同的子类包含相同的函数
3.构造函数上移
- 条件:子类拥有类似的构造函数
- 方法:将构造函数中重合的部分移动至基类构造函数
4.函数下移
- 条件:只有部分子类使用到超类的某个函数
- 方法:将函数移动到相关子类中
5.字段下移
6.提炼子类
- 条件:因类型码而导致类中的部分函数只被某些实例使用
- 方法:创建某种类型对应的子类
7.提炼超类
- 条件:不同的类拥有部分相似行为
8.提炼接口
- 条件:不同的类拥有相同的接口,客户调用不同类的相同接口
- 方法:类实现接口,客户调用接口函数
9.折叠继承关系
- 条件:父类与子类无太大区别
- 方法:合并
10.Form Template Method
- 条件:不同子类拥有类似函数,函数以相同的顺序调用不同实现的函数
- 方法:将函数移动至父类,子类实现函数中不同的调用函数
11.委托取代继承
- 条件:子类只使用父类的部分字段或函数
- 方法:取消继承关系,新类中使用字段引用原父类对象
12.Replace Delegation with Inheritance
- 条件:委托类使用受托类的所有函数
第十二章,大型重构
1.Tease Apart Inheritance梳理并分解继承体系
- 条件:某个继承体系同时承担两项责任
- 方法:建立两个继承体系,通过委托关系是一方调用另一方
2.Convert Procedural Design to Objects
- 过程化代码→面向对象代码
3.Separate Domain from Presentation
- 条件:GUI类中包含业务逻辑
4.建立继承体系
- 条件:某个类内部有大量条件表达式
- 方法:以子类表示不同的特殊情况
网友评论