再读《重构-改善既有代码的设计》

来源:互联网 发布:03年搞笑网络歌曲 编辑:程序博客网 时间:2024/06/06 04:31
利用周末,一个月左右的时间,将重构这本书又翻了一遍,也重新写了一遍读书笔记,相比第一遍阅读,很多东西理解得更深入了,局限于本人经验,查阅资料仍不是很明确的地方,加上自己的理解说明。

第二章

第二章主要是将了重构的概念
重构有名词和动词两种解释。
当作名词理解,是对软件内部的调整,在不改变软件功能的前提下,提高代码的可理解性,降低代码维护成本。
当作动词理解,就是使用重构手法,在不改变软件功能前提下,调整代码结构。

重构时候不应该修改其功能或者修改代码的错误。因为这样容易引入新的bug,应该记录下来,重构后再维护。
为何重构:
1. 重构改进软件设计
2. 重构是软件更容易理解
3. 重构帮助找到Bug
4. 重构提高编程速度

重构的作用:
平时进行维护时候也能体会出来。好的代码,代码调理清晰,功能分明,在调整功能或者修复bug的时候,很容易找到修改点,也更容易阅读。相反,差的代码,写的时候不需要多少思考,但是别人在维护的时候,很难通过代码了解功能,可能还需要重构后再作调整。
代码重构的时机:
重构应该随时进行,不应该为了重构而重构。重构应该是做其他事情的工具,比如在添加功能时,修复bug时候,复审代码时。如果代码太过混乱,需要评估是否重写代码重构,评估的标准可以定为现有代码无法稳定运行,错误较多。重构的前提应该是现有代码大部分情况下能够正常运行,并且的代价小于重写。

重构与设计的关系:
重构与设计彼此互补,重构不应代替设计,但是没有完美的设计,初始的设计可能在编程过程中,随着问题理解得加深,需要重构调整设计,使其结构更清晰,更容易维护。
重构与性能的关系:
作者认为,不能为了更好的设计,忽视性能,重构有个好处,就是可以使代码更容易调整,然后调整其使其性能更好。

第三章

