第二十四章 重构
重构简介
重构定义:
- 在不改变软件外部行为的前提下,对其内部结构进行改变,使之更容易理解以便于修改;
- 尽可能地将一个程序分解为多个组成部分。
重构的理由
- 代码重复;
- 冗长的子程序;
- 循环过长或嵌套过深;
- 类的接口未能提供层次一致的抽象;
- 拥有太多参数的参数列表;
- 类的内部修改往往被局限于某个部分;
- 变化导致对多个类的相同修改;
- 对继承体系的同样修改;
case
语句需要做相同的修改;- 同时使用的相关数据并未以类的方式进行组织;
- 成员函数使用其他类的特征比使用自身类的特征还要多;
- 过多使用基本数据类型;
- 某个类无所事事;
- 一系列传递流浪数据的子程序;
- 中间人对象无事可做;
- 某个类同其他类关系过于亲密;
- 子程序命名不当;
- 数据成员被设置为公用;
- 某个派生类仅使用了基类的很少一部分成员函数;
- 注释被用于解释难懂的代码;
- 使用了全局变量;
- 在子程序调用前使用了设置代码,或在调用后使用了收尾代码;
- 程序中的一些代码似乎是在将来的某个时候才会用到的。
特定的重构
数据级的重构
- 用具名常量替代神秘数值;
- 使变量的名字更为清晰且传递更多信息;
- 将表示内联化;
- 用函数来代替表达式;
- 引入中间变量;
- 用多个单一用途变量代替某个多用途变量;
- 在局部用途中使用局部变量而不是参数;
- 将基础数据类型转化为类;
- 将一组类型码转化为类或枚举类型;
- 将一组类型码转换为一个基类及其相应派生类;
- 将组数转换为对象;
- 把集群封装起来;
- 用数据类来代替传统记录。
语句级的重构
- 分解布尔表达式;
- 将复杂布尔表达式转换成命名准确的布尔表函数;
- 合并条件语句不同部分中的重复代码片段;
- 使用
break
或return
而不是循环控制变量; - 在嵌套的
if-then-else
语句中一旦知道答案就立即返回,而不是去赋一个返回值; - 用多态来替代条件语句;
- 创建和使用
null
对象而不是去检测空值。
子程序级重构
- 提取子程序或者方法;
- 将子程序的代码内联化;
- 将冗长的子程序转换为类;
- 用简单算法替代复杂算法;
- 增加参数;
- 删除参数;
- 将查询操作从修改操作中独立出来;
- 合并相似的子程序,通过参数区分他们的功能;
- 将行为取决于参数的子程序拆分开来;
- 传递整个对象而非特定成员;
- 传递特定成员而非整个对象;
- 包装向下转型的操作。
类实现的操作
- 将值对象转化为引用对象;
- 用数据初始化代替虚函数;
- 改变成员函数或成员数据的位置;
- 将特数代码提取为派生类;
- 将相似的代码结合起来放置到基类中。
类接口的重构
- 将成员函数放到另一个类中;
- 将一个类变成两个;
- 删除类;
- 去除委托关系;
- 去掉中间人;
- 用委托代替继承;
- 引入外部成员函数;
- 引入扩展类;
- 对暴露在外的成员变量进行封装;
- 对于不能修改的类成员,删除相关的
Set()
成员函数; - 隐藏那些不会在类之外被用到的成员函数;
- 封装不使用的成员函数;
- 合并那些实现非常类似的基类和派生类。
系统级重构
- 为无法控制的数据创建明确的索引源;
- 将单向的类联系改为双向的类联系;将双向的类联系改为单向的类联系;
- 用Factory Method模式而不是简单地构造函数;
- 用异常取代错误处理代码,或者做相反方向的变换。
安全的重构
- 保存初始代码;
- 重构的步伐请小些;
- 把要做的事情一条条列出来;
- 设置一个停车场;
- 多使用检查点;
- 利用编译器警告信息;
- 重新测试;
- 增加测试用例;
- 检查对代码的修改;
- 根据重构风险级别来调整重构方法;
不宜重构的情况
- 不要把重构当做险些后改的代名词;
- 避免用重构代替重写。
重构策略
- 在增加子程序时进行重构;
- 在添加类的时候进行重构;
- 在修补缺陷的时候进行重构;
- 关注易于出错的模块;
- 关注高度复杂的模块;
- 在维护环境下改善你手中正在处理的代码;
- 定义清楚干净代码和拙劣代码之间的边界,然后尝试把代码移过这条边界。
要点
- 修改是程序一生都要面对的事情,,不仅包括最初的开发阶段,还包括首次发布之后;
- 在修改中软件的质量要么该井,要么恶化。软件演化的首要法则就是代码演化应当提升程序的内在质量;
- 重构成功之关键在于程序员应学会关注那些标志着代码需要重构的众多的警告;
- 重构成功的另一要素是程序员应当掌握大量特定的重构方法;
- 重构成功的最后要点在于要有安全的重构策略;
- 开发阶段的重构是提升程序质量的最佳时机,因为你可以立刻让刚刚产生的改变梦想变成现实。