重构-读书笔记

Refactor

Posted by Monk on July 5, 2019

重构

概念:在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构。 重构技术就是以微小的步伐修改程序。如果你犯下错误,很容易便可发现它。

第一章 重构,第一个案列

  1. 何时重构:如果你发现自己需要为程序添加一个特性,而代码结构使你无法和方便的达成目的,那就先重构那个程序,使特性的添加比较容易进行,然后再添加特性。
  2. 重构的步骤
    • 第一个步骤永远相同:我得为即将修改的代码建立一组可靠的测试环境,这些测试必须有自我检验能力
    • 分解并重组
    1. 找出函数内的局部变量和参数,任何不会被修改的变量都可以被当成参数传入新的函数,如果只有一个变量会被修改,可以将他作为返回值。
    2. 去除临时变量
    3. 运用多态和继承

第二章 重构原则

  1. 概念
    • 名词概念:对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
    • 动词概念:使用一系列重构手法,在不改变软件可观察行为的前提下,改变其结构。
  2. 重构的目的
    • 改进软件设计:消除重复代码
    • 使软件更容易理解:利用重构来协助理解不熟悉的代码,然后理解其用途,然后进行修改,使其能更好的反映出我的理解
    • 帮助找Bug:对代码进行重构,深入理解代码行为,做出假设。
    • 提高编程速度:良好的设计是快速开发的根本
  3. 什么时候重构
    • 三次法则:事不过三,三则重构(消除重复代码)
    • 添加新功能时:代码的设计无法帮助我轻松添加我所需要的新特性
    • 修补错误时
    • 复审代码时
    • 结论:我们希望程序长这样
    1. 容易阅读
    2. 所有逻辑都只在唯一地点指定
    3. 新的改动不会危及现有行为
    4. 尽可能简单表达条件逻辑
  4. 重构的难题:在重构时,应该实时掌握其过程,注意寻找可能引入的问题
    • 接口的修改:让旧接口调用新接口,留下旧函数,调用新函数。旧的接口或者函数可以使用Java自带的 @Deprecation注解。所以不要过早的发布接口,请修改你的代码所有权政策,使重构更顺畅
  5. 不应该重构:当代码实在太混乱的时候,可能重写比重构更有效,此时应该放弃重构

第三章 代码坏味道

  1. 重复的代码:如果在一个以上的地点看到相同的程序结构,那么可以肯定,设法将他们合二为一,使程序变得更好
    • 同一个类的两个函数含有相同的表达式
    • 两个互为兄弟的子类含有相同的表达式,应该将其抽取并放到父类中
    • 如果两个毫不相关的类出现同样的代码,可以将重复代码提炼到一个独立的类中
  2. 函数体过长
  3. 类过大
  4. 参数列表过长:封装成对象来进行传递
  5. 异曲同工的类:应该重新思考其用途并重新命名。
  6. 过多的注释:当感觉需要些注释的时候,先尝试重构,让所有的注释都变得多余,尽量运用重构手法让我们不写注释。

第四章 构筑测试体系

  • 测试的最终目的:确保所有的测试都完全自动化,让他们自己检查测试结果,一套测试就是一个强大的Bug侦测器,它能够极大的缩减找Bug的时间。
  • Junit测试:主要用来编写单元测试
    1. 频繁的运行测试,每次编译请把测试也考虑进去,每天至少执行每个测试一次
    2. 每当收到一个Bug报告,应当先写一个单元测试来暴露Bug
  • 单元测试和功能测试
    1. 单元测试:编写单元测试的目的是为了提高生产效率
    2. 功能测试:用于保证软件能正常运作。
  • 测试应该放在你最担心出错的部分,这样才能从中得到最大的利益
  • 考虑可能出错的边界条件,把测试火力集中在此
  • 总结:请构筑一个良好的Bug检测器并经常运行它,这对任何开发工作都大有裨益,并且是重构的前提

第五章 重构列表

  1. 重构的记录格式
    • 名称:建造一个重构词汇表
    • 概要:简短的概要
    1. 用一句话介绍这个重构能帮我们解决的问题
    2. 一段简短陈述,介绍该做的事
    3. 一副速写图,展现重构前后实例 * 动机:告诉你“为啥子需要这个重构”,还有什么情况下“不应该使用这个重构” * 做法:介绍如何一步一步的进行重构 * 范例:用一个简单的例子说明该重构
  2. 重构的基本技巧:小步前进,频繁测试

第六章 重新组织函数