本章列出了一些代码的坏味道,当发现这些代码时,应该重构。

  1. 重复代码。重复代码是比较不能容忍的,如果在一个以上地方看到相同的程序结构,就应该想办法调整,将其提取合并。有的时候我也会犯这样的错,其实是欠缺思考,比如相同的功能没有共用一套代码,在调整功能的时候,就需要调整两处以上,容易漏掉,而且代码臃肿,不美观。如果代码只是部分类似,就应该提取相同的部分,尽量消除重复代码。
  2. 过长函数。函数过长问题可能是其中包含较多功能,复用性差,且函数越长越难阅读,不符合面向对象的精神。需要找到函数中适合几种在一起的部门,提炼出来,将函数拆分。
  3. 过大的类。一个类做太多的事情,内部往往会出现太多实例变量,导致重复代码的问题。
    可以通过提炼,将其中可以通用的部门,提取成接口,通过接口调用。
  4. 过长参数列。太多的参数会影响阅读,传递过程中比较混乱。可以将相关的参数组织成一个对象,或者已有的适合的对象代替参数。
  5. 发散式变化。某个类因为不同原因经常需要调整,比如新增一个数据源,就需要改这个类中的方法。应该将这个类拆分,将其中会因外部而变动的部分提取出来,放到一个单独的类中,或者应该尽可能减少类的变化,将这些变化放到配置中,而不是改变类的方法。
  6. 霰弹式修改。每遇到某种变化就要在需要不同的类中做小的修改,因为修改的部门不集中,容易遗漏。这时候应该将需要修改的部分集中在一起,放在一个类中,没有合适的就新建一个。
  7. 依恋情结。函数从另外一个类中调用的方法超过自己本身所在的类。应该将这个函数位置移动到另外一个类中。
  8. 数据泥团。很多地方拥有相同的数据,比如两个以上类中相同的字段,应该将这些数据提取到一个独立的对象中,简化函数调用。
  9. 基本类型偏执。Java中两种数据,一种基本类型,一种结构类型,也就是对象。对象会带来额外的开销,但是可以利用对象创造类似于基本类型作用的类。这边我理解,应该是可以封装成类似于用于对数据的处理的Bigdecimal这样的对象。
  10. Switch惊悚现身。作者认为,switch的问题在于重复,应该用多态代替switch。
  11. 平行继承体系。当为一个类增加一个子类时,必须为另外一个类增加子类。应该让一个继承体系的实例引用另一个继承体系的实例。
  12. 冗赘类。类的存在应该有其作用,否则会产生维护的成本,如果类不再被使用,就应该去除。
  13. 夸夸其谈未来性。设计的时候应该考虑到以后的扩展。我理解这里所说的应该是过于考虑到以后的可能性,而影响到现有功能的设计开发,比如在实现的时候,加入现有功能不必要的接口或者其他实现方法,对未来的维护造成误导。某种程度上,也是冗赘。
  14. 令人迷惑的暂时字段。对象内某个实例变量仅为某种特殊情况而设定。很多时候这个变量不是都用到的。我理解为某个变量仅为某个函数服务,不是通用的,应该将这个变量与其相关的函数放到一个独立类中,避免引起误解。
  15. 过度耦合的消息链。比如连续的get方法,获取对象中的对象的变量或者方法。应该观察最终需要得到什么,将这个连续的关系到一个方法中,然后return出结果。
  16. .中间人。一个接口中的方法,一半以上调用其他地方的函数,应该运用Remove Midden Man,直接调用所需要的方法。
  17. 狎昵关系。两个类的关系过于亲密,可以使用Move Method和Move Field帮它们划清界限。如继承关系中,子类对父类的过分使用。
  18. 异曲同工的类。两个函数的行为相同,名称不同,应该移除其中一个,避免重复,然后在调用的地方使用保留调整后的函数。
  19. 不完美的类库。有时候需要为提供服务的类增加一个函数,但你无法修改这个类。应该在客户类中新建一个函数,并且以第一参数形式传入。如果需要太多这样的函数,应该想办法将这些方法放入服务提供类中。
  20. 纯稚的数据类。仅仅用于数据的读写。对于其中不应该被其他类修改的字段,应该将修改其字段的方法移入,将其中的public改为private。我理解为例如对其中一些字段的判断,简单的计算等,应该放入该类中,避免外部直接修改属性。
  21. 被拒绝的遗赠。继承行为中,子类不需要父类的某些方法,应该为这个子类新建一个兄弟类,父类只包含兄弟类共同需要的部分。同时调整方法原父类中方法被调用的地方。
  22. 过多的注释。好的代码应该代替很多注释,通过阅读代码本身理解代码。注释过多还有一些坏处,比如功能调整后,注释没有跟随调整,后面再维护的时候,引起误解。

第四章

构 筑测试体系。第一章中有提到测试对重构的重要性,重构影响到代码的结构,可能会对代码功能造成一些影响,引入bug,而这应该是重构中避免出现的,这章主要是介绍了Junit测试框架的使用。单元测试在平时的开发中也应该使用起来,之前用得不多,最近也在找时间看项目中一些单元测试的例子,如一些稍微复杂点的测试,比如fwms中出库单部分金良写的mock+junit单元测试的例子,用mock构造测试数,希望尽快在开发中使用起来。

第五章

重构的记录格式。作者采用一种标准的格式,将每个重构手法分为五个部分。
1. 名称:建造一个重构词汇
2. 简短概要:简单介绍重构手法适用的场景,以及所做的事。
3. 动机:为什么需要这个重构,什么情况下不该使用。
4. 做法:这个重构的操作步骤。
5. 范例:简单的例子说明此重构的运作。

寻找引用点。重构的时候,需要找出对某个函数、字段或者类的所有引用点,避免遗漏。

