1.概念及步骤
1.1 什么是重构
重构:在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构。 提高其可理解性,降低维护成本。
本质:在代码写好之后改进它的设计。
1.2 重构步骤
1.为即将修改的代码建立一组可靠的测试环境。
2.分解重组
2.重构原则
2.1 两顶帽子
使用重构技术开发软件时,把时间分配给两种截然不同的行为:添加新功能,以及重构。
添加新功能时,不应该修改既有代码,只管添加新功能。
重构时不能再添加功能,只管改进程序结构。
2.2 为何重构
1.改进软件设计
2.使软件更容易理解
3.帮助找到bug
4.提高编程速度
2.3 何时重构
1.三次法则
事不过三,三则重构
2.添加功能时
如果你发现自己需要为程序添加一个特性,而代码结构使你无法很方便
地达成目的,那就先重构那个程序,使特性的添加比较容易进行,然后
再添加特性.
3.修补错误时
4.复审代码时
2.4 间接层和重构
间接层的价值
1.允许逻辑共享
2.分开解释意图和实现
3.隔离变化
4.封装逻辑条件
2.5 重构的难题
1.数据库
- 对象数据库难点:应用和数据库的紧密耦合,数据迁移
- 非对象数据库:在对象模型和数据库模型之间插入一个分割层,隔离两个模型各自的变化。
2.修改接口
- 保留旧接口,让旧接口调用新接口
- 除非必要否则不要发布接口
3.难通过重构手法完成的设计改动
4.何时不该重构
- 现有代码根本不能正常运作
- 项目接近最后期限
2.6 重构与设计
重构和设计彼此互补。
重构可以带来更简单的设计。
2.7 重构与性能
短期看来,重构的确可能使软件变慢,
但它使优化阶段的软件性能调整更容易,最终还是会得到好的效果。
3.代码的坏味道
3.1 Duplicated Code (重复代码)
3.1.1 常见情况
1.同一个类的两个函数含有相同的表达式
2.连个互为兄弟的子类内含相同的表达式
3.两个毫不相关的类出现Duplicated Code
3.1.2 常用方法
- Extract Method (110)
- Pull Up Method (332)
- Form Template Method (345)
- Substitute Algorithm (139)
- Extract Class (149)
3.2 Long Method (过长函数)
3.2.1 提炼信号
- 注释处
- 条件表达式和循环
3.2.2 常用方法
- Extract Method (110)
- Replace Temp with Query (120)
- Introduce Parameter Object (295)
- Preserve Whole Object (288)
- Replace Method with Method Object (135)
- Decompose Conditional (238)
3.3 Large Class (过大的类)
3.3.1 现象
1.太多实例变量
2.太多代码
3.3.2 常用方法
- Extract Class(149)
- Extract Subclass(330)
- Extract Interface(341)
- Duplicate Observed Data(189)
3.4 Long Parameter List (过长参数列表)
3.4.1 常用方法
- Replace Parameter with Method (292)
- Preserve Whole Object (288)
- Introduce Parameter Object (295)
3.4.2 例外
不希望造成被调用对象与较大对象间的某种依赖关系。
3.5 Divergent Change (发散式变化)
3.5.1 现象
某个类经常因为不同的原因在不同的方向上发生变化。即一个类受多种变化的影响。
3.5.2 常用方法
- Extract Class(149)
3.5.3 观点摘录
针对某一外界变化的所有相应修改,都只应该发生在单一类中,
而这个新类内的所有内容都应该反应此变化。
3.6 Shotgun Surgery (散弹式修改)
3.6.1 现象
一种变化引起多个类修改。
3.6.2 常用方法
- Move Method(142)
- Move Field(146)
- Inline Class(154)
3.7 Feature Envy (依恋情结)
3.7.1 现象
函数对某个类的兴趣高过对自己所处类的兴趣。
3.7.2 常用方法
- Move Method(142)
- Extract Method (110)
3.7.3 观点摘录
对象技术的全部要点在于:将数据和对数据的操作行为包装在一起。
将总是一起变化的东西放在一块。如果一个方法中用到多个类的数据那么将该方法移动到拥有最多使用数据的那个类中。
3.8 Data Clumps (数据泥团)
3.8.1 现象
两个类中相同的字段、许多函数签名中相同的参数。
3.8.2 常用方法
- Extract Class(149)
- Introduce Parameter Object (295)
- Preserve Whole Object (288)
3.9 Primitive Obsession (基本类型偏执)
3.9.1 现象
存在很多基本类型
3.9.2 常用方法
- 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)
- Extract Class (149)
- Introduce Parameter Object (295)
- Replace Array with Object (186)
3.9.3 观点摘录
对象的一个极大的价值在于:它们模糊(甚至打破)了横亘于基本数据和体积
较大的类之间的界限。
3.10 Switch Statements (switch语句)
3.10.1 现象
面向对象程序的一个显著特征:少用switch(case)语句。
switch语句的问题在于重复。
出现switch考虑用多态。
3.10.2 常用方法
- Extract Method (110)
- Move Method (142)
- Replace Type Code with Subclasses (223)
- Replace Type Code with State/Strategy (227)
- Replace Conditional with Polymorphism (255)
- Replace Parameter with Explicit Methods (285)
- Introduce Null Object (260)
3.11 Parallel Inheritance Hierarchies (平行继承体系)
3.11.1 现象
当为某个类增加一个子类必须也要为另一个类增加一个子类。
某个继承体系的类名前缀和另一个继承体系的类名前缀完全相同。
3.11.2 常用方法
消除这种重复性的一般策略:让一个继承体系的实例引用另一个继承体系的实例。
- Move Method(142)
- Move Field(146)
3.12 Lazy Class (冗赘类)
3.12.1 现象
一个类的所得不值其身价,它就应该消失。
3.12.2 常用方法
- Collapse Hierarchy(344)
- Inline Class(154)
3.13 Speculative Generality (夸夸其谈未来性)
3.13.1 现象
以各种各样的钩子和特殊情况来处理一些非必要的事情。
3.13.2 常用方法
- Collapse Hierarchy(344)
- Inline Class(154)
- Remove Parameter(277)
- Rename Method(273)
3.14 Temporary Field (令人迷惑的暂时字段)
3.14.1 现象
类中某个实例变量只为某种特殊情况而设。
3.14.2 常用方法
- Extract Class (149)
- Introduce Null Object (260)
3.15 Message Chains (过度耦合的消息链)
3.15.1 现象
一长串getThis()或一长串临时变量。
3.15.2 常用方法
- Hide Delegate(157)
- Extract Method(110)
- Move Method(142)
3.15.3 建议
先观察消息链最终得到的对象是用来干什么的,看看能否以Extract Method (110)把使用该对象的代码提
炼到一个独立函数中,再运用Move Method (142)把这个函数推入消息链。
3.16 Middle Man (中间人)
3.16.1 现象
某个类接口有一半的函数都委托给
其他类。
3.16.2 常用方法
- Remove Middle Man (160)
- Inline Method (117)
- Replace Delegation with Inheritance (355)
3.17 Inappropriate Intimacy (狎昵关系)
3.17.1 现象
两个类太过亲密,花费太多时间去探究彼此的private成分。
3.17.2 常用方法
- Move mehtod(142)
- Move Field(146)
- Change Bidirectional Association to Unidirectional (200)
- Extract Class (149)
- Hide Delegate (157)
- Replace Inheritance with Delegation (352)
3.18 Alternative Classes with Different Interfaces (异曲同工的类)
3.18.1 现象
两个函数做同样的事,却有着不同的签名。
3.18.2 常用方法
- Rename Method(273)
- Move Method(142)
- Extract Superclass(336)
3.19 Incomplete Library Class (不完美的库类)
3.19.1 现象
库造的不够好,而且往往不可能让我们修改其中的类。
3.19.2 常用方法
- Introduce Foreign Method(162)
- Introduce local Extention(164)
3.20 Data Class (纯稚的数据类)
3.20.1 什么是Data Class
所谓Data Class是指:它们拥有一些字段,以及用于访问(读写)这些字段的函
数,除此之外一无长物。这样的类只是一种不会说话的数据容器。
3.20.1 常用方法
- Encapsulate Field (206)
- Encapsulate Collection (208)
- Remove Setting Method (300)
- Move Method (142)
- Extract Method(110)
- Hide Method (303)
3.21 Refused Bequest (被拒绝的遗赠)
3.21.1 现象
子类复用了超类的行为(实现),却又不愿意支持超类的接口.
3.21.2 常用方法
- Push Down Method (328)
- Push Down Field (329)
- Replace Inheritance with Delegation (352)
3.22 Comments (过多的注释)
3.22.1 现象
因代码糟糕而存在的长长注释。好的代码时自解释的。
Comments可以带我们找到各种坏味道。
3.22.2 常用方法
- Extract Method (110)
- Rename Method (273)
- introduce Assertion (267)
3.22.3 观点摘录
当你感觉需要撰写注释时,请先尝试重构,试着让所有注释都变得多余。
4.构筑测试体系
测试的要诀:测试你最担心出错的地方。