如果你发现自己需要为程序添加一个特性,而代码结构是你无法很方便地达成目的,那就先重构那个程序,是特性的添加比较容易进行,然后再添加特性。
重构的第一步骤永远相同:为即将修改的代码建立一组可靠的测试环境。
重构之前,首先检查自己是否有一套可靠的测试机制,这些测试必须有自我检验能力。
重构技术就是以微笑的步伐修改程序。如果你犯下错误,很容易便可发现它。
任何一个傻瓜都能写出计算机可以理解的代码,唯有写出人类容易理解的代码,才是优秀的程序员。
重构的原则:
重构:对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
重构:使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
添加新功能时,你不应该修改既有代码,只管添加新功能(开-闭原则)。
重构时你就不能再添加宫嗯那个,只管改进程序结构。此时你不应该添加任何测试(除非发现有先前遗漏的东西),只在绝对必要(用以处理接口变化)时才修改测试。
为何重构?
重构改进软件设计
重构使软件更容易理解
重构提高编程速度
何时重构?
三次法则:第一次做某件事时只管去做;第二次做类似的事会产生反感,担误落如何还是可以去做;第三次再做类似的事,你就应该重构。也就是事不过三,三则重构。
何时不该重构:
例如当你应该重新编写所有代码的时候,有时候既有代码实在太混乱,重构它还不如重新写一个来得简单。
重写(而非重构)的一个清楚讯号就是:现有代码根本不能正常运作。你可能只是试着做点测试,然后就发现代码中满是错误,根本无法稳定运作。记住,重构之前,代码必须起码能够在大部分情况下正常运作。
另外,如果项目已近最后期限,你也应该避免重构。在此事件,从重构过程赢得的生产力只有在最后期限过后才能体现出来,而那个时候已经为时晚矣。
重构确实能够提高生产力,如果最后你没有足够时间,通常就表示你其实早该进行重构。
重构肩负一项特殊使命它和设计彼此互补。
代码的坏味道:
Duplicated Code(重复代码)
Long Method(过长函数)
条件表达式和循环常常也是提炼的信号。
Large Class(过大的类)
Long Parameter List(过长参数列)
Divergent Change(发散式变化)
一个类受多种变化的影响
Shotgun Surgery
如果每遇到某种变化,你都必须在许多不同的类内做出许多小修改,你所面临的坏味道就是Shotgun Surgery。
Feature Envy(依恋情绪)
Data Clumps(数据泥团)
减少字段和参数的个数
Primitive Obsession(基本类型偏执)
Switch Statement
面向对象程序的一个最明显的特征就是:少用switch(或case)语句。
Parallel Inheritance Hierarchies(平行继承体系)
如果每当你为某个类增加一个子类,必须也为另一个类增加一个子类,如果你发现某个继承体系的类的名称前缀和另一个继承体系的类名称前缀完全相同,便是问到了这种坏味道。消除这种重复性的一般策略是:让一个继承体系的实例引用另一个继承体系的实例。
Lazy Class(冗赘类)
如果一个类的所得不值其身价,它就应该消失。
Speculative Generality(夸夸其谈未来性)
Temporary Field(令人迷惑的暂时字段)
Message Chains(过度耦合的消息链)
Middle Man(中间人)
不要过度运用委托
Inappropriate Intimacy
Alternative Classes With Different Interfaces(异曲同工的类)
Incomplete Library Class(不完美的类库)
Data Class(纯稚的数据类)
所谓Data Class是指:他们拥有一些字段,以及用于访问(读写)这些字段的函数,除此之外一无长物。这样的类知识一种不会说话的数据容器,他们几乎一定被其他类过分细琐地操控者,应该考虑把它们封装起来。
Refused Bequest(被拒绝的遗赠)
如果子类复用了超类的行为(实现),却又不愿意支持超类的接口,Refused Bequest的坏味道就会变得浓烈。
Comments(过多的注释)
当你感觉需要写注释时,清闲尝试重构,试着让所有注释都变得多余。
构筑测试体系:
一套测试就是一个强大的bug侦测器,能够大大缩减查找bug所需要的时间。
Junit测试框架(用途是单元测试)
频繁地运行测试,每次编译请把测试页考虑进去-每天至少执行每个测试一次。
单元测试/功能测试。功能测试用来保证软件能够正常运作。
当你收到bug报告,请先写一个单元测试来暴露bug。
编写未臻完善的测试并实际运行,好过对完美测试的无尽等待。
考虑可能出错的边界条件,把测试火力集中在那儿。
当事情被认为应该会出错时,别忘了检查是否抛出了预期的异常。
不要因为测试无法捕捉到所有bug就不写测试,因为测试的确可以捕捉到大多数bug。
重构列表:
每个重构手法都有如下五个部分:
首先是名称(name). 建造一个重构词汇表,名称是很重要的。
名称之后是一个简短概要。
动机为你介绍为什么需要这个重构和什么情况下不该使用这个重构。
做法简明扼要地一步一步介绍如何进行此一重构
范例以一个十分简单的例子说明此重构手法如何运作。
重新组织函数:
Extract Method(提炼函数)
首先,如果每个函数的粒度都很小,那么函数被复用的机会就更大;其次,这会使高层函数读起来就像一系列注释;
函数的命名:以它“做什么”来命名,而不是以它“怎样做”命名。
即使你想要提炼的代码非常简单,例如只是一条消息或一个函数调用,只要心函数的名称能够以更好方式昭示代码意图,你也应该提炼它。但如果你想不出一个更有意义的名称,就别动。
分解临时变量:
如果程序有某个临时变量被赋值超过一次,他既不是循环变量,也不被用于收集计算结果。针对每次赋值,创造一个独立、对应的临时变量。同一个临时变量承担超过一件事情的时候,会令代码阅读者模糊。
移除对参数的赋值:
做法:
建立一个临时变量,把待处理的参数值赋予它;以“对参数的赋值”为界,将其后所有对此参数的引用点,全部替换为“对此临时变量的引用”;修改复制语句,使其改为对新建之临时变量赋值。(如果代码的语义是按引用传递的,在调用端检查调用后是否还使用了这个参数,页要检查有多少个按引用传递的参数被赋值后又被使用,请尽量以return方式返回一个值)。还可以为参数加上关键字final,从而强制他遵循“不对参数赋值”
简化条件表达式
分解条件表达式 合并条件表达式 合并重复的条件片段
简化函数调用
如果你发现自己需要为程序添加一个特性,而代码结构是你无法很方便地达成目的,那就先重构那个程序,是特性的添加比较容易进行,然后再添加特性。
重构的第一步骤永远相同:为即将修改的代码建立一组可靠的测试环境。
重构之前,首先检查自己是否有一套可靠的测试机制,这些测试必须有自我检验能力。
重构技术就是以微笑的步伐修改程序。如果你犯下错误,很容易便可发现它。
任何一个傻瓜都能写出计算机可以理解的代码,唯有写出人类容易理解的代码,才是优秀的程序员。
第二章:重构的原则
重构:对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
重构:使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
添加新功能时,你不应该修改既有代码,只管添加新功能(开-闭原则)。
重构时你就不能再添加宫嗯那个,只管改进程序结构。此时你不应该添加任何测试(除非发现有先前遗漏的东西),只在绝对必要(用以处理接口变化)时才修改测试。
为何重构?
重构改进软件设计
重构使软件更容易理解
重构提高编程速度
何时重构?
三次法则:第一次做某件事时只管去做;第二次做类似的事会产生反感,担误落如何还是可以去做;第三次再做类似的事,你就应该重构。也就是事不过三,三则重构。
何时不该重构:
例如当你应该重新编写所有代码的时候,有时候既有代码实在太混乱,重构它还不如重新写一个来得简单。
重写(而非重构)的一个清楚讯号就是:现有代码根本不能正常运作。你可能只是试着做点测试,然后就发现代码中满是错误,根本无法稳定运作。记住,重构之前,代码必须起码能够在大部分情况下正常运作。
另外,如果项目已近最后期限,你也应该避免重构。在此事件,从重构过程赢得的生产力只有在最后期限过后才能体现出来,而那个时候已经为时晚矣。
重构确实能够提高生产力,如果最后你没有足够时间,通常就表示你其实早该进行重构。
重构肩负一项特殊使命它和设计彼此互补。
第三章:代码的坏味道
Duplicated Code(重复代码)
Long Method(过长函数)
条件表达式和循环常常也是提炼的信号。
Large Class(过大的类)
Long Parameter List(过长参数列)
Divergent Change(发散式变化)
一个类受多种变化的影响
Shotgun Surgery
如果每遇到某种变化,你都必须在许多不同的类内做出许多小修改,你所面临的坏味道就是Shotgun Surgery。
Feature Envy(依恋情绪)
Data Clumps(数据泥团)
减少字段和参数的个数
Primitive Obsession(基本类型偏执)
Switch Statement
面向对象程序的一个最明显的特征就是:少用switch(或case)语句。
Parallel Inheritance Hierarchies(平行继承体系)
如果每当你为某个类增加一个子类,必须也为另一个类增加一个子类,如果你发现某个继承体系的类的名称前缀和另一个继承体系的类名称前缀完全相同,便是问到了这种坏味道。消除这种重复性的一般策略是:让一个继承体系的实例引用另一个继承体系的实例。
Lazy Class(冗赘类)
如果一个雷的所得不值其身价,它就应该消失。
Speculative Generality(夸夸其谈未来性)
Temporary Field(令人迷惑的暂时字段)
Message Chains(过度耦合的消息链)
Middle Man(中间人)
不要过度运用委托
Inappropriate Intimacy
Alternative Classes With Different Interfaces(异曲同工的类)
Incomplete Library Class(不完美的类库)
Data Class(纯稚的数据类)
所谓Data Class是指:他们拥有一些字段,以及用于访问(读写)这些字段的函数,除此之外一无长物。这样的类知识一种不会说话的数据容器,他们几乎一定被其他类过分细琐地操控者,应该考虑把它们封装起来。
Refused Bequest(被拒绝的遗赠)
如果子类复用了超类的行为(实现),却又不愿意支持超类的接口,Refused Bequest的坏味道就会变得浓烈。
Comments(过多的注释)
当你感觉需要写注释时,清闲尝试重构,试着让所有注释都变得多余。
第四章:构筑测试体系
一套测试就是一个强大的bug侦测器,能够大大缩减查找bug所需要的时间。
Junit测试框架(用途是单元测试)
频繁地运行测试,每次编译请把测试页考虑进去-每天至少执行每个测试一次。
单元测试/功能测试。功能测试用来保证软件能够正常运作。
当你收到bug报告,请先写一个单元测试来暴露bug。
编写未臻完善的测试并实际运行,好过对完美测试的无尽等待。
考虑可能出错的边界条件,把测试火力集中在那儿。
当事情被认为应该会出错时,别忘了检查是否抛出了预期的异常。
不要因为测试无法捕捉到所有bug就不写测试,因为测试的确可以捕捉到大多数bug。
第五章 重构列表
每个重构手法都有如下五个部分:
首先是名称(name). 建造一个重构词汇表,名称是很重要的。
名称之后是一个简短概要。
动机为你介绍为什么需要这个重构和什么情况下不该使用这个重构。
做法简明扼要地一步一步介绍如何进行此一重构
范例以一个十分简单的例子说明此重构手法如何运作。
第六章 重新组织函数
6.1 Extract Method(提炼函数)
首先,如果每个函数的粒度都很小,那么函数被复用的机会就更大;其次,这会使高层函数读起来就像一系列注释;
函数的命名:以它“做什么”来命名,而不是以它“怎样做”命名。
即使你想要提炼的代码非常简单,例如只是一条消息或一个函数调用,只要心函数的名称能够以更好方式昭示代码意图,你也应该提炼它。但如果你想不出一个更有意义的名称,就别动。
分解临时变量:
如果程序有某个临时变量被赋值超过一次,他既不是循环变量,也不被用于收集计算结果。针对每次赋值,创造一个独立、对应的临时变量。同一个临时变量承担超过一件事情的时候,会令代码阅读者模糊。
移除对参数的赋值:
做法:
建立一个临时变量,把待处理的参数值赋予它;以“对参数的赋值”为界,将其后所有对此参数的引用点,全部替换为“对此临时变量的引用”;修改复制语句,使其改为对新建之临时变量赋值。(如果代码的语义是按引用传递的,在调用端检查调用后是否还使用了这个参数,页要检查有多少个按引用传递的参数被赋值后又被使用,请尽量以return方式返回一个值)。还可以为参数加上关键字final,从而强制他遵循“不对参数赋值”
第九章 简化条件表达式
分解条件表达式 合并条件表达式 合并重复的条件片段
第十章 简化函数调用