如何实施代码重构?

阅读《重构》的笔记献上。算法

重构的定义

重构是在不改变软件可观察行为的前提下改善其内部结构。编程

重构的节奏

以微小的步伐修改程序。若是你犯下错误,很容易即可发现它。数组

  • 一个方法里面,不该该有不少的代码,咱们能够经过分解后重组。session

  • 好的代码应该清楚的表达出本身的功能,变量名称是代码清晰的关键。app

  • 尽可能减小临时变量,大量参数被传来传去,很容易跟丢,可读性差。dom

  • 提炼出逻辑代码,以便功能复用。ide

重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提升其可理解性,下降其修改为本。函数

重构(动词):使用一系列重构首发,在不改变软件可观察行为的前提下,调整其结构。单元测试

为什么重构?

  • 重构改进软件设计测试

  • 重构是软件更容易理解

  • 重构帮助找到bug

  • 重构提升编程速度

什么时候重构?

几乎任何状况下我都反对专门拔出时间进行重构。在我看来重构原本就不是一件应该特别拨出时间作的事情,重构应该随时随地的进行。你不该该为重构而重构,你之因此重构,是由于你想作别的什么事,而重构能够帮助你把那些事作好。

三次法则

第一次作某件事只管去作,第二次作相似的事情会有反感,第三次再作相似的事,你就应该重构。
事不过三,三则重构。

  • 添加功能时重构

  • 修补错误时重构

  • 复审代码时重构

为何重构有用?

难以修改的程序

  • 难以阅读的

  • 逻辑重复的

  • 添加新行为须要修改已有代码的

  • 带复杂条件逻辑的

好的程序

  • 容易阅读

  • 全部逻辑都只是在惟一地点指定

  • 新的改动不会危及现有行为

  • 尽量简单表达条件逻辑

重构是这样一个过程:它在一个目前可运行的程序上进行,在不改变程序行为的前提下使其具有上述美好性质,使咱们可以继续保持高速开发,从而新增程序的价值。

什么时候不应重构?

  • 没法稳定运行直接重写不用重构

  • 项目以及接近最后期限,不该该重构,虽然重构可以提升生产力,可是你没有足够的时间,一般标示你其实早该进行重构了。

代码的坏味道

  • Duplicated Code 重复代码

  • Long Method 过长函数

  • Large Class 过大的类

  • Long Parameter List 过长参数列

  • Divergent Change 发散式变化

  • Shotgun Surgery 散弹式修改

  • Feature Envy 依恋情结 (StrategyVisitor)

  • Data Clumps 数据泥团

  • Primitive Obsession 基本类型偏执

  • Switch Statements switch惊悚现身 (使用多态性替换)

  • 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 纯稚的数据类

  • Refused Bequest 被拒绝的遗赠

  • Comments 过多的注释

当你感受须要撰写注释时,请先尝试重构,试着让全部注释变得多余。

构建测试体系

  • 确保全部测试都彻底自动化,让他们检查本身的测试结果。

  • 一套测试就是一个强大的bug侦察器,可以大大缩减查找bug所须要的时间。

  • 频繁地运行测试。每次编译请把测试也考虑进去—— 天天至少执行每一个测试一次。

  • 每当你收到bug报告,请先写一个单元测试来暴露这个bug。

  • 编写未完善的测试并执行,好过对完美测试的无尽等待。

  • 考虑可能出错的边界条件,把测试火力集中在那儿。

  • 当事情被认为应该会出错时,别忘了检查是否抛出了预期的异常。

  • 不要由于测试没法捕捉全部bug就不写测试,由于测试能够捕捉到大多数的bug。

重构列表

重构记录格式

  • 名称

  • 概要

    • 描述解决的问题

    • 描述要作的事情

    • 速写图展现重构前和重构后的示例

  • 动机

  • 作法

  • 范例

重构的基本技巧—小步前进、频繁测试

模式和重构之间有着一种与生俱来的关系。模式是你但愿到达的目标,重构则是到达之路。

