重构-改善既有代码的设计-读书笔记

Reading time ~11 minutes

此文章为原创文章,引用请标记文章出处: 重构-改善既有代码的设计-读书笔记

为什么要重构?

  • 重构的目的是使软件代码更容易被理解和修改

  • 重构能够帮我们更有效地写出强健的代码

  • 重构可以帮助我们更快速地开发软件,阻止系统腐败变质,提高设计质量

  • 重构不会改变软件可观察的行为——重构之后软件功能一如既往

什么时候应该重构?

  • 添加功能时重构

  • 修补错误时重构

  • 复审代码时重构

什么时候不应该重构?

  • 代码根本无法工作或者太糟糕,重构还不如重写来的简单

  • 在项目的最后期限,应该避免重构

代码的坏味道

  • 重复代码:多个代码类里看到相同的代码或程序结构

  • 过长函数:函数是否有多种职责

  • 过大的类:单个类做太多的事情,其内往往就会出现太多实例变量

  • 过长参数列:过长的参数会难以理解,是否能用一个对象替代

  • 发散式变化、霰弹式修改:如果受外界变化的影响而需要对类的代码做出多出修改,那应该把变化的内容放在一起成为类或函数。

  • 依恋情结:判断哪个类拥有最多被此函数使用的数据,然后就把这个函数和那些数据放在一起

  • 数据泥团:放在对象里的属性应该和对象的意义仅仅关联

  • 基本类型偏执:该用对象的时候用对象,改用基本类型变量的时候用基本类型变量

  • switch惊悚现身:只当使用switch语句能使代码结构更清晰的时候才用

  • 平行继承体系:当你为某一个类增加子类时,也必须为另一个类相应增加一个类,解决方法是使用引用替代继承

  • 冗赘类:如果一个类不值得存在,那就让它消失

  • 夸夸其谈的未来星:无用的抽象类、无用的抽象参数都可以移除

  • 令人迷惑的暂时字段:只用一次的临时变量一般情况下都可以移除

  • 过度耦合的消息链:清楚消息链上的对象的作用,能否把使用链上的该对象的代码提炼到独立函数中

  • 中间人:如果某个类接口有一半的函数都委托给其他类,说明可以移除委托类,让代码直接和真正负责的对象打交道

  • 狎昵关系:多个类是否具有共同点太多,可以提取共同点为抽象类

  • 异曲同工的类:是否存在不同名字的类或函数,做着相同的事

  • 过多的注释:注释是在别人无法单从你的代码就从快速理解其作用时使用

重构列表

1.重新组织函数

  • 提炼函数:保证提炼代码可以强化逻辑清晰度,并能通过函数名称解释函数的用途

  • 内联函数:如果函数比较短,且其内容就和其名称一样清晰易懂,就应该在函数调用点替换为函数本体,然后移除该函数。

  • 内联临时变量:你有一个临时变量,只被一个简单表达式赋值一次并只被引用一次,那就把临时变量删掉,并在引用点替换为对表达式自身。但如果是被引用多次且不止一个函数内使用到,那么可以将这个表达式提炼到一个独立函数中,并将这个临时变量的所有引用点替换为对新函数的调用。

  • 引入解释性变量:你有一个复杂的表达式。应该拆分复杂表达式为多个部分,分到多个临时变量,并以此变量名称来解释这个被拆分的表达式的用途。

  • 分解临时变量:如果程序有某个临时变量被多个用途的代码赋值过,并且会对代码的清晰度有影响,那么可以针对每次赋值,创造一个独立、对应的临时变量。

  • 移除对参数的赋值:代码对一个参数进行赋值,如果会应该代码的清晰度,则以一个临时变量取代参数的位置。

  • 替换算法:你想要把某个算法替换为另一个更清晰的算法。

2.在对象之间搬移特性

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

  • 搬移字段:你的程序中,某个字段被其所驻类之外的另一个类更多地用到。在目标类新建一个字段,删除源字段,并修改引用源字段的地方为新字段。

  • 提炼类:某个类做了应该有两个类做的事。建立一个新类,将相关的字段和函数从旧类搬移到新类。

  • 将类内联化:某个类没有做太多事情。将这个类的所有特性根据其职责搬移到另一个或多个类中,然后移除原类。

  • 隐藏“委托关系”:如果调用的几段代码之间是有逻辑上的关联,那么可以考虑把其中一个调用当做委托来调用另一段代码,用以隐藏调用的实际逻辑关系。

  • 移除中间人:某个类做了过多的简单委托动作。让客户直接调用受托类,可以的话删除委托类。

  • 引入外加函数:你需要为提供服务的类增加一个函数,但你无法修改这个类且这个函数其实只需要用到服务实例的属性,则可以在客户类中建立这个函数,并以参数形式传入服务类实例。

  • 引入本地扩展:你需要为服务类提供一些额外函数,但你无法修改这个类。建立一个新类并继承源类,使它包含这些额外函数。

