重构——改革既有代码的宏图

重构——改正既有代码的筹划
Refactoring——Improve the Design of Existing Code
作者: 马丁 富勒Martin Fowler


壹 、什么是重构?

所谓重构是那般壹个过程在不转移代码外在表现的前提下,对代码作出修改,以改进程序的内部结构。本质上说,重构就是在代码写好未来改进它的规划

重构(名词):对软件内部结构的一种调动,目标是在不更改软件可寓目行为的前提下,进步其可了然性,下落其修改费用
重构(动词):使用一层层重构手法,在不改动软件可观察行为的前提下,调整其组织

重构的目的是使软件更便于被清楚和改动。重构不会变动软件可旁观的行事——重构之后软件功效还是。

重构技术就是以轻微的步伐修改程序,即使您犯下错误,很不难便足以发现它。


二 、为什么重构?

(1)
重构革新软件设计。如若没有重构,程序的设计会日渐堕落变质。重构很像是在整理代码,你所做的就是让全部东西回到应出的职分上。常常性的重构可以协理维持自个儿该有的形态。

(2)重构使软件更易于精晓。重构可以扶持我们让代码更易读。

(3)重构支持找到bug。对代码进行重构,可以协理大家深远驾驭代码,对代码领悟的越深,就越能帮大家找到bug。重构可以帮我们更有效地写出健康的代码。

(4)重构进步成为速度。重构可以协理大家更快速地开发软件,因为它阻挡系统腐败变质,它甚至还足以增加统筹品质。


③ 、哪一天重构?

重构不是一件理当特别拨出时间做的事情,重构应该随时遍地进行。不应当为重构而重构,之所以重构,是因为大家想做别的什么事,而重构可以资助我们把那几个事做好。

五回法则:事可是三,三则重构

(1)添加效应时重构。
(2)修补错误时重构。
(3)复审代码时重构。


④ 、曾几何时不应该重构?

代码根本不能工作可能太不佳,重构还不如重写来的简易

在项目的末梢期限,应该幸免重构


五 、代码的坏味道

  • 再一次代码(Duplicated Code)
  • 过长函数(Long Method)
  • 过大的类(Large Class)
  • 过长参数列(Long Parameter List)
  • 发散式变化(Divergent Change):三个类受二种生成的熏陶
  • 霰弹式修改(Shotgun Surgery):一种转移引发多少个类对应修改
  • 留恋情结(Feature Envy):函数对有个别类的趣味高过本人所处类的兴趣
  • 多少泥团(Data
    Clumps):相同的多少项数据出现在分化地方,这一个绑在一齐出现的多少应该有属于它们本人的目标
  • 中央类型偏执(Private
    Obsession):很多人不情愿在小职责上运用小目标
  • switch惊悚出现(Switch
    Statements):switch语句会在不胜枚举位置重复出现,一改则需全改
  • 平行继承体系(Parallel Inheritance
    Hierarchies):当你为某二个类扩充子类时,也不可以不为另一个类对应增多3个类
  • 冗赘类(Lazy Class):借使一个类不值得存在,那就让它没有
  • 说长话短的前途星(Speculative
    Generality):预留的不行的抽象类,无用的虚幻参数
  • 令人迷惑的一时半刻字段(Temporary
    Field):类中有个别字段只为某个特殊情状而设置
  • 过度耦合的讯息链(Message
    Chains):用户向壹个对象请求另四个目的,然后再向后者请求另1个对象……
  • 中间人(Middle Man):无用的寄托,过多的中间层
  • 浪漫关系(Inappropriate
    Intimacy):八个类过于亲近,贰个类过于关切另2个类的积极分子
  • 如出一辙的类(Alternative Classes with Different
    Interfaces):不一致名字的类或函数,笔者相同的事
  • 不周全的库类(Incomplete Library Class):类库设计不可以完美
  • 纯数据类(Data
    Class):二个类具有一些字段以及用于访问那个字段的函数,除此之外一无长物
  • 被驳回的遗赠(Refused
    Bequest):子类不想继承超类全数的函数和数目,只想挑几样来玩
  • 过多的诠释(Comments)

