编码

《重构》读书笔记

2018-06-13  本文已影响0人  VictorBXv

chapter 1 重构,第一个案例

1.1 什么时候需要重构

需要为程序添加一个特性,但代码结构无法使自己方便的那么做,就先重构那个程序,使得特性添加比较容易进行,然后在添加特性。

1.2 重构的第一步

为即将修改的代码建立一组可靠的测试环境,即有一套可靠的测试机制;

1.3 分解并重组statement()

  1. 重构准则1:提炼一个函数时,必须知道可能出什么错。
  2. 首先找出局部变量参数,任何不会被修改的变量都可以当成参数传入新的函数。如果只有一个变量会被修改,就可以将其当成返回值。
  3. 重构必须以微小的步伐修改程序,如果犯下错误,就很容易发现他。
  4. 临时变量去掉,减少参数传递的过程,当然这可能会出现性能下降的问题。

1.4 利用多态取代与价格相关的条件逻辑

  1. 使用switch语句时,应该在自己的对象的数据身上使用,而不是在别的对象的数据身上使用。

chapter 2 重构原则

2.1 何谓重构

  1. 重构(名词):对软件内部结构的一种调整,目的是在不改变[软件之可察行为]前提下,提高其可理解性,降低修改成本。
  2. 重构(动词):使用一系列重构准则(手法),在不改变[软件之可观察行为]前提下,调整其结构
  3. 使用重构技术开发软件时,只要做两件事情
    1. 添加新功能:此时不应该修改既有代码,只管添加新功能。通过测试(并让测试正常运行)可以衡量自己的工作进度。
    2. 重构:此时不能再添加新功能,只管改进程序结构。此时不应该添加任何测试(除非发现先前遗漏的任何东西),只在绝对必要(用以处理接口变化)时才修改测试。

2.2 为何重构(重构的好处和目的)

  1. 改进软件设计,去掉重复代码;
  2. 使程序易于理解;
  3. 帮助找bug(理解了程序的行为,自然更容易发现程序中的bug)
  4. 提高编程速度。

2.3 何时重构

重构应该随时随地进行,重构是为了做别的事情,而重构可以帮助自己把那件事情做的更好。

  1. 添加功能时一并重构
  2. 修补错误时一并重构
  3. 复审代码时一并重构

2.4 怎么对经理说

不告诉经理关于重构的任何事情。

2.5 重构的难题

重构可能引入的问题,以及局限

  1. 数据库

    面向对象的数据库设计在数据迁移的时候会出现隐患

  2. 修改接口

    • 修改接口时,需要特别谨慎,因为接口被修改了,任何事情都有可能发生。即使找到也不能修改的接口才会成为问题。
    • 解决办法:让旧接口继续工作,让旧接口调用新接口。让旧函数调用新函数,千万不要复制实现代码,防止重复代码
  3. 难以通过重构手法完成的设计改动

  4. 何时不该重构

2.6 重构与设计

重构与设计互补。有个重构,在设计的时候可以为了减少所谓的灵活性而增加无畏的复杂。使得软件设计更加简洁。

2.7 重构与性能

重构是为了更好的性能优化。

2.8 重构的起源


chapter 3 代码的坏味道

重构时机的选择

3.1 重复的代码

  1. 同一个类中两个函数有相同的表达式
    • 抽取方法,将重复代码提取到一个方法里面。
  2. 两个互为兄弟的子类中含有相同的表达式
    1. 代码完全相同
      • 抽取方法,提取到父类中
    2. 代码只是相似,并非完成相同
      • 将相似部分和差异部分割开,单独构成一个函数,这里可以使用模板方法设计模式。
    3. 有些函数用不同的算法做相同的事
      • 选择较清晰的方法,将其他函数的算法替换掉。
  3. 两个互不相关类中出现了重复代码
    • 对其中一个类抽取新的类,将重复代码提炼到一个独立的class中,然后在另一个类中使用这个新的class。

3.2 过长函数

小函数的价值:间接层所能带来的全部好处都是小型函数支持的。

间接层的好处:解释能力、共享能力、选择能力

  1. 每当感觉需要用注释来说明的时候,我们就需要把说明的东西写进一个独立的函数,用函数的用途来命名而非实现方法。
  2. 条件和循环也是提炼的信号。

3.3 过大的类

  1. 一个class中做了太多的事情,此时就会出现太多实例变量,这时就需要抽取子类或者父类,将过多的实例变量分解到不同的类中。
  2. 抽取新的类、父类、子类的技巧:先确定客户端如何使用它们然后用抽取接口的方式为每一种使用方式提炼出一个接口。

3.4 过长参数列

  1. 少使用全局变量,多使用局部变量。
  2. 通过传递对象来缩短参数列表,如果某些数据缺乏合理的对象归属,就为他们制造一个参数对象。

