如果你发现自己需要为程序添加一个特性,而代码结构使你无法很方便地达成目的,那就先重构那个程序,使特性的添加比较容易进行,然后再添加特性。
没个傻瓜都能够写出计算机能够理解的代码,但是唯有写出人类能够理解的代码的程序员,才是优秀的程序员。
第一章
重构步骤
1. 为即将修改的代码建立一组可靠的测试环境。测试机制再重构领域的地位很重要。
2. 分解并重组
2.1 extract
2.2 move
2.3 replace temp with query
2.4 State模式 Gang of four,一个对象
2.5 Replace Type Code with State/Strategy
2.6 Replace Conditional with Polymorphism
2.7 Self Encapsulate Field
第二章 重构原则
重构的定义
1. 对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
2. 使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
何时重构
三次法则,事不过三,三则重构
3. 重构起源何处
small talk: https://zh.wikipedia.org/wiki/Smalltalk
Bill Opdyke,博士论文,重构领域具有相当高的价值。
第三章 代码的坏味道
坏味道条款
1. duplicate code
2. Long Method
3. Large Class
4. Long Parameter List
5. Divergent Change 发散式变化, 如果某个类经常因为不同的原因在不同的方向上发生变化,divergent change就出现了。
6. Shotgun Surgery 霰弹式修改。如果每遇到某种变化,你就必须在许多不同的类内做出许多小修改,你所面临的坏味道就是shotgun surgery
7. Feature Envy 依恋情结,函数对某个类的兴趣高于对自己所处类的兴趣
8. Date Clumps 数据泥塘
9. Primitive Obsession
10. Switch Statements,多态优雅解决
11. Parrallel Inheritance Hierachies(平行继承体系),Shotgun surgery的特殊形式。
12. Lazy Class(冗余类)
13. Speculative
14. Temporary Field(令人迷惑的暂时字段)
15. Message Chains(过度耦合的消息链)
16. Middle Man(中间人),过度委托,过多中间人调用
17. Inappropriate Intimatcy(狎昵关系),
18. Alternative Classes with Different Interfaces
19. Incomplete Library Class(不完美的类库)
20. Data Class(幼稚的数据类)
21. Refused Bequest(别拒绝的遗赠)
22. Comments(过多的注释)
第四章 构筑测试体系
JUnit
建立一个独立的类用于测试,并在一个框架中运行它
1. suite
2. runner
第五章 重构列表
第六章 重构组织函数
1. Extract Method(提炼函数),创建一个新函数,以它“做什么”来命名
2. Inline Method,在函数调用点插入函数本体,然后删除该函数。函数内容和函数名称一样清晰的时候可以应用该策略
3. Inline Temp,将所有对该变量的引用动作,替换为对它赋值的那个表达式。
4. Replace Temp With Query,将这个表达式提取到一个独立的函数中。将这个临时变量的所有引用点替换为对新函数的调用。此后,新函数就可被其他函数使用。某个变量只是被赋值一次,可以应用该策略,可以通过在变量加final来确保只被赋值一次。
5. Introduce Explaining Variable(引入解释型变量),你有一个复杂的表达式,将该复杂表达式(或者其中一部分)的结果放进一个临时变量,以此变量来解释表达式用途。
当表达式复杂并且难以阅读的时候,临时变量会帮助把表达式分解为更易于理解的形式。 这种时候也可以应用 extract method方法替换临时变量,但是当extract method的方式消耗过大的时候,可以应用introduce explaining variable
6. Split Temporary Variable(分解临时变量)
当一个赋值超过以此的临时变量,但是它既不是循环变量,又不是用于手机的计算结果。那么,针对每次赋值,创造一个独立、对应的临时变量。
7. Remove Assignments to Parameters(移除对参数的赋值)。代码对一个参数赋值,以一个临时变量来取代参数的位置。
对参数赋值,降低了代码的清晰度
8. Replace Method with Method Object(以函数对象取代函数),将这个函数放入一个单独的对象中,如此一来局部变量就成了对象内的字段。然后你可以在同一个对象中,将这个大型函数分解为多个小型函数。
9. Substitute Algorithm(替换算法),将函数本体替换为另一个算法。(把某个算法替换为另一个更清晰的算法)
第七章 在对象之间搬移特性
1. Move Method(搬移函数),在你的程序中,有个函数与其所驻类之外的另一个类交流过多,调用后者,或者被后者调用。在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是将旧函数完全移除。
2. Move Field(搬移字段),你的程序中,某个字段所驻类之外的另一个类更多地用到。在目标类新建一个字段,修改原字段的所有用户,另他们改用新字段。
3. Extract Class(提炼类),某个类做了应该由两个类做的事情,建立一个新类,将相关的字段和函数从旧类搬移到新类。
4. Inline Class(将类内联化),某个类没有做太多事情,将这个类的所有特性搬移到另一个类中,然后移除原类。
5. Hide Delegate(隐藏“委托关系”),客户通过一个委托类来调用另一个对象。在服务类上建立客户所需要的所有函数,用以隐藏委托关系。
6. Remove Middle Man(移除中间人),某个类做了过多的简单委托动作。让客户直接调用受托类。当在受委托的函数越来越多的时候,服务类添加的委托越来越多,完全成为了中间人,这样,就应该移除中间人,让它直接应用受托类。
7. Introduce Foreign(引入外加函数),你需要为提供服务的类增加一个函数,但是你无法修改这个类。在客户类中建立一个函数,并以第一参数的形式传入一个服务类实例。
第八章 重新组织数据
1. Self Encapsulate Field(自封装字段),你直接访问一个字段,但与字段之间的耦合关系逐渐变得笨拙。为这个字段建立取值/设置函数,并且只以这个函数来访问字段。
2. Replace Data Value with Object(以对象取代数据值),你有一个数据项,需要与其他数据和行为一起使用才有意义。将数据项变成对象。
8. Change Bidirectional Association to Unidirectional(将双向关联改为单向关联),两个类之间有双向关联,但其中一个类如今不在需要另一个类的特性。去除不必要的关联。
9. Replace Magic Number with Symbolic Constant(以字面常量取代魔法数),你有一个字面数值,带有特别含义。创造一个常量,根据其意义为其命名,并将上面的字面数值替换为这个常量。
魔法数:拥有特殊意义,却不能明确表现出这个数字的意义的数字。
10. Encapsulate Field(封装字段),你的类中有一个public字段,将他申明为private,并提供相应的访问函数。(数据隐藏,封装,对象的特性)
11. Encapsulate Collection(封装集合),有个函数返回一个集合,让这个函数返回该集合的一个只读副本,并在这个类中添加/移除集合中元素的函数。
12. Replace Record with Data Class(以数据类取代纪录),你需要面对传统编程环境中纪录结构。为该纪录创建一个“哑”数据对象
13. Replace Type code with Class(以类取代类型码),类之中有一个数值类型码,但它并不影响类的行为。以一个新的类替换该数值类型码
14. Replace Type code with subclass(以子类取代类型码),你有一个不可变的类型码,它会影响类的行为,用子类取代这个类型码。工厂模式。
15. Replace Type code with State/Strategy,当无法通过继承来取代类型码的时候,可以通过状态类来解决
16. Replace subclass with Fields,建立子类的目的,是为了增加新特性或变化其行为。有一种变化行为称之为“常量函数 constant method”,他们会返回一个硬编码的值。如果子类中只有常量函数,就没有足够的存在价值。
第九章 简化条件表达式
1. Decompose Conditional Expression(分解条件表达式),复杂的条件逻辑可能导致复杂性上升,所以可以将条件表达式提取出来,成为一个函数。
2. Consolidate Conditional Expression(合并条件表达式),当一连串的条件不同,但是返回的结果一样的时候,可以合并表达式。之后可以提取一个函数出来。
3. Consolidate Duplicate Conditional Fragments(合并重复的条件片段),在条件表示的没个分支上都有相同的表达式,可以将重复代码搬移到条件表达式之外。
4. Remove Control Flag(移除控制标记),通过break或者return语句来替代具有控制标记用途的常量。
5. Replace Nested Conditional with Guard Clausees(以卫语句取代嵌套条件表达式),ifelse两个条件判断语句是平等,但是卫语句就是条件特别,是需要单独检查的条件语句,当该条件出现的时候,需要做出相应的操作,然后继续处理。这个时候要用卫语句。
6. Replace Condition with Polymorphism(以多态取代条件表达式),当程序中有根据对象的类型来选择不同的行为的时候,那么可以将条件表示式的没个分支写到子函数中,然后将原始函数改为抽象函数。
7. Introduce NULL Object(引入NULL对象),你需要再三检查某对象是否为NULL,将null 值替换为null 对象。多态的最根本好处是你不用再向对象询问“你是什么类型”,然后根据得到答案的不同采取不同的行为。Null对象也称之为虚拟对象。并且,空对象一定是常量,他的任何成分都不会发生变化,因此我们多使用singlenon模式来实现他们。
8. Introduce Insertion(引入断言),某一段代码需要对程序状态做出某种假设,以断言明确表现这种假设。断言的失败会导致一个非受控异常,表示程序员失败,有bug存在。断言不能被系统的其他部分调用,最后系统中不应该存在断言。断言可以作为交流和调试的辅助。
第十章 简化函数的调用
良好的接口只向用户展现必须展现的东西,如果一个接口暴露了过多的细节,你就可以将不必暴露的东西隐藏起来,进而提高代码的质量。
1. Rename Method,函数改名,改成揭示函数功能的名字。
2. Add Parameter,某个函数需要调用很多信息,那么通过添加一个对象参数,让该对象传入函数所需的信息。
3. Remove Parameter,移除不必的参数
4. Separate Query with Modifier,分离查询函数和修改函数。并发程序中需要保留这样一个函数,处理查询和修改两个任务。
5. Parameterize Method(令函数携带参数)。函数做了类似工作,但是值不同,那么建立一个单一函数,通过参数来传达不同的值。
6. Replace Parameter with Explicit Methods(以明确函数取代参数)。你有一个函数,其中的行为完全取决于传入的参数,那么针对该参数的所有可能值,建立一个独立的函数。某个参数有多种可能性,并且类通过条件判断语句采用不同的行为,那么可以用函数取代条件判断语句。
7. Preserve Whole Object(保持对象完整),你从对象中取出若干值,作为函数参数,那么改为传递整个对象。
8. Replace Parameter with Methods(以函数取代参数),如果对象调用某个函数A,然后将返回值作为参数传递给另一个函数B,同时函数B能够调用函数A,那么可以省略对象调用函数A的过程,减少参数传递,直接再函数B中调用函数A。
9. Introduce Parameter Object(引入参数对象),某些参数总是很自然的出现,那么以一个对象替换掉。
10. Remove Settings Method(移除设值函数),某个字段在对象创建的时候设值,然后就不再改变,那么去除掉所有该字段的设值函数。
11. Hide Method。一个函数没有被其他函数用过,则将这个函数设值为private。
12. Replace Constructor With Factory Method,你希望在构建对象的时候不是简单的构建一个构建动作,那么用工厂模式替代。
13. Encapsulate Downcast(封装向下转型),某个函数返回的对象,需要由函数调用者执行向下转型,则将向下转型动作移到函数中。
14. Replace Error Code with Exception(用异常取代错误码),某个函数返回一个特定的代码,用以表示某种错误情况。改用异常。
15. Replace Exception with Test(以测试取代异常),面对一个调用者可以预先检查的条件,你抛出了一个异常。修改调用者,使它在调用之前进行检查。
第十一章 处理概括关系
概括关系,继承关系(generalization)
1. pull up field(字段上移)
2. pull up method(函数上移)
3. pull up constructor body(构造函数本体上移)
4. push down method
5. push down field
6. extract subclass
7. extract superclass
8. extract interface
9. collapse hierarchy(折叠继承体系),超类和子类之间无太大差异,将他们合为一体。
10. form template method(塑造模板函数)
11. replace inheritance with delegation(以委托取代继承)
12 replace delegation with inheritance
第12章 大型重构
1. tease apart inheritance(梳理并分解继承体系),某个继承体系同时承担两项责任。建立两个继承体系,并通过委托关系让其中一个可以调用另一个。
2. convert procedure design to objects(将过程化设计转化为对象设计),你手上有一段传统过程化风格的代码,将数据记录变成对象,将大块的行为分成小块,并将行为移入相关的对象之中。
3. separate domain from presentation(将领域和表示/显示分离),某些GUI类中包含了领域逻辑。则将领域逻辑分离出来,为他们建立独立的领域类。
4. extract hieranchy(提炼继承体系),你有某个类有大量的工作,其中一部分以大量的条件表达式完成的。建立继承体系,以一个子类表示一种特殊情况。
第十三章 重构、复用和现实
第十四章 重构工具
重构工具的技术标准:
1. 程序数据库
2. 解析树
3. 准确性,由工具实现的重构,必须合理保持程序原有行为
重构工具的实用标准:
1. 速度
2. 撤销,重构保持了程序的行为,那么方向重构应该也能够保持程序的行为
3. 与其他工具集成