从新组织函数

  • Extract Method 提炼函数

    • 你有一段代码能够被组织在一块儿并独立出来。将这段代码放进一个独立函数中,并让函数名称解释该函数的用途。

  • Inline Method 内联函数

    • 一个函数,其本体应该与其名称一样清楚易懂。在函数调用点插入函数本体,而后移除该函数。

  • Inline Temp 内联临时变量

    • 你有一个临时变量,只被一个简单表达式赋值一次,而它妨碍了其余重构方法。将全部对该变量的引用动做,替换为对它赋值的那个表达式自己。

  • Replace Temp with Query 已查询取代临时变量

    • 你的程序以一个临时变量(temp)保存某一个表达式的运算结果。将这个表达式提炼到一个独立函数(query查询式)中。将这个临时变量的全部被引用点替换为对新函数的调用。新函数可被其余函数使用。

  • Introduce Explaining Variable 引入解释性变量

    • 你有一个复杂的表达式。将该复杂表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途。

  • Split Temporary Variable 分解临时变量

    • 你的程序有某个临时变量被赋值超过一次,它既不是循环变量,也不是一个集用临时变量(collecting temporary variable)。针对每次赋值,创造一个独立。对应的临时变量。

  • Remove Assignments to Parameters 移除对参数的赋值

    • 你的代码对一个参数进行赋值动做。以一个临时变量取代该参数的位置。

  • Replace Method with Method Object 以函数对象取代函数

    • 你有一个大型函数,其中对局部变量的使用,使你没法采用Extract Method。将这个函数放在一个独立的对象中,如此一来局部变量就变成了对象内的值域,而后你能够在同一个对象中将这个大型函数分解为数个小型函数。

  • Substitute Algorithm 替换算法

    • 你想要把某个算法替换为另外一个更清晰的算法。将函数本体替换为另外一个算法。

在对象之间搬移特性

  • Move Method 搬移函数

    • 你的程序中,有个函数与其所驻class以外的另外一个class进行更多交流:调用后者,或被后者调用。在函数最常引用的class中创建一个有着相似行为的新函数。将旧函数变成一个单纯的委托函数,或是将旧函数彻底移除。

  • Extract Class 提炼类

    • 你的程序中,某个field(值域)被所驻class以外的另外一个class更多地用到。在target class 创建一个new field ,修改source field 的全部用户,令它们改用new field.

  • Inline Class 将类内敛化

    • 某个class作了应该由两个classes作的事情。创建一个新的class,将相关的值域和函数从就class搬移到新class。

  • Hide Delegate 隐藏委托类

    • 客户直接调用其server object(服务对象)的delegate class。在sever端(某个class)创建客户所须要的全部函数,用以隐藏委托关系。

  • Remove Middle Man 移除中间人

    • 某个class作了过多的简单委托(simple delegation).让客户直接调用delegate(受托类)。

  • Introduce Foreign Method 引入外加函数

    • 你所使用的server class 须要一个额外函数,但你没法修改这个class。在client class 中创建一个函数,并以一个server class实体做为第一引数(argument)。

  • Introduce Local Extension 引入本地扩展

    • 你所使用的server class 须要一些额外函数,但你没法修改这个class。创建一个新class,使它包含这些额外函数。让这个扩展品成为source class的subclass(子类)或(wrapper)外覆类。