六 、构筑测试系统

  1. 重构的重大前提是负有三个保险的测试环境
  2. 比方写好一点效益,就旋即添加测试,并确保全体测试都完全自动化,让它们检查自身的测试结果。一套测试就是三个强劲的bug侦测器,可以大大压缩查找bug所急需的年月。
  3. 撰写测试代码的最得力时机是在起初编程之前。当您需求添加天性的时候,先写相应测试代码。编写测试代码其实就是在问自身:添加这几个功效要求做些什么。编写测试代码还是可以使您把注意力集中于接口而非落成。预先写好的测试代码也为您的做事设置2个鲜明的完成标志:一旦测试代码平常运维,工作就足以截止了。
  4. 多应用单元测试。测试你最放心不下出错的地点,考虑大概出错的界限条件。不要因为测试不大概捕捉全数bug就不写测试,因为测试的确可以捕捉到大部分bug。“花合理时间抓出大多数bug”要好过“穷尽生平抓出具有bug”。

7、重新协会函数

  1. 提炼函数(Extract
    Method)。你有一段代码可以被公司在联合并独自出来。将那段代码放进1个单身函数中,并将函数名称解释该函数的用途。
  2. 内联函数(Inline
    Method)。五个函数的本体与名称一致清楚易懂。在函数调用点插入函数本体,然后移除该函数。
  3. 内联一时变量(Inline
    Temp)。你有三个一时半刻变量,只被1个简便表明式赋值四回,而它妨碍了别样重构手法。将全部对该变量的引用动作,替换为对它赋值的那些表明式自己。
  4. 以询问取代一时变量(Replace Temp with
    Query)。你的次第以1个权且变量保存某一表明式的运算结果。将以此表明式提炼到贰个单身函数中。将这些一时半刻变量的兼具引用点替换为对新函数的调用。此后,新函数就可被其余函数使用。
  5. 引入解释性变量(Introduce Explaining
    Variable)。你有2个错综复杂的表明式。将该复杂表达式(或内部一些)的结果放进一个目前变量,以此变量名称来诠释表明式用途。
  6. 讲演权且变量(Split Temporary
    Variable)。你的主次有有个别一时半刻变量被赋值过一遍,它既不是循环变量,也不被用来采集总计结果。针对每一遍赋值,创设三个单独、对应的一时变量。
  7. 移除对参数的赋值(Remove Assignments
    Parameters)。代码对3个参数进行赋值。以三个一时半刻变量取代参数的地点。
  8. 以函数对象取代函数(Replace Method with Method
    Object)。你有多个巨型函数,其中对有的变量的利用使你不能选用Extract
    Method。将以此函数放进3个独自对象中,如此一来局地变量就成了目的内的字段。然后你可以在同多个对象少将以此巨型函数分解为三个小型函数。
  9. 替换算法(Substitute
    Algorithm)。你想要把某部算法替换为另三个更清晰的算法。将函数本体替换为另2个算法。

⑧ 、在对象之间搬移特性

  1. 搬移函数(Move
    Method)。你的次第中,有个函数与其所驻之外的另2个类举行越多交换:调用后者,或被继承人调用。在该函数最常引用的类中创建二个颇具相仿行为的新函数。将旧函数变成二个仅仅的寄托函数,或是将旧函数完全移除。
  2. 搬移字段(Move
    Field)。你的顺序中,某些字段被其所驻类之外的另贰个类越多地用到。在目标类新建二个字段,修改源字段的具备用户,令它们改用新字段。
  3. 提炼类(Extract
    Class)。某个类做了应有有五个类做的事。建立二个新类,将相关的字段和函数从旧类搬移到新类。
  4. 将类内联化(Inline
    Class)。某些类没有做太多工作。将这么些类的持有天性搬移到另一个类中,然后移除原类。
  5. 暗藏“委托关系”(Hide
    Delegate)。客户通过一个信托来调用另壹个对象。在劳务类上确立客户所需的具有函数,用以隐藏委托关系。
  6. 移除中间人(Remove Middle
    Man)。某些类做了过多的简要委托动作。让客户直接调用受托类。
  7. 引入外加函数(Introduce Foreign
    Method)。你须要为提供服务的类增添一个函数,但您无法修改这几个类。在客户类中确立二个函数,并以第1参数形式传播三个服务类实例。
  8. 引入本地增添(Introduce Local
    Extension)。你需求为服务类提供部分附加函数,但您没办法修改那个类。建立壹个新类,使它包含那些额外函数。让这些扩充品成为源类的子类或包装类。

