在初始开发阶段,代码会有实质性的进化,会出现很多剧烈的改变。代码首次编写后就已经完美,测试通过即可抛到脑后的情况只是不存在的神话。

1、 软件演化的类型

软件演化就像生物进化一样,有些突变是有益的,有些则是有害的。区分软件演化类型的关键是程序的质量在这一过程中是提高了还是降低了,演化是源于程序构建中的修改,还是维护过程中的修改。

演化一开始就充满危险,但同时也是是你的软件开发接近完美的天赐良机。一旦有机会重新审视你的程序,就用自己的全部所学去改进它。

2、重构简介

重构是指在不改变软件外部行为的前提下,对其内部结构进行改变,使之更容易理解并便于修改。重构是实现软件演化基本准则的最关键策略。

标志着程序需要重构的信号:

  • 代码重复
  • 冗长的子程序
  • 循环过长或嵌套过深
  • 内聚性太差的类
  • 类的接口未能提供层次一致的抽象
  • 拥有太多参数的参数列表
  • 类的内部修改往往被局限于某个部分
  • 变化导致对多个类的相同修改
  • 对继承体系的同样修改
  • case语句需要做相同的修改
  • 同时使用的相关数据并未以类的方式进行组织
  • 成员函数使用其他类的特征比使用自身类的特征还要多
  • 过多使用基本数据类型
  • 某个类无所事事
  • 一系列传递流浪数据的子程序
  • 中间人对象无事可做
  • 某个类同其他类关系过于亲密
  • 子程序命名不恰当
  • 数据成员被设置为公用
  • 某个派生类仅使用了基类的很少一部分成员函数
  • 注释被用于解释难懂的代码
  • 使用了全局变量
  • 在子程序调用前使用了设置代码,调用后使用了收尾代码
  • 程序中的一些代码似乎是在将来的某个时候才会用到

超前设计的问题:

  • 超前设计的代码,需求不可能定义得很完备
  • 即使程序员对未来需求的前瞻几近完全准确,他也不可能广泛预见未来需求的所有复杂脉络
  • 使用超前设计代码的未来程序员们并不值得自己手中的代码原本是经过超前设计的,或许他们会期望这些代码能比实际情况表现更好
  • 超前设计的代码是画蛇添足,增加了程序的复杂性。

3、特定的重构

  • 数据级的重构

    • 用具名常量替代神秘数值
    • 使变量的名字更为清晰且传递更多信息
    • 将表达式内联化
    • 用函数来代替表达式
    • 引入中间变量
    • 用多个单一用途变量代替某个多用途变量
    • 在局部用途中使用局部变量而不是参数
    • 将基础数据类型转化成类
    • 将一组类型码转化为类或枚举类型
    • 将一组类型码转化为一个基类机器相应派生类
    • 将数组转化成对象
    • 把集群封装起来
    • 用数据类来代替传统记录
  • 语句级的重构

    • 分解布尔表达式
    • 将复杂布尔表达式转化成命名准备的布尔函数
    • 合并条件语句不同部分中的重复代码片段
    • 使用break或return而不是循环控制变量
    • 在嵌套的if-then-else语句中一旦知道答案就立即返回,而不是去赋一个返回值
    • 用多态来替代条件语句,尤其是重复的case语句
    • 创建和使用null对象而不是去检测空值
  • 子程序级重构

    • 提取子程序或者方法
    • 将子程序的代码内联化
    • 将冗长的子程序转化为类
    • 用简单算法替代复杂算法
    • 增加参数
    • 删除参数
    • 将查询操作从修改操作中独立出来
    • 合并相似的子程序,通过参数区分他们的功能
    • 将行为取决于参数的子程序拆分开来
    • 传递整个对象而非特定成员
    • 传递特定成员而非整个对象
    • 包装向下转型的操作
  • 类实现的重构

    • 将值对象转化为引用对象
    • 将引用对象转化为值对象
    • 用数据初始化替代虚函数
    • 改变成员函数或成员数据的位置

      减少派生类的重复工作:

      • 将子程序上移到基类中
      • 将成员上移到基类中
      • 将构造函数中的代码上移到基类中

      对派生类进行特殊化

      • 将子程序下移到派生类中
      • 将成员下移到派生类中
      • 将构造函数下移到派生类中
    • 将特殊代码提取为派生类
    • 将相似的代码结合起来放置到积累中
  • 类接口的重构

    • 将成员函数放到另一个类中
    • 将一个类变成两个
    • 删除类
    • 去除委托关系
    • 去掉中间人
    • 用委托代替继承
    • 用继承代替委托
    • 引入外部的成员函数
    • 引入扩展类
    • 对暴露在外的成员变量进行封装
    • 对于不能修改的类成员,删除相关的set成员函数
    • 隐藏不会在类之外被用到的成员函数
    • 封装不适用的成员函数
    • 合并那些实现非常类似的基类和派生类
  • 系统级重构

    • 对无法控制的数据创建明确的索引源
    • 将单向的类联系改为双向的类联系
    • 将双向的类联系改为单向的类联系
    • 用Factory Method模式而不是简单的构造函数
    • 用过一场取代错误处理代码,或者做相反方向的变换

4、安全的重构

  • 避免错误的使用重构

    • 保存初始代码
    • 重构的步伐请小些
    • 同一时间只做一项重构
    • 把要做的事情一条条列出来
    • 设置一个停车场
    • 多使用检查点
    • 利用编译器警告信息
    • 重新测试
    • 增加测试用例
    • 检查对代码的修改
    • 根据重构的风险级别来调整重构方法
  • 不宜重构的情况

    • 不要把重构当做先写后改的代名词
    • 避免用重构代替重写

5、 重构策略

  • 在增加子程序时进行重构
  • 在添加类的时候进行重构
  • 在修补缺陷的时候进行重构
  • 关注易于出错的模块
  • 关注高度复杂的模块
  • 在维护环境下,改善你手中正在处理的代码
  • 定义清楚干净代码和拙劣代码之间的边界,然后把代码移过边界

更多有关《代码大全 2》的读书笔记,请关注 :
http://tabalt.net/blog/code-complete-2-reading-notes/

本文链接:http://tabalt.net/blog/cc2-refactoring/,转载请注明。