读书笔记-《重构》

读书笔记-《重构》

March 7, 2017
读书笔记

关键点

  1. 重构是在不改变软件可观察行为的前提下改善其内部结构
  2. 重构前,必须先要给即将修改的代码建立一组可靠的测试环
  3. 重构时要小步前进
  4. 重构(名词):是对软件内部结构的一种调整,目的是不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
  5. 重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。

第一章:分解并重组

  1. 找出函数内的局部变量和参数
  2. 任何不会被修改的变量都可以被当成参数传入新的函数
  3. 只有一个变量被修改则可以把它当做返回值
  4. 每次修改后,都要执行测试
  5. 绝大多数,函数应该放在它所使用的数据的所属对象内
  6. 如果旧函数是public函数,而不想修改其他类的接口,则可以采用保留旧函数的形式
  7. 临时变量可能是个问题
  8. 重构时不必担心系统性能问题,优化时才需要
  9. 最好不要再另一个对象的属性基础上运用switch语句,如果不得不使用,也应该在对象自己的数据上使用。
  10. 如果函数依赖两个对象的数据,尽可能把函数划分给可能变化的数据对象内实现
  11. 一个对象不能在生命周期内修改自己所属的类
  12. State或Strategy模式去除switch…case…和if…else…

第二章:重构原则

  1. 添加新功能时,不应该修改既有代码,只管添加新功能;重构时不能再添加新功能,只管改进程序结构,不应该添加任何测试(除非有遗漏),只有处理接口变化时才修改测试。
  2. 重构是随时随地进行,不应该为重构而重构(比如:划出一段时间专门专门做重构)
  3. 事不过三,三则重构
  4. 不要过早发布接口
  5. 如果大部分代码不能正常运作,那么此时应该是重写而不是重构
  6. 如果项目已近最后期限则应避免重构
  7. 编写快速软件的秘密是:首先写出可调的软件,然后调整它以求获得足够速度
  8. 关于性能,大部分时间耗费再小部分代码上。

第三章:代码的坏味道 — 感觉需要读N遍才行

  1. Duplicated Code(重复代码)
    1. 情况1:同一类的两个函数含有相同的表达式
    2. 情况2:两个互为兄弟的子类内含相同的表达式
    3. 情况3:两个毫不相关的类出现重复,则考虑将一个类的重复代码提炼到独立类中,另一个类使用新类;或函数属于第三类,另两个类引用第三个类。
  2. Long Method(过长方法) 1.函数应以其用途,而非实现手法命名 2.循环和条件表达式尝尝是提炼的信号
  3. Large Class(过大的类)
    1. 选择类内彼此相关的变量,放到同一类中
  4. Long Parameter List(过长参数列)
  5. Divergent Change(发散式变化)?——-
  6. Shotgun Surgery(散弹式修改)?
  7. Feature Envy(依恋情结)?
  8. Data Clumps(数据泥团)?
  9. Primitive Obession(基本类型偏执)?
  10. Switch Statements(switch 惊悚现身)?
  11. Parallel Inheritance Hierarchies(平行继承体系)
  12. Lazy Class(冗赘类)
  13. Speculative Generality(夸夸其谈未来性)
  14. Temporary Field(令人迷惑的暂时字段)
  15. Message Chains(过度耦合的消息链)
  16. Middle Man(中间人)
  17. Inappropriate Intimacy(狎昵关系)
  18. Alternative Classes With Different Interfaces(异曲同工的类)
  19. Incomplete Library Class(不完美的库类)
  20. Data Class(纯稚的数据类)
  21. Refused Bequest(被拒绝的遗赠)
  22. Comments(过多的注释)

第四章–构筑测试体系

  1. 每当收到bug,先写一个单元测试暴露这个bug
  2. 测试最担心出错的部分
  3. 考虑可能出错的边界条件,集中火力
  4. 当事情被认为应该会出错时,应该检查是否抛出了预期的异常

第五章–重构列表

第六章–重新组织函数

前面一页完整的讲解了如何进行重新组织函数

