神话:一个管理很完善的软件项目,应该首先以系统化的方法进行需求开发,
定义一份严谨的列表来描述程序的功能。设计完全遵循需求,并且完成得相当仔
细,这样就让程序员的代码编写工作能够从头至尾饩线型地工作。这也表明绝大
多数代码117欠编写后就己完美,测试通过即可被抛到脑后。如果这样的神话是真
的,那么代码被修改的唯一时机就是在软件维护阶段,而这一阶段只会在系统的
最初版本交付用户之后。
现实情况:在初始开发阶段,代码会有实质性的进化。在初始的代码编写过
程中,就会出现很多剧烈的改变,如同在代码维护阶段可以看到的那样。根据项
目的规模不同,典型的项目花在编码、调试和单元测试上的时间会占到整个项目
的30%到65%不等〔请阅第27章“程序规模对构建的影响”〕。如果代码编写和
单元测试能够一帆风顺,这两个阶段所占整个项目时间的比例不会超过20%到30%。即
使是管理完善的项目,每个月都有大约1/4的需求发生变化。需求的变
化将不可避免地导致相关代码的改变一釘时是实质性的代码改变。
核对表(重构的理由)
- 代码重复
- 子程序太长
- 循环太长或者嵌套太深
- 类的内聚性太差
- 类的接口和抽象层次不一致
- 参数表中的参数太多
- 类的内部修改往往局限于某个部分
- 需要对多个类进行并行修改
- 对继承体系的并行修改
- 需要对多个case语句进行并行修改
- 相关的数据项只是被放在一起,没组织到类中
- 成员函数更多的使用了其他类的功能,而非自身类的
- 过于依赖基本数据类型
- 一个类不做什么事
- 一连串传递流浪数据的子程序
- 中间人对象什么也不干
- 某个类同其他类关系过于密切
- 子程序的命名太差
- 数据成员被设置为公用
- 派生类仅仅使用了基类的一小部分成员函数
- 用注释来掩饰拙劣的代码
- 使用了全局变量
- 在子程序调用前使用设置代码,调用前使用收尾代码
- 程序包含的某些代码似乎在将来某个时候才会被用到
核对表(重构总结)
数据级的重构
- 用具名常量来代替神秘数值
- 用更明确或者更具信息量的名字来重命名变量
- 将表达式内敛化
- 用函数来代替表达式
- 引入中间变量
- 将多用途变量转换为多个单一用途变量
- 使用局部变量实现局部用途而不是使用参数
- 将基础数据类型转化为类
- 将一组类型码转化为类或是枚举类型
- 将一组类型码转化为含派生类的类
- 将数组转化为对象
- 封装群集
- 用数据类替代传统记录
语句级的重构
- 分解布尔表达式
- 将复杂的布尔表达式转换为命名精确的布尔函数
- 将条件语句中不同部分中的重复代码合并
- 使用break或return而不是循环控制变量
- 在嵌套的if-then-else语句中一旦知道结果就立刻退出,而不是仅仅赋一个返回值
- 用多态来代替条件语句(尤其是重复地case语句)
- 创建并使用空对象代替对空值得检测
子程序级的重构
- 提取子程序
- 将子程序代码内联化
- 将冗长的子程序转化为类
- 用简单的算法替代复杂算法
- 增加参数
- 减少参数
- 将查询操作同修改操作区分开来
- 合并功能相似的子程序,并用参数来区分他们
- 通过传递不同的参数使子程序体现不同的功能
- 传递整个对象而非特定的成员
- 传递特定的成员而非整个对象
- 封装向下转型操作
类实现的重构
- 将值对象改为引用对象
- 将引用对象改为值对象
- 用数据初始化来代替虚函数
- 改变成员函数或数据的位置
- 将特定的代码提出生成派生类
- 将相似的代码合并起来放到基类中
类接口的重构
- 将某成员子程序放到另一个类中
- 将一个类转化成两个
- 删除某个类
- 隐藏委托关系
- 去掉中间人
- 用委托代替继承
- 用继承代替委托
- 引入外部子程序
- 引入扩展类
- 封装暴露在外的成员变量
- 对不能修改的成员去掉Set()函数
- 隐藏在类的外部不会使用的成员函数
- 封装不会用到的成员函数
- 如果基类和派生类的代码实现相似,将二者合一
系统级的重构
- 为无法控制的数据创建明确的索引源
- 将单向类联系改为双向类联系
- 将双向类联系改为单向类联系
- 使用工厂函数而非简单的构造函数
- 使用异常代替错误代码,或者反其道而行之
核对表(安全的重构)
- 每一改变都是系统改变策略的一部分吗?
- 在重构之前,你保存了初始代码了吗?
- 你是否保持较小的重构步伐?
- 你是否同一时间只处理一项重构?
- 在重构时你是否把要做得事情一条条列出来了吗?
- 你是否设置了一个停车场,把你在重构时所想到的任何东西记录下来?
- 在每次重构后你会重新测试吗?
- 如果所做的修改非常复杂,或者影响到了关键代码,你会重新检查这些修改吗?
- 你是否考虑过特定重构的风向,并以此来调整你的重构方法?
- 你所做的修改是提升还是降低了程序的内在质量?
- 你是否避免了将重构作为先写后改的代名词,或者作为拒绝重写拙劣代码的托词?
要点
- 修改是程序医生都要面对的事情,不仅包括最初的开发阶段,还包括首次发布之后
- 在修改中软件的质量要么改进,要么恶化。软件烟花的首要法则就是代码演化应当提升程序的内在质量
- 重构成功之关键在于程序员应该学会关注那些标志着代码需要重构的众多的警告或“代码臭味”
- 重构成功的另一个要素是程序员应当掌握大量特定的重构方法
- 重构成功的最后要点在于要有安全重构的策略。一些重构方法会比其他重构方法要好。
- 开发阶段的重构时提升程序质量的最佳时机,因为你可以立刻让刚产生的改变梦想编程现实。请珍惜这些开发阶段的天赐良机。
--》《重构,改善既有代码的设计》