1

重构 (refactoring) 在不改变代码的外在的行为的前提下 对代码进行修改最大限度的减少错误的几率 本质上, 就是代码写好之后 修改它的设计。

1,书中开始用一个例子简单阐释为什么要重构,以及重构的好处

- 如果没有重构,程序就会腐败变质,程序逐渐失去自己的结构,

 越来越难通过读源码理解设计,不良重复的代码不便于后来的修改和阅读。

- 重构可以深入理解代码并且帮助找到bug。

    同时重构可以减少bug引入的机率,方便日后扩展。提高可理解性,
    降低修改成本。

- 提高编程的速度,良好的设计师快速开发的根本(阻止代码腐败,

 提高质量,从而提高开发速度)。
 sum:容易阅读,所有逻辑都只在唯一的地点指定,新的改动不会影响过去的功能,
 尽可能的表达条件逻辑。

----------

  • 1.2,重构的第一步就是建立一个有效的测试机制,好的测试机制可以帮助重构
  • 1.3,针对案例中的customer类的函数statement进行分析,这个函数太长,需要分解,拆分,因为简单的代码块容易管理。
    作者将这个statement函数里面的根据电影的类型返回不同的价格的方法,提炼出来,代码简洁易懂
    关于函数的名称的命名,作者总结道,只有写出人类容易理解的代码 才是优秀的代码。

    函数位置:函数应该放在它使用的数据的所属对象内,所以作者举例说明那个customer中的statement函数根据不同类的作用做了拆分,比如获取电影的价钱和积分数放在租赁类中,获取总电影价以及总积分放在Customer类中,这样逻辑清晰。


2,重构原则

 2.1,什么时候重构
   - 添加新功能是重构 
   - 修复bug时重构 
   - 评审代码是重构(结对编程)

重构小技巧:

  - 允许逻辑共享 
  - 分开解释意图或实现 
  - 隔离变化 例如一个类需要在2个地方使用,可以使用子类来处理变化 4)封装条件逻辑
  
2.2, 重构与设计
  前提简单可行的设计(不一定要完全正确), 重构可以预先取代设计,先把功能做好 然后重构 “极限编程” 支持这种观点,证明这种方法可行
 2.3,重构与性能
   时间预算 持续关注法(深度理解代码,并且知道哪些地方耗费时间)
   性能优化案例:
   大字符串操作耗费性能?
   字符串缓存起来 - 改用文件流处理 - 多处理器计算机,多线程 系统从运算1000个小时 到 2个小时的飞速提升
   

3,代码坏味道

重复代码
 - 同一个类 2个函数有相同的表达式 
 - 兄弟子类有相同的表达式 
 - 2个独立的类有相同的代码
3.2 函数过长
  分解函数的原则:每当感觉需要注释说明点什么的时候,我们需要把需要说明的东西写到一个独立的函数中,并以其用途来命名。如果代码前面有一条注释,就说明这个地方需要提炼出一个函数。

  条件表达式(多个if else 改成三元表达式 Decompose Conditional) 和循环也是提炼的信号,
  循环可以提炼到一个单独的函数中。

 3.3 过大的类 --解决重复代码的问题
 3.4 过长的参数列,建议使用参数对象,传递一个参数。
 3.5 发散式变化(一个类受到多种变化的影响),散弹式修改 一个变化引发多个类修改
 解决经常被修改的类,将经常需要修改的变量字段提炼到一个独立的类。

 3.6 依恋情节 数据和数据操作包装在一起 原则:将总是一起发生变化的东西放在一起。
 3.7 数据泥团 类中有相同的属性 函数中有类似的参数 删除一个不影响这个类或者函数的意义,建议拆分新对象 减少字段或者参数的个数
 3.8,switch statement
  大多情况下 出现switch语句 就应该考虑多态来替换,这样的switch需要提炼到一个独立的函数并且将它放到需要处理的类中。
 3.9 平行继承 目的在于消除类之间的重复代码。
 3.10 冗赘类 无用的代码类。
 3.11 暂时使用的字段 只有执行某个代码块才有意义的字段  抽到一个独立的类中
 3.12 过多的注释 试图用函数提炼代码,删除注释

4,构建测试体系

意义:一套测试就是一个强大的bug监听器 能够大大减少bug查找时间
建立一个独立的类用于测试并且在框架中运用它,使测试工作更加轻松
4.1 Junit 测试框架的运用
每收到一个bug,先写一个单元测试来暴露bug,考虑可能出错的边界条件,把测试的重点集中在那

5 重构列表
6.重构函数