3.5 发散式变化

3.6 霰弹式修改

即一个变化引起多个class的修改。

3.7 依恋情节

对象技术的要点:将数据和加诸其上的操作行为包装在一起。

3.8 数据泥团

两个class内的相同值域(field)或许多函数签名式中的相同参数。

这些总是绑在一起出现的数据应该放进属于他们自己的对象中。

3.9 基本型别偏执

可以将基本类型的数据封装成对象。

3.10 switch惊悚现身

少用switch-case语句,用多态来代替。

3.11 平行继承体系

  1. 当自己为一个类增加一个子类的时候,就必须为另一个class增加一个子类;
  2. 某个继承体系的class名称前缀和另一个继承体系的class名称前缀完全一样。

3.12 冗余类

如果一个class所得不值其身价,则这个类就应该消失,或者用内部类来代替。

3.13 夸夸其谈未来性

过多考虑未来的特性,使得代码冗余且难于理解,此时就需要将其去掉。

  1. 如果某个abstract class没有太大作用,就去掉这个层级。
  2. 没有必要的委托,可以用内部类来代替。
  3. 函数的某些参数未被使用,就应该去掉这些参数。
  4. 函数的名称带有多余的抽象意味,就应该对方法重命名。

3.14 令人迷惑的暂时值域

3.15 过度耦合的消息链(调用链)

3.16 中间转手人

封装往往伴随委托,就有可能产生过度委托的情况;

3.17 狎昵关系

3.18 异曲同工的类

两个函数做同一件事,函数声明却不一样,此时可以采用移动方法抽取父类来解决。

3.19 不完美的程序类库

需要在类库的基础上在进行封装。

3.20 纯稚的数据类

数据类:他们拥有一些成员变量,以及操作这些成员变量的函数,除此之外,没有其他用处。

3.21 被拒绝的遗赠

子类应该继承父类的数据,访问父类的函数,但如果不想或者不需要继承,就意味着继承体系设计错误。这时需要为这个子类建立一个兄弟siblingclass,在把所有用不到的函数下推给那个兄弟,如此父类就只持有所有的子类共享东西。

3.22 过多的注释

当感觉要写注释时,先尝试重构,试着让所有的注释都变得多余。

如果不知道要做什么,这才是写注释的时机。可以把自己将来要做的事情写下来,以及自己无十足把握的地方。


chapter 4 构建测试体系

4.1 自我测试代码的价值

  1. 程序员时间消耗:
    1. 编写代码;
    2. 决定下一步做什么;
    3. 设计;
    4. 调试解bug;(时间花费最多的地方)

    正是因为没有好的测试框架,使得调试花的时间最多,因此有一个好的测试框架特别重要。

  2. 每个class中都应该有一个测试函数。

    要频繁的进行测试,确保所有测试都完成自动化,让它们检查自己的测试结果,通过测试来确定bug出现的阶段和位置。

4.2 JUnit 测试框架

可以一次运行多个测试,使用TestSuite来完成,实际开发中,单元测试就够了。

单元测试和功能测试

  1. 单元测试
    • JUnit是用来做单元测试的。我们所说的测试就是单元测试。
    • 单元测试的思想:每个test class只对单一的package运行,可以测试其他packages的接口,除此之外假设其他package一切正常。
    • 重构时使用的就是单元测试
  2. 功能测试
    • 功能测试保证软件能够正常运行。当功能测试测出问题后,还是需要用单元测试来定位bug;

4.3 添加更多测试

编写不完美的测试并实际运行,好多对完美测试的无尽等待。

即使只做一点点测试,也会让自己从中受益。

测试不可能捕获所有bug,但却可以捕获大部分bug,这会节省不少时间。


chapter 5 重构名录

5.1 重构的记录格式

5.2 寻找引用点

即函数、对象、变量被调用的地方,通过开发工具、编译器来完成即可。

5.3 这些重构准则有多成熟

重构的基本技巧:小步前进,频繁测试


chapter 6 重新组织你的函数

6.1 Extract Method(抽取函数)

函数过长,一个函数里面做了太多事情,导致逻辑特别复杂。

6.2 Inline Method (内联方法)###

在不影响代码的逻辑清晰度和理解的情况下,多个函数能合并成一个函数时就应该合并,把不必要的间接层去掉。

6.3 Inline Temp(内联临时变量)

一个临时变量,只被一个简单的表达式赋值过一次,同时影响了其他重构手法。

6.4 replace Temp with Query(以查询取代临时变量)###

用一个临时变量(temp)保存一个表达式的运算结果。

6.5 Introduce Explaining Variable(引入解释性变量)

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

6.6 Split Temporary Varible (分解临时变量)

某个临时变量被赋值超过一次,他既不是循环变量,也不是一个集用临时变量