从新组织数据

  • Self Encapsulate Field 自封装字段

    • 你直接访问一个值域(field),但与值域直接的耦合关系变得逐渐变得笨拙。为这个值域创建取值/设值函数,而且只有这些函数来访问值域。

  • Replace Data Value with Object 以对象取代数据值

    • 你有一笔数据项(data item),须要额外的数据和行为。将这笔数据项变成一个对象。

  • Change Value to Reference 将值对象改为引用对象

    • 你有一个class,衍生出许多相等视图(equal instance),你但愿将它们替换为单元对象。将这个 value object(实值对象)变成一个reference object(引用对象)。

  • Change Reference to Value 将引用对象改为值对象 (equals hashCode)

    • 你有一个reference object(引用对象),很小且不可变,并且不易管理。将他变成一个value object(实值对象)。

  • Replace Array with Object 以对象取代数组

    • 你有一个数组(array),其中的元素个各自表明不一样的东西,以对象替换数组。对于数组中的每一个元素,以一个值域表示之。

  • Duplicate Observed Data 复制“被监听数据”

    •  你有一些domain data 置身GUI控件中,而domain method 须要访问之。即那个该笔数据拷贝到以到domain object。创建一个observer模式,用以对domain object和GUI object 内的重复数据进行同步控制(sync).

  • Change Unidirectional Association to Bidirectional 将单向关联改成双向关联

    • 两个classes都须要使用对方特性,但其间只有一条单向链接。添加一个反向指针,并使修改函数可以同时更新两条链接。

  • Change Bidirectional Association to Unidirectional 将双向关联改为单向关联

    • 两个classes之间有双向关联,但其中一个class现在再也不须要另外一个class的特性。去除没必要要的关联(association)。

  • Replace Magic Number with Symbolic Constant 以字面常量取代魔法数

    • 你有一个字面值,带有特别的含义。创造一个常量,根据其意义为它命名,并将上述的字面数值替换为这个常量。

  • Encapsulate Field 封装字段

    • 你的class存在一个public值域。将它声明为private,并提供相应的访问函数。

  • Encapsulate Collection 封装集合

    •  有个函数返回一个群集(collection)。放这个函数返回该群集的一个只读映件,并在这个class中提供添加移除群集元素的函数。

  • Replace Record with Data Class 以数据类取代记录

    • 你须要面对传统编程环境中的record structure(记录结构)。为该record(记录)建立一个哑数据对象(dumb data object)。

  • Replace Type Code with Class 以类取代类型码

    •  class之中有一个数值别码,但他并不影响class的行为。以一个新的class替换数值型别码。

  • Replace Type Code with Subclasses 以子类取代类型码 (多态机制)

    •  你有一个不可变的type code,它会影响class的行为。以一个subclass取代这个type code。

  • Replace Type Code with State/Strategy 以State/Strategy取代类型码

    • 你有一个type code,它会影响class 的行为,但你没法使用subclassing。以state object取代type code.

  • Replace Subclass with Fields 以字段取代子类

    • 你的各个subclasses的惟一差异只在返回常量数据的函数身上。修改这些函数,使他们返回superclass中的某个(新增)值域,而后销毁sublcasses。

简化条件表达式

  • Decompose Conditional 分解条件表达式

    • 你有一个复杂的条件语句。从if、then、else三个段落中分别提出独立函数。

  • Consolidate Conditional Expression 合并条件表达式

    •  你有一系列条件测试,都获得相同结果。将这些测试合并为一个条件式,并将这个条件式提炼成为一个独立函数。

  • Consolidate Duplicate Conditional Fragments 合并重复的条件片断

    • 在条件式的每个分支上着相同的代码。将这个段重复代码搬移到条件式以外。

  • Remove Control Flag 移除控制标记 (break/continue/return)

    • 在一系列布尔表达式中,某个变量带有控制标记的做用,以break语句或return语句取代控制标记。

  • Replace Nested Conditional with Guard Clauses 以卫语句取代嵌套条件表达式 (单独判断被称为“卫语句”)

    • 函数中的条件逻辑令人难以看清正常的执行路径。使用卫语句表现全部特殊的状况。卫语句就是把复杂的条件表达式拆分红多个条件表达式,好比一个很复杂的表达式,嵌套了好几层的if - then-else语句,转换为多个if语句,实现它的逻辑,这多条的if语句就是卫语句

  • Replace Conditional with Polymorphism 以多态取代条件表达式

    • 你手上有个条件式,它根据对象型别的不一样而选择不一样的行为。将这个条件式的每一个分支放进一个subclass内的覆写函数中,而后将原始函数声明为抽象函数。

  • Introduce Null Object 引入Null对象

    • 你须要再三检查某个是否为null value。将null value(无效值)替换为null object(无效物)

  • Introduce Assertion 引入断言

    • 某一段代码须要对程序状态作出某种假设。以assertion(断言)明确表现这种假设。

