重构笔记

1. 什么是重构?

在不改变软件可观察行为的前提下改善其内部结构,提高其可理解性,降低其修改成本

当面对历史遗留问题的时候,规模很大,历史超久,使得增加单元测试或者理解逻辑成为不可能的任务,那么我们有的工具就是那些已经被证明是行为保持的重构手法,整理出可测试的接口,并添加测试,以此作为继续重构的立足点。

1.1 Tips

  1. 以微小的步伐修改程序,以期更好的发现错误。
  2. 代码应该表现自己的目的!!!
  3. 大多数情况下,函数应该放在它所使用的数据的所属对象当中
  4. 尽量去除临时变量,因为其很容易产生问题,导致大量参数被传来传去。 Replace Temp with Query
  5. 注意将变化的东西放到一块去,一块来解决

2. 重构原则

使用重构原则开发软件的时候,实质上是将自己的时间分配给了两种截然不同的行为:添加新功能以及重构。添加新功能的时候就不修改既有代码;重构时不添加新功能,只管改进程序结构。

2.1 为何重构

  • 改进软件设计
    • 整理代码,让代码回到应有的位置上去
    • 减少代码量,消除重复代码
  • 使得软件更容易理解
  • 更容易找到Big
  • 提高编程速度
    • 良好的设计是快速开发的根本

当重复做一件事情三次的时候,请重构。

  • 在添加功能的时候重构
    • 帮助理解需要修改的代码
  • 修补错误时重构
  • 修改接口
    • 当接口不得不被修改的时候
    • deprecate掉旧接口,并且让旧接口Internally调用新接口

3. 代码的坏味道

本章比较大概,但里面又提及了很多细节性的方法,建议先去了解这些方法,再来看这一章!

量度规矩 vs 见识广博者的直觉

3.1 Duplicated Code 重复代码

  • 同一个类的两个函数有相同表达式
    • extract method
  • 两个互为兄弟的子类内的相同表达式
    • extract method to super class
  • 两个不相关的类, duplicate code
    • 对其中一个使用extract class,将重复代码提炼到一个独立类当中

3.2 Long Method (过长函数)

间接层所能带来的全部利益 - 解释能力,共享能力,选择能力都是由小型函数支持的

给函数一个好名字,让代码阅读者可以很迅速地了解到这个函数是做什么的。

每当感觉需要以注释来说明点什么的时候,我们就需要把说明的东西写进一个独立函数中。

3.3 Large Class (过大的类)

如果用单个类做太多事情,往往会出现大量的实例变量,一旦如此,duplicated code也就会接踵而至了。

extract class or extract subclass

3.4 Long Parameter List (过长参数列)

replace parameter with method

introduce paramter object

3.5 Divergent Change (发散式变化)

针对某一外界变化的所有的相应修改都只应该发生在单一类当中,并且新类内的所有内容都应该反应此变化。应找到导致变化的原因,然后运用Extact Class将其提炼到另一个类当中。

3.6 Shotgun Surgery

如果每遇到某种变化,都必须在很多不同的类内做许多小修改,那么就应该使用move method和move field把所有需要的修改都放到一个类里去。努力使外界变化和需要修改的类能够一一对应。

3.7 Feature Envy

3.8 Data Clumps

将经常绑在一起出现的数据放到一起,创建属于他们的对象

3.9 Primitive Obsession

3.10 switch

少用switch,应该考虑用多态来做替换

3.11 Parallel Inheritance Hierarchies (平行继承关系)

3.12 Lazy Class

3.13 Speculative Generality

3.14 Temporary Field

3.15 Message Chains

3.16 Middle Man

3.17 Inappropriate Intimacy

3.18 Alternative Classes with Different Interfaces

3.19 Incomplete Library Class

3.20 Data Class

3.21 Refused Bequest

3.22 Comments

4. 构筑测试体系

对于程序员来说,写代码的时间实际上很少,大部分时间是用在了调试代码上。实际上,每个类都应该有一个测试函数,并以它来测试自己。

写好一点功能就立即去进行测试。事实上,最好能在添加特性之前先写测试代码,使得将注意力集中于接口而非实现。

编写代码时,应该先让代码失败,以此证明测试机制的确可以运行,并且正确测试了其该测试的东西。

观察类该做的所有事情,然后针对任何一项功能的任何一种可能失败情况,进行测试。

5. 重构列表

  • 方法
  • 动机

6. 重新组织函数

重构手法大部分都针对函数,而问题往往源自于Long Methods。因为其包含了太多信息。

6.1 Extract Methods

  • 将一段代码放进一个独立函数当中,并且用函数名称来解释该函数的用途
  • 动机
    • 当函数过长,或者需要注释才能让人理解用途,就放到独立函数当中
    • 函数粒度小意味着更容易进行复用
    • 使得高层函数读起来就像一些列注释
  • 做法
    • 创造一个函数,根据这个函数的意图来对其进行命名,以做什么而不是怎么做来命名

6.2 Inline Method 内联函数

  • 在函数调用点插入函数本体,然后移除该函数
  • 动机
    • 移除没有太大价值的间接层

6.3 Inline Temp 内联临时变量

  • 你有一个临时变量,只被简单表达式赋值一次,而它妨碍了其他重构手法
  • 动机
    • 一般作为Replace Temp with Query的一部分使用

6.4 Replace Temp with Query 以查询取代临时变量

  • 现象
    • 你的程序以一个临时变量保存某一表达式的运算结果
  • 将这个表达式提炼到一个独立函数当中,将这个临时变量的所有引用点替换为对新函数的调用。此后,新函数就可以被其他函数调用了。
  • 动机
    • 临时变量的问题是他们是暂时的,很可能你需要写出更长的函数来访问到需要的临时变量

6.5 Introduce Explaining Variable 引入解释性变量

  • 将该复杂表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途。
  • 动机
    • 表达式有可能非常复杂且难以阅读,临时变量可以帮助你将表达式分解为比较容易管理的形式
    • 这样子我们就可以运用临时变量来解释每一步的意义,在执行一些比较长的判断逻辑的时候很关键
  • 做法
    • 和 extract method 有可替换的潜在部分的
    • 使用introduce explaining variable是在extract method需要花费更大工作量的时候

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 stone2paul@gmail.com

文章标题:重构笔记

文章字数:1.8k

本文作者:Leilei Chen

发布时间:2020-01-29, 22:41:38

最后更新:2020-02-01, 22:06:58

原始链接:https://www.llchen60.com/%E9%87%8D%E6%9E%84%E7%AC%94%E8%AE%B0/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