重构的基本技巧。小步前进,频繁测试。

第六章

本章的重构手法都是对函数的整理。
1.提炼函数:
提取一段代码封装为一个独立的函数,函数的名称能够解释这段代码的用途。提炼函数可以提高函数的复用度,阅读更高层函数只需要看其调用的函数的函数名,函数也更容易覆写。
注意点:局部变量的处理,如果局部变量只在提取后的函数中使用,就将其一并提取,如果其他地方也使用,且提取后的函数改变了局部变量,就将其作为返回值return。

2.内联函数。
调用的函数很简短,没必要单独封装,属于非必要性间接调用,反而会增加代码阅读难度。如封装的函数中只有一行代码被return,可以直接将这行代码放到调用的地方。另一种情况是,有一群组织不合理的函数,可以将其内联到一个函数中,再重新组织提炼函数。

3.内联临时变量。
临时变量仅被一个简单表达式赋值一次的情况下。比如从一个对象中get一个属性,如果仅被使用一次,就没必要为其单独声明一个临时变量,只需要在使用的地方用这个对象直接get。这样的临时变量有可能在提取函数的时候有影响,这时候应该将对象以参数的形式传入新函数中,然后取对象中的属性。

4.以查询取代临时变量。
临时变量只能在所属函数中使用,例如用一个临时变量保存一段计算,如果同一个类中有其它地方需要使用同样的计算,会重复计算。应该将这段计算的代码提取出,将结果return。
如果这个临时变量原本用于保存循环累加的值,需要将循环的逻辑也提取到新函数中。

5.引入解释性变量。
存在复杂的表达式难以阅读,例如if判断中的多个复杂判断。应该将复杂的判断提取出来赋值给临时变量,用变量名解释判断的用途,再放入条件语句中。

6.分解临时变量
临时变量被多次赋值,除了循环累加与结果收集变量,其它情况下变量应该只被赋值一次,超过一次就应该被分解为多个临时变量,每个变量只承担一个责任。

7.移除对参数的赋值
在java中,不要对参数赋值。Java只采用按值传递方式,对参数的任何修改,都不会对调用端造成影响,例如:
public class Test {
public static void main(String[] args) {
int s = 5;
Test t = new Test();
t.test(s);
System.out.print(s);
}

private void test(int s){    s = 6;}

}
输出结果仍是5。应该用临时变量代替参数,return这个临时变量。
如果参数是对象。那么可以修改参数对象内部状态,但是对参数对象重新赋值没有意义。
另一点,参数只表示传入的东西,代码会更加清晰。

8.以函数对象取代函数
大型函数中的局部变量阻碍了函数提炼。应该将该函数单独放到一个对象中,这些临时变量作为对象内部的字段,然后再提取函数。

9.替换算法
用更清晰简单的算法取代现有算法,或者更清晰的实现方式取代臃肿的设计。

第七章

在对象之间搬移特性。
1. 搬移函数。
函数与其所在类之外的另一个类有更多的交流,应该将该函数移到与其交流较多的那个类中,或者将函数体放到该类中,旧函数调用新函数。这样可以降低类之间的耦合。

  1. 搬移字段
    如果字段被另一个类更多地使用,应该将这些字段搬移到使用它的那个类中
  2. 提炼类
    某个类做了应该由两个类做的事情,随着功能的增加,这个类会变得更加复杂,功能不清晰。应该新建一个类,以功能区分,将相关的字段和函数搬移到另一个新类中。
    另一种可能是,开发后期发现类的子类化方式的不同

  3. 将类内联化
    一个类不再承担足够的责任,不再有单独存在的理由,应该将这个类塞进调用其最频繁的类中。我理解这里应该是例如一些DTO,由于重构等原因,其中的一些属性使用率较低,或者其属性较少,可以用一些实体类来代替它,将其中的属性放到实体中,加上注解(Hibernate)不与数据库映射。然后用这个实体替换原DTO。

  4. 隐藏委托关系
    通过一个委托类来调用另一个对象。以便调用者不需要直接调用目标对象,通过委托类获得。如书中的例子,如果调用者想获取部门经理,可以调用department中的getManager,取到部门经理的时候,必须先获取Department对象,就暴露了department。通过隐藏委托关系,可以在Person类中加入获取部门经理的方法,让Person类去获取Manager,调用方只需要person.getManager(),就可以不获取Departmetn得到Manager
  5. 移除中间人
    某个类做了过多的简单委托动作,过多的简单的委托关系,服务类变成了一个‘中间人’,应该改为让调用方直接调用受托类。

  6. 引入外加函数
    需要为提供服务的类增加一个函数,但是无法修改这个类。可以在调用端自行添加一个新函数,并且以第一参数的形式传入一个服务类的实例。
    如:
    Date newStart = new Date(previousEnd.getYear(),
    previousEnd.getMonth(),previousEnd.getDate()+1);
    改为:
    将其提取为一个方法,将结果return出来。