总体:重构手法

  1. Extract Method(提炼函数):把一段代码从原先的函数中提取出来,放到一个单独的函数中
    • 做法:
    1. 创造一个新函数,根据意图来命名(以它“做什么”来命名)
    2. 将被提炼代码中需要读取的局部变量,作为参数来传递
  2. Inline Method(内联函数):将一个函数调用的动作替换为函数本体
    • 做法:
    1. 检查函数,确定它不具有多态性(如果有子类来继承该函数,是不能做内联的)
    2. 找出该函数的所有被调用点
    3. 将这个函数的所有调用点都替换为函数的本体
  3. Inline Temp(内联临时变量):有一个临时变量,只被一个简单的表达式赋值了一次
  4. 检查给临时变量赋值的语句,确保等号右边的表达式没有副作用
  5. 如果该变量没有被声明为final类型,那么可以声明为final,然后编译,这样的目的是确保该变量是否真的被只赋值了一次
  6. 找到该变量的引用点,替换为临时变量的赋值表达式
  7. Replace Temp with Query(以查询取代临时变量):以临时变量保存表达式的结果
    • 做法:
    1. 找出只被赋值一次的变量
    2. 声明为final类型
    3. 将该临时变量右边的表达式提炼到一个函数中去
  8. Introduce Explain Variable(引入解释性变量):将复杂的表达式的结果放入一个临时变量来解释其用途
    • 做法:
    1. 声明一个final类型的临时变量,将待分解的复杂表达式中的一部分运算结果赋值给它
    2. 将表达式中的“运算结果”这一部分,替换为上述临时变量
  9. Split Temporary Variable(分解临时变量):针对每次赋值,创造一个独立,对应的临时变量
    • 做法:如果一个变量被赋值超过一次,意味着在函数中承担了一个以上的责任,那么就应该分解临时变量
    1. 在待分解临时变量的声明及第一次赋值处,修改其名称
    2. 为新的变量声明为final
    3. 以该临时变量的第二次赋值处为界,修改其所有的名称为新定义的变量
  10. Remove Assignments to Parameters(移除对参数的赋值)
    • 做法:
    1. 建立一个临时变量,把待处理的参数值赋予它
    2. 以对参数的赋值为界,将其后所有对此参数的引用点,全部替换为对临时变量的引用
    3. 修改赋值语句,使其改为对新建的临时变量赋值
  11. Replace Method with Method Object(以函数对象取代函数):将这个函数单独放到一个对象中,如此一来局部变量就成了成员变量,然后将这个大型函数分解为多个小型函数
    • 做法:
    1. 建立新类,根据待处理函数的用途来命名
    2. 在新类中建立一个原对象的final变量,用来保存原先大型函数所在的对象
    3. 建立大型函数中每个临时变量作为成员变量
  12. Substitute Algorithm(替换算法)
    • 做法:
    1. 准备好另一个算法,并使之通过编译
    2. 针对现有测试,执行上述算法。如果结果与原本结果相同,重构结束
    3. 如果不同,那么以旧算法为参考

第七章 在对象之间搬移特性

概念:在对象的设计过程中,“决定把责任放在哪儿”即使不是最重要的事情,也是最重要的事情之一”

  1. Move Method(搬移函数):在程序中,有个函数与其所驻类之外的类有更多的交流:调用后者,或者被后者调用。
    • 做法:在该函数最常引用的类中建立一个有着类似行为的函数。将旧函数变为单纯的委托函数,或是将就函数移除
    1. 检查源类中被源函数所使用的一切特性(包括字段和函数),考虑是否应该被搬移
    2. 检查源类中子类和超类是否有该函数的声明(多态性)。
    3. 在目标类中建立新函数,被搬移源函数的函数体
    4. 修改源函数,使之成为一个纯委托函数
  2. Move Filed(搬移字段):在程序中,某个字段被其所驻类之外的另一个类用得更多。
    • 做法:在目标类新建一个字段,修改源字段的所有用户,令他们改用新字段
    1. 如果该字段是public的,应该先进行封装起来
    2. 在目标类中建立相同的字段,并提供set/get方法
    3. 删除源中的字段
  3. Extract Class(提炼类):某个类做了应该由两个或多个类做的事情
    • 做法:建立一个新类,将相关的字段和函数提炼到新的类中
    1. 决定如何分解类的责任
    2. 建立一个新类,用于表现出旧类中分解出来的责任
    3. 建立两个类之间的访问连接关系
  4. Inline Class(将类内联化):类没有做太多事情。
    • 做法:将这个类中的所有特性搬移到另一个类中,然后移除源类
    1. 在目标类上什么源类的协议,将其中的所有函数委托至源类
    2. 修改所有源类的引用点,改为目标类的引用
    3. 运用Move Filed和Move Method将源类的变量和函数移动到目标类中
  5. Hide Delegate(隐藏“委托关系”):通过一个委托类来调用另一个对象
    • 做法:在服务的类上建立所需的所有函数,用以隐藏委托关系
    1. 对于每一个委托关系中的函数,在服务对象端建立一个简单的委托函数
    2. 修改为调用服务对象提供的函数 // 185页 - templates
      * 做法:
      a.
      b.
      c.