⑨ 、重新社团数据

  1. 自封装字段(Self Encapsulate
    Field)。你一直访问三个字段,但与字段之间的耦合关系逐级变得粗笨。为那一个字段建立取值/设值函数,并且只以这一个函数来拜会字段。
  2. 以目的取代数据值(Replace Data Value with
    Object)。你有1个数据项,需求与任何数据和行为一起利用才有含义。将数据项改成对象。
  3. 将值对象改为引用对象(Change Value to
    Reference)。你从1个类衍生出累累并行卓越的实例,希望将它们替换为同3个对象。将那一个值对象变成引用对象。
  4. 将引用对象改为值对象(Change Reference to
    Value)。你有贰个引用对象,很小且不可变,而且不易管理。将它成为一个值对象。
  5. 以目标取代数据(Replace Array with
    Object)。你有3个数组,其中的要素分别代表差距的东西。以目的替换数组,对于数组中的每一个成分,以1个字段来代表。
  6. 复制“被监视数据”(Duplicate Observed
    Data)。你有一对天地数据置身GUI控件中,而世界函数要求拜访这个数量。将该数量复制到3个天地对象中。建立三个Observe方式,用以同步领域对象和GUI对象内的重复数据。
  7. 将单向关系改为双向关联(Change Unidirectional Association to
    Bidirectional)。多个类都亟待接纳对方特性,但中间唯有一条单向链接。添加多个反向指针,并使修改函数可以同时更新两条链接。
  8. 将双向关联改为单向关系(Change Bidirectional Association to
    Unidirectional)。多少个类之间有双向关联,但中间二个类近来不再必要另1个类的特征。去除不须求的涉嫌。
  9. 以字面常量取代魔法数(Replace Magic Number with Symbolic
    Constant)。你有一个字面数值,带有尤其意义。创制三个常量,依照其意思为它定名,并将上述的字面数值替换为那么些常量。
  10. 封装字段(Encapsulate
    Field)。你的类中留存3个public字段。将它注明为private,并提供对应的走访函数。
  11. 包装集合(Encapsulate
    Collection)。有个函数再次回到二个汇聚。让那么些函数再次回到该集合的二个只读副本,并在这一个类中提供丰盛/移除集合成分的函数。
  12. 以数据类取代记录(Replace Record with Data
    Class)。你需求直面传统编程环境中的记录协会。为该记录创建三个“哑”数据对象。
  13. 以类取代类型码(Replace Type Code with
    Class)。类之中有3个数值类行码,但它并不影响类的行事。以2个新的类替换该数值类型码。
  14. 以子类取代类型码(Replace Type Code with
    Subclass)。你又3个不可变的类型码,它会影响类的表现。以子类取代这些类型码。
  15. 以State/Strategy取代类型码(Replace Type Code with
    State/Strategy)。你有2个类型码,它会潜移默化类的表现,但您无法通过两次三番手法化解它。以状态对象取代类型码。
  16. 以字段取代子类(Replace Subclass with
    Fields)。你的相继子类的绝无仅互分歧只在“重返常量数据”的函数身上。修改那么些函数,使他么再次回到超类中的有些(新增)字段,然后销毁子类。

⑩ 、简化条件表明式

  1. 解说条件表明式(Decompose
    Conditional)。你有二个复杂的规范(if-then-else)语句。从if、then、else三分段落中分头提炼出独立函数。
  2. 联合条件表明式(Consolidate Conditional
    Expression)。你有一种类标准测试,都得到平等结果。将这个测试合并为2个规格说明式,并将那几个规则表明式提炼成为一个独立函数。
  3. 集合重复的原则片段(Consolidate Duplicate Conditional
    Fragments)。在规范表达式的各类分支上具备相同的一段代码。将那段重复的代码搬移到条件表明式之外。
  4. 移除控制标记(Remove Control
    Flag)。在一名目繁多布尔表达式中,有些变量带有“控制标记”的效应。以break语句或return语句取代控制标记。
  5. 以卫语句取代嵌套条件表明式(Replace nested Conditional with
    Guard
    Clauses)。函数中的条件逻辑使人为难看清平常的施行路径。使用卫语句表现有所的超常规意况。
  6. 以多态取代条件表明式(Replace Conditional with
    Polymorphism)。你手上有个标准化表明式,它依据目的类型的不一致选项不相同的表现。将以此规则表明式的各样分支放进2个子类内的覆写函数中,然后将原始函数申明为架空函数。
  7. 引入Null对象(Introduce Null
    Object)。你须求频繁检查某目标是或不是为null。将null值替换为null对象。
  8. 引入断言(Introduce
    Assertion)。某一段代码需求对先后状态做出某种假如。以断言鲜明表现那种假若。