Date newStart nextDay(previousEnd);
Private static Date nextDay(Date arg) {
return new Date(previousEnd.getYear(),
previousEnd.getMonth(),previousEnd.getDate()+1);
}
8.引入本地扩展
需要为服务类提供一些额外函数,但是无法修改这个类。应该新建一个新类,让他包含这些额外的函数,让这个扩展品成为源类的子类或者包装类。

第八章 重新组织函数

不要使用魔法数值,应该用名称有含义的static final或者enum来代替。这点平时工作中也会强调,魔法数值不好理解,不好维护,代码不美观。
1. 自封装字段。
直接访问一个字段,但是与字段之间的耦合关系逐渐变得笨拙。为这个字段建立get、set方法,并且只用get
、set方法来访问字段。如果想访问父类中的字段,又想在子类中将这个变量重新赋值,就应该使用此手法重构。
2.以对象取代数据值。
有一个数据项,需要与其他数据和行为一起使用才有意义。需要将数据项变成对象。例如书中例子,可以用一个字符串表示电话号码,但是电话号码可能需要格式化,提取区号等行为,
应该封装一个电话号码的类,将数据变成对象,电话号码作为其中的一项属性。
3.将值对象改为引用对象。
从一个类衍生出许多彼此相等的实例,希望将它们替换为同一个对象。应该将这个值对象改为引用对象。此处理解一下:值传递表示方法接收的是调用者提供的值,引用传递表示方法接收的是调用者提供的变量地址,
引用对象只是保存了其地址,变量保存地址指向的地方。所以要确保对任何一个对象的修改都能影响到所有引用此一对象的地方,就要将这个对象变成一个引用对象。
4.将引用对象改为值对象
有一个引用对象,很小且不可变,而且不易管理。应该将其变成一个值对象。引用对象必须是可控的,使用时总是必须向其控制者请求适当的引用对象,可能会造成内存趋于之间错综复杂的关联,
在分布系统和并发系统中,不可变的值对象特别有用,因为你无需考虑他们的同步问题。改手法要对qeuals方法重写,也要重写hashCode方法,重写equals方法的原因是在Object类中的equals方法是直接用“==”来比较的,
比较的是对象的引用地址是否相同,即是否指向同一个对象实例。当只需要比较内容时,就要重写equals。重写equals必须同时重写hashCode,否则依赖hash的任何集合对象(如Hashtable,HashSet和HashMap)都可能产生意外行为。
5.以对象取代数组。
有一个数组,其中的元素各自代表不同的东西。应该以对象替换这个数组,数组的元素用对象的字段代替。原因是数组应该容纳同一种类型的东西。如果数组元素无规律,对于增加代码理解难度。
6.复制“被监视数据”。观察者模式,定义了一个一对多的依赖关系,让一个或者多个观察者对象监督一个主题对象,这个主题对象状态上的变化能通知所有的依赖于此的对象。
7. 将单向关联改为双向关联。两个类都需要使用对方的特性,但其间只有以一条单向连接。并使修改函数能够同时更新两条连接。如订单实体中有客户属性,客户实体中也可以添加订单属性。
8.将双向关联改为单向关联。两个类之间有双向关联,但是其中一个类如今不再需要另一个类的特性,则去除不必要的关联。双向关联要有一些维护的代价在里面,所以双向关联应该在需要的时候再使用,不再需要的时候就去掉
9.以字面常量取代魔法数。代码直接用字面数值的,应该创造一个常量来代替它,常量的命名要能表示出所替代魔法数值的含义。魔法数值增加代码维护的难度,影响阅读,替换为常量后,如果多处引用,只需要修改常量即可。
10.封装字段。类中存在一个public字段,将它声明为private,并且提供get、set函数访问它。
11.封装集合。有个函数返回一个集合,让这个函数返回该集合的一个只读的副本,并在这个类中提供添加/移除集合元素的函数。
12.以数据类取代记录。当需要面对传统编程环境中的记录结构时,为该记录创建一个” 哑”数据对象。这里我理解应该是新建一个类,其字段与遗留程序或者api返回的数据中的字段对应起来,传递或者临时存储值。
13.以类取代类型码。类中有一个数值类型码,但它并不影响类的行为,应该以一个新的类替换该数值类型码。接收类型码作为参数的函数,所期望的实际上是一个数值,无法强制使用符号名,降低代码可读性。
14.以子类取代类型码。有一个不可变的类型码,会影响类的行为,应当以子类取代这个类型码。
15.以状态模式/策略模式取代类型码。有一个类型码,它会影响类的行为,但你无法通过继承手法消除它。
16.以字段取代子类。各个子类唯一差别只在“返回常量数据”的函数身上。应当修改这些函数,使它们返回超类中的某个(新增)字段,然后销毁子类。