3.重新组织数据

  • 自封装字段:你直接访问一个字段,但与字段之间的耦合关系逐渐变得笨拙。为这个字段建立getter/setter函数,并且只以这些函数来访问字段。

  • 以对象取代数据值:你有一个数据项,需要与其他数据和行为一起使用才有意义,将数据项归类变成对象。

  • 以对象取代数据:你有一个数组,其中的元素各自代表不同的东西([“15”,”hello”])。以对象替换数组,对于数组中的每个元素,以一个字段来表示。

  • 将单向关联改为双向关联:两个类都需要使用对方特性,则在关联函数中使其能够同时更新两条链接。

  • 将双向关联改为单向关联:两个类之间有双向关联,但其中一个类如今不再需要另一个类的特性。在关联函数中去除不必要的更新。

  • 以字面常量取代魔法数:你有一些字面数值,带有特别含义。创造一些常量或枚举的方式,根据其意义命名,并将上述的字面数值替换为这个常量。

  • 封装字段:你的类中存在一个public字段。将它声明为private,并提供相应的访问函数。

4.简化条件表达式

  • 分解条件表达式:如果你的条件(if-then-else)语句的代码很复杂,则可以从if、then、else三分段落中分别提炼出独立函数。

  • 合并重复的条件片段:在条件表达式的每个分支上有着相同的一段代码,则将这段重复的代码搬移到条件表达式之外。

  • 移除控制标记:在循环的表达式中,如果考虑到表达式的可读性,可以用break语句或return语句取代控制循环的条件表达式。

  • 以卫语句取代嵌套条件表达式:函数中嵌套的条件逻辑往往使代码不清晰,如果某些条件是极其罕见的,则可以允许把嵌套的条件提取出来进行单独检查。

  • 以多态取代条件表达式:你手上有个条件表达式,它根据对象类型的不同选择不同的行为。将这个条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数。

  • 提炼继承体系:你有某个类做了太多工作,其中一部分工作是以大量条件表达式完成的。建立继承体系,以一个子类表示一种特殊情况。

5.简化函数调用

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

  • 添加参数:某个函数需要传入的参数很多时,可以替换为一个对象来作为入参,让该对象带进函数所需信息。

  • 保持对象完整:你从某个对象中取出若干值,将它们作为某一次函数调用时的参数。改为传递整个对象。

  • 引入参数对象:某些参数总是很自然地同时出现。以一个对象取代这些参数。

  • 移除参数:函数本体不再需要某个参数。将该参数去除。

  • 将查询函数和修改函数分离:某个函数既返回对象状态值,又修改对象状态。建立两个不同的函数,其中一个负责查询,另一个负责修改。

  • 令函数携带参数:若干函数做了类似的工作,但在函数本体中却包含了不同的值。提取相同的代码为统一调用的函数,以入参变量替换那些不同的值。

  • 以明确函数取代参数:如果一个函数根据入参值而采取不同行为且某些行为的代码量很大时,应该针对某些代码量很大的参数来建立一个独立函数。

  • 以工厂函数取代构造函数:你希望在创建对象时不仅仅是做简单的构建动作,将构建函数替换为工厂函数。

  • 封装向下转型:如果希望某个函数返回的对象可以直接操作,将向下转型动作移到函数中。

  • 以异常取代错误码:如果不是调用外系统的函数,可以改用异常或自定义异常直接抛出以表示某种错误情况,否则返回一个特定的代码或结果对象。

6.处理概括关系

  • 字段上移:两个子类拥有相同的字段,将该字段移至超类。

  • 函数上移:有些函数,在各个子类中产生完全相同的结果,将该函数移至超类。

  • 函数下移:超类中的某个函数只与部分(而非全部)子类有关。将这个函数移到相关的那些子类去。

  • 字段下移:超类中的某个字段只被部分(而非全部)子类用到。将这个字段移到需要它的那些子类去。

  • 提炼子类:类中的某些特性只被某些(而非全部)实例用到。新建一个子类,将上面所说的那一部分特性移到子类中。

  • 提炼超类:两个类有相似特性。为这两个类建立一个超类,将相同特性移至超类。

  • 提炼接口:如果某个类在不同环境下扮演截然不同的角色,使用接口;多个种类的服务对象,使用接口作为服务提供方来提供操作

  • 塑造模板函数:你有一些子类,其中相应的某些函数以相同顺序执行类似的操作,但各个操作的细节上所有不同,则可以替换为使用设计模式中的模板方法

7.大型重构

  1. 梳理并分解继承体系:某个继承体系同时承担两项责任。建立两个继承体系,并通过委托关系让其中一个可以调用另一个。

  2. 将过程化设计转化为对象设计:你手上有一些传统过程化风格的代码。将数据记录变成对象,将大块的行为分成小块,并将行为移入相关对象之中。

实践

  1. 不要过早发布接口,请修改你的代码所有权政策,使重构更顺畅。

  2. 对于一个重要的遗留系统,可以重构为封装良好的小型组件,然后你就可以逐一对组件做出“重构或重建”的决定。

  3. 重构能让你对问题的理解加深,让日后修改成本不再高昂

  4. 当你感觉需要撰写注释时,请先尝试重构,试着让所有注释都变得多余

  5. 在性能优化阶段,首先应该用一个度量工具来监控程序的运行,让它告诉你程序中哪些地方大量消耗时间和空间,这样就可以找出性能热点所在的一小段代码,然后集中关注这些热点,短期看来,重构的确可能使软件变慢,但它使优化阶段的软件性能调整更容易,最终还是会得到好的结果。

  6. 多运用单元测试,重构的基本技巧-小步前进、频繁测试

jdk-timer、spring-task、quartz的比较

这篇文章将简单介绍目前常用的定时任务框架! Continue reading

小岛经济学-读书笔记

Published on May 10, 2018

java常见的http请求库

Published on May 05, 2018