十一 、简化函数调用

  1. 函数改名(Rename
    Method)。函数的名称不恐怕揭破函数的用途。修改函数的名号。
  2. 增加参数(Add
    Parameter)。某些函数必要从调用端得到越来越多音讯。为此函数添加一个对象参数,让该目的带进函数所需音讯。
  3. 移除参数(Remove
    Parameter)。函数本体不再须要有个别参数。将该参数去除。
  4. 将查询函数和改动函数分离(Separate Query from
    Modifier)。有个别函数既重返对象意况值,又涂改对象情状。建立五个不等的函数,其中一个承担查询,另三个承受修改。
  5. 令函数指导参数(Parameterize
    Method)。若干函数做了近似的做事,但在函数本体中却含有了差距的值。建立单一函数,以参数表明那贰个不一致的值。
  6. 以显然函数取代参数(Replace Parameter with Explicit
    Methods)。你有三个函数,其中完全在于参数值而采纳两样行为。针对该参数的每3个可能值,建立2个独门函数。
  7. 保持对象完整(Preserve Whole
    Object)。你从有个别对象中取出若干值,将它们作为某五回函数调用时的参数。改为传送整个对象。
  8. 以函数取代参数(Replace Parameter with
    Methods)。对象调用有个别函数,并将所得结果作为参数,传递给另一个函数。而接受该参数的函数本身也可以调用前一个函数。让参数接受者去除该项参数,并直接调用前2个函数。
  9. 引入参数对象(Introduce Parameter
    Object)。某个参数总是很当然地同时出现。以1个目的取代那么些参数。
  10. 移除设值函数(Remove Setting
    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)。面对贰个调用者可以预先检查的尺度,你抛出了贰个至极。修改调用者,使它在调用函数此前先做检讨。

十二 、处理包蕴关系

  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)。你在七个类之间利用委托关系,并平时为任何接口编写许多极简单的寄托函数。让委托类来继承受托类。

十三 、大型重构

  1. 梳理并解释继承体系(Tease Apart
    Inheritance)。有些继承连串同时担当两项义务。建立五个持续系列,并透过委托关系让其中三个方可调用另二个。
  2. 将进程化设计中转为目的设计(Convert Procedural Design to
    Objects)。你手上有一些古板进程化风格的代码。将数据记录变成对象,将大块的行为分成小块,并将表现移入有关对象之中。
  3. 将世界和公布/突显分离(Separate Domain from
    Presentation)。有个别GUI类之中包罗了世界逻辑。将世界逻辑分离出来,为它们创立单独的世界类。
  4. 提炼继承连串(Extract
    Hierarchy)。你有某各项做了太多做事,其中一部分办事是以大气原则表明式完成的。建立继续种类,以二个子类表示一种特有景况。

十肆 、经典句子

  1. Any fool can write code that a computer can understand. Good
    programmers write code that humans can understand.
    ——Martin
    Fowler

    其余3个白痴都能写出计算机可以精通的代码。唯有写出人类不难通晓的代码,才是优质的代码。
    ——马丁 Fowler
  2. I’m not a great programmer; I’m just a good programmer with
    great habits.
    ——Kent Beck
    作者不是个伟大的程序员,作者只是三个颇具一些精粹习惯的好程序员。
    ——Kent Beck
  3. Computer Science is the discipline that believes all problems
    can be solved with one more layer of indirection.
    ——Dennis
    DeBruler

    电脑科学是这么一门科学:它相信全部标题都可以由此增添3个直接层来化解。——Dennis
    DeBruler

本人的心得:

  1. 读者最好有自然的品种经验,大概打听过局地代码风格、设计格局、代码设计经济学等。
  2. 那是一本以java语言来讲重构的书,读者最好懂面向对象的基本知识,通晓java、C++等面向对象的语言。
  3. 本身不是作java开发的,不过笔者领汇合向对象,精晓C++,java。书中的一些冲突和指点在其余设计语言上也有很大的协理,并且本身意识,在档次中小编这儿就在运用着有个别重构方法。书中那多少个过于结合面向对象的卷入、继承、多态等知识的重构方法,大概就只适用于面向对象的言语吧。
  4. **您值得一读。反复看,反复读。 **

小编的风马牛不相干:

  1. 被你们发现了,其实小编也是三个程序员,依旧低档程序员。
  2. 这本书听新闻说是java进阶不可不看,也是程序员不可不看的经文图书。设计语言本人只怕有分别,造成部分特征也会有分别,然则有个别思想仍然相通的。全数,非java、C++语言工小编,也迟早要读一下。
  3. 没有根据的话stack
    overflow.com上刊登的一篇《哪一本书最具影响力,是每种程序员都应当读的?》。《重构》那本书就排行前十,貌似是第④名。

重构——改变既有代码的设计.jpg


ps:我的笔记只记录了理论部分,书上每种重构方法都配有实例代码来讲解,大家可以细细研究。