第九章 简化条件表达式

  1. 分解条件表达式。有一个复杂的条件语句,应当将其中的条件抽取出独立函数,结果作为返回值return出来,判断的地方用函数代替原来的表达式。复杂的逻辑提升代码的复杂度,可读性下降。
  2. 合并条件表达式。多个判断,其中执行语句都得到相同的结果,应当将这些判断合并为一个条件表达式,并且将其提炼为一个独立函数。
  3. 合并重复的条件判断。在条件表达式的每个分支上面都有相同的一段代码。应当将这段重复代码搬移到条件表达式之外。也就是其实无论这个条件表达式是什么结果,都会执行这段代码。
  4. 移除控制标记。在一系列布尔表达式中,某个变量带有“控制标记”作用。应当以break或者return语句取代控制标记。比如声明某个变量为true,当满足某个条件时,为false,函数停止执行。这样会降低条件表达式的可读性。
  5. 以卫语句取代嵌套条件表达式。函数中的条件逻辑使人难以看清正常的执行路径。应当使用卫语句表现所有的特殊情况。嵌套的条件表达式也提升代码的阅读难度,应当抓住重点,假如条件表达式答案中的正常行为只有一种,单独检查这种正常的行为,并在该条件为true时从函数中返回。
  6. 以多态取代条件表达式。多个条件表达式,它根据对象类型的不同而选择不同的行为。应当将这个条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始的函数声明为抽象函数。多态的好处是,如果需要根据对象的不同类型而采取不同的行为,虽然实际上还是根据不同情况执行不同函数,但是使你不必编写明显的条件表达式。
  7. 引入null对象。需要检查某对象是否为null,应当将null值替换为null对象。
  8. 引入断言。某一段代码需要对程序状态做出某种假设,假如从业务上考虑某个值必须不为null,就应当以断言明确表现这种假设。