重构的大部分集中在函数的重构,参数的重构
 内联函数 去除间接层
1,不要对参数赋值(Java 1.1 允许将参数定义为final)。 
2,如果临时变量被多次赋值,应该分解多个独立的临时变量 
3,分析临时变量是否被公用,把这个临时变量提炼到一个函数中(Replace temp with query) 
4,临时变量用于分解复杂的表达式 
5,替换算法,将比较复杂的算法替换成简单的 易懂的(或者将大的算法分成小部分 易于理解,然后寻找小算法的替换方案)

7,对象之间的搬移特性

1,函数搬移 -- 一个类函数过多 或者 函数依赖的类比较多 考虑这个函数与哪个类接触较多来决定搬移路径。
2,字段搬移 -- 随着业务的发展,发现一个类中的一个字段被多个函数或者类共同使用,考虑搬移这个字段或者可见度的改变(将这个字段变成public)
3,提炼类 -- 随着业务的发展 一个类中有太多的函数和字段 考虑把类似的字段和函数搬移出去。
4,内联化 -- 将一些不独立承担任务的类塞到另外一个独立类中,这种类称为萎缩类。
5, 委托关系,简单理解为封装函数,便于日后修改变得容易。


8,重新组织数据

1,自封装字段 简单理解就是将这个字段需要转换的值通过函数的方式表达 getBussinessVal();
2,以对象取代数据值 一个字段存在多种数据与行为一起使用才有意义时 建议将字段变成对象。
3,将值对象(数值)变成引用用对象(物体) 同一个对象被多个其他的类同时用到,比如 数据库连接,这个时候我们用工厂模式 进行复用来重构。
4,以对象取代数组, 方便用户记忆
5,将系统的魔法数定义为常量 static  final double WEIGHT_CONSTANT = 9.18;
6, 封装字段,将行为和意义类似的字段封装起来定义为private 提供public的访问函数
7,封装对象关联的集合,和数组  根据业务逻辑封装 list.add 改为emp.addEmp(); 将类似操作对象的集合统一集中到这个类中
8,运用多态继承取代switch,将涉及到switch的类型码判断 统一移动对象类里面处理
9,子类中不应该有常量 如果有在超类中申明抽象类 在子类实现 返回不同值


9,简化条件表达式

1,分解合并条件表达式 - 利用函数的方法 将多个判断条件根据业务逻辑封装成独立函数,用功能目的来命名,然后调用函数判断 例如 if (notData(params)){...} else {...}
2,合并上下重复的条件判断
3,循环逻辑用break 和 return 取代控制指标 比如 while(a<b){a.setA(true)} 这里用break直接跳出来,便于清晰逻辑判断
4,使用简单的if else 来判断逻辑,如果出现简单的if else 不能判断的,用独立的函数独立封装解决


10,简化函数调用
1,修改函数的名称 -- 首先考虑给这个函数写上一句怎样的注释,然后想办法将注释变成函数的名称
2,将查询函数和修改函数分离 如果发现一个函数有返回值又有副作用的函数 优化方法,将查询到的值缓存到某个字段中,这样后续的查询就可以加快速度
3,将2个类似的函数 通过函数合并 减少代码量
4,保持函数参数传递的对象完整 比如 in a = sumANums(b).getM(); 直接传递真个对象 plan.setCountNums(sumANums(b));
5, 尽量减少参数个数,建议传递一个对象作为参数
6,将一些经常一起传递的参数作为一个参数对象传递
7,移除设值函数,发现系统中有一个字段初始化被赋值一次然后一直不变 移除这个字段,并且直接修改这个字段为final
8,隐藏函数,更加这个函数是否被调用的次数判断是否设置某些值为private 和 public,尽可能的降低函数的可见度
9,以工厂函数取代构造函数 解决switch的类型码的判断

 例如:private int _type; static final int A = 1; static final int B = 2;
Employee(int type) {_type = type;}static Employee create(int type) { return new Employee(type)}

11, 处理概括关系

1,字段上移 两个类拥有相同的字段 将该字段移至超类
2,函数上移,两个函数,在各个子类中产生完全相同的结果,将该函数移至超类
3,构造函数上移,在子类的构造函数中调用它
4,函数、字段下移,相反
5,提炼接口,将两个接口相同的子集提炼到一个独立的接口中
6,以委托取代继承 随着义务的发展 超类中的一些函数你不需要,这时需要新建一个子类委托继承你需要的函数,然后再继承你需要的类

12,大型重构 关于继承的重构
13,重构 复用与现实


MichaelDuan
1.8k 声望39 粉丝