重构 (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,重构 复用与现实
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。