第十章 简化函数调用

  1. 函数改名。函数的名称未能揭示函数的用途,就应当修改函数名称。函数名称应该能准确表达函数的含义,在使用的时候不需要知道其内部实现,通过函数名即可了解其作用。
  2. 添加参数。某个函数需要从调用端得到更多信息。应当为此函数添加一个对象参数,让对象带进函数所需信息。过长的参数不是好的选择,所以使用添加参数时,考虑是否有其他选择。
  3. 移除参数。当函数本体不再需要某个参数的时候,就应当移除参数。从功能上讲,参数多不会影响函数的功能,但是参数过多影响代码阅读,没有意义的参数需要去除,或者用其他方式代替参数。
  4. 将查询函数和修改函数分离。如果某个函数既返回对象状态值,又修改对象的状态。这样的函数复用可能会产生问题,应当建立两个不同的函数,其中一个复杂查询,另一个负责修改,各司其职。
  5. 令函数携带参数。若干的函数做了类似的工作,但是在函数本体中却包含了不同的值,应当让这些不同的值作为参数传入,将其中共同的部分抽为一个函数,可以去除重复代码。
  6. 以明确函数取代参数。有一个函数,其中完全取决于参数值而采取不同行为,应当针对该参数的每一个可能值,建立一个独立函数。
  7. 保持对象完整。从某个对象中取出若干值,将它们作为某一次函数调用时的参数。应当改为传递整个对象。这样的好处是将来被调用函数需要新的数据,就不必修改对此函数的所有调用。只需要在对象中添加即可。
  8. 以函数取代参数。对象调用某个函数,并将所得结果作为参数,传递给另一个函数,而接受该参数的函数本身也能够调用前一个函数。应当让参数接受者去除该项参数,并直接调用前一个函数。如果函数可以通过其他途径获取参数值,就不应该通过参数取得该值,过长的参数列会增加程序阅读的难度。
  9. 引入参数对象。某些参数总是很自然地同时出现。应当以一个对象取代这些参数。如果某一组参数总是被一起传递,可能也有其他地方有相同的用法,就应当封装一个包含这些参数作为属性的对象。改用这个对象作为参数,可以缩短参数的长度,同时扩展性也较好。
  10. 移除设值函数。类中的某个字段应该在对象创建时候被设值,然后就不再改变。应当去掉该字段的所有设值函数。包含设值函数,就说明这个值可以被改变,所以如果这个值不应该被改变,就去除设值函数。
  11. 隐藏函数。有一个函数从来没有被其他任何类用到,应当将这个类声明为private。函数的可见度应当由其使用范围决定,当这个函数只在当前类被使用的时候,就应当只在当前类中可见。
  12. 以工厂函数取代构造函数。假如你希望在创建对象时不仅仅是做简单的构建动作,就应当将构造函数替换为工厂函数。工厂函数中可以加入需要的元素。
  13. 封装向下转型。某个函数返回的对象,需要由函数调用者执行向下转型,应当将向下转型动作移到函数中。
  14. 以异常取代错误码。某个函数返回一个特定的代码,用以表示某种错误情况。应当改用异常。异常有一个好处是清楚地将“普通程序”与错误处理分开,使程序更容易理解。
  15. 以测试取代异常。面对一个调用者可以预先检查的条件,抛出了一个异常,应当修改调用者,使它在调用函数之前先判断检查,就像在使用对象之前先做非空检查。

第十一章 处理概括关系

有一批重构手法专门用来处理类的概括关系,其中主要是将函数上下移动于继承体系之中。