Extract Method(提炼函数)–110

**概念:**你有一段代码可以被组织在一起并独立出来,将代码放进独立函数中,并让函数名称解释改函数的用途 做法:

  1. 以函数意图命名函数(以“做什么”命名,而不是“怎么做”),如果想不错有意义名称,就别重构
  2. 检查移动到目标函数的临时变量,检查原本的生命式是否在提炼代码段的外围,如果是则可以删除这些声明式

复杂情况:

  1. 提炼代码段对局部变量赋值–如果内外都使用则返回该变量
  2. 返回的变量不只一个–尽量只返回一个,否则可以提炼多个方法

Inline Method(内联函数)–117

**概念:**一个函数的本地和名称同样清楚易懂,在函数调用点插入函数本体,然后移除该函数 做法:

  1. 检查多态性,如果子类继承该函数,则不能进行内联
  2. 如果遇到递归调用,多返回点,内联至另一个对象中而该对象无法提供访问函数的等复杂情况,那么不应该使用改重构手法

Inline Temp(内联临时变量)–119

概念:如果一个临时变量,只被一个简单表达式赋值一次,而它妨碍了其他重构手法,将对该变量的引用动作,替换为对它赋值的表达式自身。 **动机:**多半是为Replace Temp With Query(120)的一部分使用 做法:

  1. 检查临时变量赋值语句,确保等号右边表达式没有副作用
  2. 如果临时变量不是final,则改为final(这可以检查这个变量是否被赋值一次)

Replace Temp With Query(以查询取代临时变量)–120

**概念:**一个临时变量保存某一表达式的结果,将这个表达式提炼到一个独立函数中,将这个临时变量所有引用点替换为对函数的调用。新函数可以被其他函数使用。 **动机:**往往是运用Extact Method(110)之前的重构手法 做法:

  1. 临时变量被赋值超过一次,需要使用Split Temporary Variable(128)方式分割成多个变量
  2. 声明为final(保证临时变量被赋值一次)
  3. 将临时变量等号右侧部分提炼到独立函数中;先声明成private需要更多类使用时才放松保护;确保不修改任何对象内容,如果修改了,则需要Separate Query From Modifer(279)
  4. Inline Temp(119)手法

  1. 对于循环中累加好几个值,需要对每个累加值重复一边循环,当然,循环必须很简单,才不会出问题
  2. 每次替换一个临时变量
  3. 声明为final

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

**概念:**你有一个复杂表达式,将该表达式(或一部分)的结果放进一个临时变量,以此变量名称来解释表达式的用途。 **动机:**不常用,一般使用Extract Method(110)来解释一段代码的意义

什么时候使用? 处理一个拥有大量局部变量的算法时

Split Temporary Variable(分解临时变量)–128

**概念:**临时变量被赋值超过一次,它不是循环变量,也不用于收集计算结果,针对每次赋值,创造一个独立、对应的临时变量 **动机:**如果临时变量承担多个责任,就应该被替换为多个临时变量

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

**概念:**代码对一个参数进行赋值,以一个临时变量取代该参数的位置。 **动机:**针对传入一个对象并被改成指向另一个对象的情况

Replace Method with Method–135 Object(以函数对象取代函数)

**概念:**有一个大型函数,对局部变量无法Extract Method(110),将这个函数放进单独对象,局部变量编程对象内字段,然后将大型函数分解成多个小型函数。

Substitute Algorithm(替换算法)–139

**概念:**想要把某个算法替换为另一个更清晰的算法,将函数本体替换为另一个算法。

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

决定把责任放到哪儿?

Move Method (搬移函数)–142

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

Move Field(搬移字段)–146


###关键点

  1. 重构的意义在于:你永远不必说对不起–只要把出问题的地方修补好就行。

第八章–重新组织数据

Self Encapsulate Field(自封装字段)

注意:小心“在构造函数中使用设值函数”的情况,一般设值函数被认为应该再对象创建后才使用。一般要不直接访问字段,要不定义一个初始化函数