简化函数调用

  • Rename Method 函数更名

    • 函数的名称未能揭示函数的用途,修改函数名称。

  • Add Parameter 添加参数

    • 某个函数须要从调用端获得更多信息。为此函数添加一个对象参数,让该对象带进函数所需信息。

  • Remove Parameter 移除参数

    • 函数本体再也不须要某个参数,将该参数去除。

  • Separate Query from Modifier 讲查询函数和修改函数分离

    • 某个函数即返回函数对象状态值,又修改对象状态。未来两个不一样的函数,其中一个负责查询,另外一个负责修改。

  • Parameterize Method 令函数携带参数

    • 若干函数作了相似的工做,但在函数本体中却包含了不一样的值。创建单一函数,以参数表达那些不一样的值。

  • Replace Parameter with Explicit Methods 以明确函数取代参数

    • 你有一个函数,其内彻底取决于参数值而采起不一样反应。针对该参数的每个可能值,创建一个独立函数。

  • Preserve Whole Object 保持对象完整

    •  你从某个对象中取出某个值,将它们做为某一次函数调用的参数。该引用(传递)整个对象。

  • Replace Parameter with Methods 以函数取代参数

    • 对象调用某个函数,并将所得结果做为参数,传递给另外一个函数。而接受该参数的函数也能够调用前一个函数。让参数接受者去除该项参数,并直接调用前一个函数。

  • Introduce Parameter Object 引入参数对象

    • 某个参数老是很天然地同时出现。以一个对象取代这些参数。

  • Remove Setting Method 移除设置函数

    • 你的class中的某个值域,应该在对象初创时被设值,而后就再也不改变。去掉该值域的全部设置函数。

  • Hide Method 隐藏函数

    • 有一个函数,历来没有被其余任何class用到。将这个函数改成private。

  • Replace Constructor with Factory Method 以工厂函数取代构造函数

    • 你但愿在建立对象时不只仅是对它作简单的构建工做,将construcotr(构造函数)替换为factory method(工厂函数)

  • Encapsulate Downcast 封装向下转型

    • 某个函数返回的对象,须要由函数调用者执行向下转型动做。将向下转型动做移到函数中。

  • Replace Error Code with Exception 以异常取代错误码

    • 某个函数返回一个特定的代码,用以表示某种错误状况。改用异常。

  • Replace Exception with Test 以测试取代异常

    • 面对一个调用者可预先加以检查的条件,你抛出一个异常。修改调用者,使它在调用函数以前先作检查。

处理归纳关系

  • Pull Up Field 字段上移

    • 两个subclass拥有相同的值域。将此一值域移至superclass。

  • Pull Up Method 函数上移

    • 有些函数,在各个subclass中产生彻底相同的效果。将该函数移至superclass。

  • Pull Up Constructor Body 构造函数本体上移

    • 你的各个subclass中拥有一些构造函数,它们的本体几乎彻底一致。在superclass中新建一个构造函数,并在subclass构造函数中调用它。

  • Push Down Method 函数下移

    • superclass中的某个函数只与部分(而非所有)subclass有关。将这个函数移到相关的那些subclasses去。

  • Push Down Field 字段下移

    • superclass中的某个值域只被部分subclass用到。将这个值域到须要它的那些subclasses去。

  • Extract Subclass 提炼子类

    • class中某些特性只被某些而非所有实体用到。新建一个subclass,将上面所说的那一部分特性移到subclass中。

  • Extract Superclass 提炼超类

    • 两个classes有类似特性。为这连个classes创建一个superclass.将相同特性移至superclass.

  • Extract Interface 提炼接口

    • 若干客户使用class接口中的同一子集。或者,两个Classes的接口有部分相同。将相同的子集提炼到一个独立接口中。

  • Collapse Hierarchy 折叠继承体系

    • superclass和subclass之间无太大区别。将它们和为一体。

  • Form Template Method 塑造模板函数

    • 有一些subclasses,其中相应的某些函数以相同顺序执行相似的措施,但各措施实际上有所不一样。将各个措施分别放进独立函数中,并保持它们都有相同的签名式,因而原函数也就变得相同了。而后将原函数上移至superclass。

  • Replace Inheritance with Delegation 以委托取代继承

    • 某个subclass只使用superclass接口中的一部分,或是更本不须要继承而来的数据。在subclass中新建一个值域用以保存superclass;调整subclass函数,令它改而委托superclass;而后去掉二者之间的继承关系。

  • Replace Delegation with Inheritance 以继承取代委托

    • 你的两个classes之间使用了委托关系,并常常为整个接口编写许多极其简单的请托函数。让请托Class继承受托class。

大型重构

  • Tease Apart Inheritance 梳理并分解继承体系

    • 某个继承体系同时承担两项责任。创建两个继承体系,并经过委托关系让其中一个能够调用另外一个。

  • Convert Procedural Design to Objects 将过程化设计转化为对象设计

    • 你手上有一些代码,以传统的过程化风格写就。将数据记录变成对象,将行为分开,并将行为移入相关对象中。

  • Separate Domain from Presentation 将领域和表述/显示分离

    • 某些GUI classes之中包含了domain login(领域逻辑)。将domain loginc(领域逻辑)分离出来,为它们创建独立的domain classes.

  • Extract Hierarchy 提炼继承体系

    • 你有某个class作了太多工做,其中一个部分工做以大量条件式完成的。创建继承体系,以一个subclass表示一种特殊状况。

相关文章
相关标签/搜索