6.7 Remove Assignments to Parameters (移除对参数的赋值)

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

6.9 substitute Algotithm (替换算法)

将函数本体替换成另一个算法。


chapter 7 在对象之间搬移特性

7.1 Move Method (搬移函数)

7.2 Move Field(搬移字段)

迁移函数

7.3 Extract Class (抽取类)

7.4 Inline class (将类内联化)

7.5 Hide delegate(隐藏“委托关系”)

7.6 Remove Middle Man(移除中间人)

7.7 Introduce Foreign Method(引入外加函数)

对源码根据自己的需要进行在封装,自己就新封装一个函数,传入已有的对象。

7.8 Introduce Local Extention(引入本地扩展)

转型构造函数:接受原对象作为参数的构造函数

1.采用子类化方案:转型构造函数应该调用适当的超类构造函数

2.采用包装类方案:转型构造函数应该将他得到的传入参数以实例变量的形式保存起来,用作接受委托的原对象。


chapter 8 重新组织数据

8.1 Self Encapsulate Field(自封装字段)

即常说的把字段值的修饰符改为private修饰,提供访问他们的方法

8.2 Replace Data value with Object (以对象取代数值项)###

8.3 Change Value to Reference(将值对象改为引用对象)

8.4 Change Reference to Value(将引用对象改为值对象)

8.5 Replace Array with Object(以对象取代数组)

8.6 Duplicate Observed Data(复制“被监视数据”)

8.7 change UniDirectional Association to Bidirectional (将单向关联改为双向关联)

8.8 Change Bidirectional Assocition to Unidirectional (将双向关联改为单向关联)

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

魔法数字:拥有特殊含义,却又不能明确表现这种意义的数字。

8.10 Encapsulate Field(自封装字段)

8.11 Encapsulate Collection(封装集合) 【donnot understand】###

8.12 replace record with Data class(以数据类取代记录)###

8.13 replace Type code with class(以类取代类型码)###

8.14 replace Type code with subclass (以子类取代类型码)

8.15 replace type code with state/strategy(以state/strategy取代类型码)

8.16 Replace Subclass with Fields(以字段取代子类)


chapter 9 简化条件表达式

9.1 Decopose Conditional (分解条件表达式)

9.2 Consolidate Conditional Expression(合并条件表达式)

9.3 Consolidate Duplicate Conditional Fragments(合并重复的条件片段)

9.4 Remove Control Flag(移除控制标记)

9.5 Replace Nested Conditonal with Guard Clauses(以卫语句取代嵌套条件表达式)

9.6 Replace Conditional with Polymorphism (用多态取代条件表达式)###

9.7 Introduce Null Object(引入null对象)

9.8 Introduce Assertion(引入断言)


chapter 10 简化函数调用

10.1 Rename Method(函数改名)

10.2 Add parameter(添加参数)

10.3 Remove Parameter(移除参数)

10.4 Separate Query from Modefier(将查询函数和修改函数分离)

10.5 Parameterize Method (令函数携带参数)

10.6 Replace Parameter with Explicit Methods(以明确函数取代参数)【此项重构和上一条相反】

10.7 Preserve Whole Object(保持对象完整)

10.8 Replace Parameter with Method(以函数取代参数)

10.9 Introduce Parameter Object(引入对象参数)

10.10 Remove Setting Method(移除设值函数)

10.11 Hide Method (隐藏函数)

10.12 replace Constructor with Factory Method(以工厂方法取代构造函数)

10.13 Encapsulate Downcast(封装向下转型)

10.14 Replace Error Code with Exception (以异常取代错误码)

10.15 Replace Exception with Test(以测试(条件表达式)取代异常)


chapter 11 处理概括关系(继承关系)

11.1 Pull Up Field(字段上移)

11.2 Pull Up Method (函数上移)

11.3 Pull Up Constrcutor Body (构造函数本体上移)

11.4 Push Down Method (函数下移)

11.5 Push Down Field (字段下移)

11.6 Extract Subclass(提炼子类)

11.7 提炼超类

11.8 Extract Interface(提炼接口)

11.9 Collapse Hierarchy(折叠继承关系)

11.10 Form Template Method (塑造模板函数)

11.11 Replace Inheritance with Delegation(以委托取代继承)

11.12 Replace Delegation with Inheritance(以继承取代委托)##


chapter 12 大型重构

12.1 Tease Apart Inheritance(梳理并分解继承体系)

12.2 Convert Procedural Design to Objects(将过程设计)

12.3 Separate Domain from Presentation(将领域和表述/显示分离)

12.4 Extract Hierarchy(提炼继承体系)


chapter 13 重构、复用与实现


chapter 14 重构工具


chapter 15 集成

上一篇 下一篇

猜你喜欢

热点阅读