1.字段上移。两个子类拥有相同的字段,应当将该字段移到父类中。这样做可以除去重复数据的声明,其次使你可以将使用该字段的行为从子类移动到父类中,也除去了重读的行为。使用这个重构前,需要判断函数如何使用这些字段。
2.函数上移。有些函数,在各个子类中产生完全相同的结果,应当将函数移动到父类中,在子类中使用。同样也是为了避免重复行为,只要面临重复,就会面临“修改其中一个却未能修改另一个”的风险,带来了额外的维护成本。
3.构造函数本体上移。在各个子类中拥有一些构造函数,它们的本体几乎完全一致,应当在父类中新建一个构造函数,并在子类中调用它。对于普通的函数,如果在各个子类中行为一样,首先应当想到将其提炼到父类中,对于构造函数来说,其共同行为往往就是对象的构建。将其提炼到父类中,子类的构造函数的唯一作用就死调用父类构造函数。
4.函数下移。父类中的某个函数只与部分子类有关。应当将这个函数移动到需要的子类中。
5.字段下移。父类中的某个字段只被部分子类使用,应当将这个字段移动到其需要的那些子类中。
6.提炼子类。类的某些特性只被某些实例用到,应当新建一个子类,将上面所说的那一部分特性移到子类中。
7.提炼超类。两个类有相似的特性,应当为这两个类新建一个超类,将相同的特性放入超类,两个类继承这个超类。重复代码是最糟糕的东西之一,这样做可以避免很多重复代码。
8.提炼接口。若干客户使用类接口中的同一子集,或者两个类的接口有部分相同,应当将相同的子集提炼到一个独立接口中。类之间彼此互用通常意味着类的所有责任被使用,如果另一个类只使用这个类的特定的部分,应当将类中的这部分分离出来提供服务。
9.折叠继承体系。父类和子类之间无太大区别,应当将其合为一体。继承体系容易变得复杂,重构继承体系时候,也常常将函数和字段在体系中上下移动,之后很可能发现某个子类并未带来该有的作用,因此需要合并父类与子类。
10.塑造模板函数。有一些子类,其中相应的某些函数以相同顺序执行类似操作,但是各个操作细节上有所不同。应当将这些操作分别放进独立函数中,并保持它们都有相同的签名,于是原函数也变得相同,然后将函数上移到父类中。
11.以委托取代继承。某个子类只使用父类接口中的一部分,或是根本不需要继承而来的数据,应当在子类中新建一个字段来保存父类,调整子类函数,令它改而委托父类,然后去掉两者之间的继承关系。子类不应当只使用父类的一部分,如果只使用父类的一部分,常常会导致代码传达的信息与开发者的意图不一致。如果只需要其中部分功能,可以委托取代继承,只需要受托类的一部分功能,接口中需要使用,需要忽略的部分,都由开发者控制。
12.以继承取代委托。两个类之间使用委托关系,并经常为整个接口编写许多极简单的委托函数。应当让委托类继承受托类。本重构与上面一项恰相反,如果发现自己需要使用受托类中的所有功能,就应当放弃委托,改为继承受托类。

第十二章 大型重构

大型重构的没有明确的步骤,只有在看到实际情况时候,才能知道该怎么做。
1. 梳理并分解继承体系。如果某个继承体系同时承担两项责任,应当建立讲个继承体系,并通过委托关系让其中一个可以调用另一个。继承可以减少子类的代码,避免重复等,但是也容易被误用,例如长期开发或者维护的过程中,子类逐渐变多后,发现继承体系混乱,自己深陷泥淖。混乱的继承体系会导致重复代码,会使修改变得困难。
2. 将过程化设计转为对象设计。有一些传统过程化风格的代码,应当将数据记录编程对象,将大块的行为分成小块,并且将行为移入相关的对象之中。
3. 将领域和表述/显示分类。即使用MVC模式,项目分层开发。
4. 提炼继承体系。某个类做了太多工作,其中一部分工作是以大量条件表达式完成的。应当建立继承体系,以一个子类表示一种特殊情况。这样的好处是方便扩展。

第十三章 重构,复用与现实

如何重构,在哪里重构?
面对一个既有程序,应该使用哪些重构,取决于开发者的目标,一个常见的重构原因,同时也是重构这本书关注的焦点,就是调整程序结构以使(短期内)添加新功能更加容易,容易扩展、降低维护成本。

第十四章 重构工具

和手工重构相比,自动化重构工具支持的重构,可以节省很多时间,人力。我理解目前重构的工具,应当是功能强大的开发工具,如IDEA,对语言支持较好的一些编辑器,方便各种查找,测试工具等。