《重构:改善既有代码的设计(第2版)》读书笔记


前言

  1. 重构(refactoring):在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构。重构的本质就是在代码写好之后改进它的设计。

第1章 重构,第一个示例

  1. 如果你要给程序添加一个特性,但发现代码因缺乏良好的结构而不易于进行更改,那就先重构那个程序,使其比较容易添加该特性,然后再添加该特性。
  2. 需求的变化使重构变得必要。如果一段代码能正常工作,并且不会再被修改,那么完全可以不去重构它。
  3. 重构的第一步:确保即将被修改的代码拥有一组可靠的测试。这些测试必须有自我检验能力。
  4. 无论每次重构多么简单,养成重构后即运行测试的习惯非常重要。
  5. 对于重构过程的性能问题的处理:大多数情况下可以忽略它。如果重构引入了性能损耗,先完成重构,再做性能优化。
  6. 编程时,需要遵循营地法则:保证你离开时的代码库一定比来时更健康。
  7. 好代码的检验标准就是人们是否能轻而易举地修改它。

第2章 重构的原则

  1. 重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
  2. 重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
  3. 如果有人说他们的代码在重构过程中有一两天时间不可用,基本上可以确定,他们在做的事不是重构。
  4. “结构调整”(restructuring)泛指对代码库进行的各种形式的重新组织或清理,重构则是特定的一类结构调整。
  5. 使用重构技术开发软件时,添加新功能和重构,同一时间,二者只能做其一。
  6. 为何重构:重构改进软件的设计;重构使软件更容易理解;重构帮助找到bug;重构提高编程速度。
  7. 何时重构:预备性重构(添加新功能之前重构);帮助理解的重构(提升代码阅读性);捡垃圾式重构(消除代码冗余等);有计划的重构(固定周期对之前写过的代码重构)和见机行事的重构(写新代码过程中发现其他代码需要重构);长期重构(更新三方库等);代码复审时重构。
  8. 不必将重构与添加新功能在版本控制的提交中分开。原因:重构常因新功能所致,单独提交脱离重构的上下文,体现不出为何需要重构;重构的代码和新功能代码高耦合。
  9. 如果经理不能接受代码重构需要花时间,那就不要给经理说。
  10. 何时不应该重构:凌乱且不需要修改的代码;重写比重构更容易的代码。
  11. 重构的挑战:延缓新功能开发;代码所有权(代码是其他部门写的);分支(多分支开发,最后合并代码);测试(代码测试不完善,无法保证重构后不引入新BUG);遗留代码;数据库(改字段值)。
  12. 重构的唯一目的就是让我们开发更快,用更少的工作量创造更大的价值。
  13. “写代码之前就完成软件的设计和架构”这个思想是不正确的。因为需求会变,且一开始的考虑而不是完全的周全。
  14. 三大实践:自测试代码、持续集成、重构。
  15. 用性能测试工具去定位性能问题,而不是根据对代码的了解来猜测。
  16. 一些语言(Java、C#等)可以借助工具进行自动化的重构。

第3章 代码的坏味道

  1. 当你”闻到”代码中有坏味道时,就表明代码需要进行重构了。
  2. 当你感觉需要撰写注释时,请先尝试重构,试着让所有注释都变得多余。
  3. 神秘命名。
  4. 重复代码。
  5. 过长函数。
  6. 过长参数列表。
  7. 全局数据。(PS:慎用单例模式)
  8. 可变数据(如在js中使用Immutable.js)。
  9. 发散式变化(违背了单一职责原则)。
  10. 霰弹式修改(需求变化是,需要在很多个地方做修改。封装性不够好)。
  11. 依恋情结(违背了依赖倒置原则)。
  12. 数据泥团(很多类中的变量名相同,考虑抽象出基类)。
  13. 基本类型偏执(针对问题域定义新的类型而不是用基本类型,比如定义坐标类,而不是用std::pair)。
  14. 重复的switch。
  15. 循环语句。
  16. 冗赘的元素(有些类结构是不必要的)。
  17. 夸夸其谈通用性。
  18. 临时字段(类内部某个字段仅为某种特定情况而设)。
  19. 过长的消息链。
  20. 中间人(过度使用委托)。
  21. 内幕交易(用委托替代继承等)。
  22. 过大的类。
  23. 异曲同工的类。
  24. 纯数据类(使用面向对象编程而不是面向过程)。
  25. 被拒绝的遗赠(子类复用了超类的实现,却又不愿意支持超类的接口,那就不要用继承)。
  26. 注释(过长的注释)。

第4章 构筑测试体系

  1. 稳固的测试集合是正确地进行重构的前提条件之一。
  2. 确保所有测试都完全自动化,让它们检查自己的测试结果。
  3. 一套测试就是一个强大的bug侦测器,能够大大缩减查找bug所需的时间。
  4. 测试驱动开发的编程方式依赖于下面这个短循环:先编写一个(失败的)测试,编写代码使测试通过,然后进行重构以保证代码整洁。
  5. 总是确保测试不该通过时真的会失败。
  6. 频繁地运行测试。对于你正在处理的代码,与其对应的测试至少每隔几分钟就要运行一次,每天至少运行一次所有的测试。
  7. 编写未臻完善的测试并经常运行,好过对完美测试的无尽等待。
  8. 考虑可能出错的边界条件,把测试火力集中在那儿。
  9. 如果错误会导致脏数据在应用中到处传递,或是产生一些很难调试的失败,考虑引入断言手法,使代码不满足预设条件时快速失败。不用为这样的失败断言添加测试,它们本身就是一种测试的形式。
  10. 不要因为测试无法捕捉所有的bug就不写测试,因为测试的确可以捕捉到大多数bug。
  11. 每当你收到bug报告,请先写一个单元测试来暴露这个bug。

第5章 介绍重构名录

  1. 书的剩余部分是重构方法的索引,作为参看书来使用。

第6章 第一组重构

  1. 提炼函数 vs 内联函数。
  2. 提炼变量 vs 内联变量。
  3. 改变函数声明。
  4. 封装变量。
  5. 变量改名。
  6. 引入参数对象。
  7. 函数组合成类v
  8. 函数组合成变换。
  9. 拆分阶段。

第7章 封装

  1. 封装记录。
  2. 封装集合。
  3. 以对象取代基本类型。
  4. 以查询取代临时变量。
  5. 提炼类 vs 内联类。
  6. 隐藏委托关系 vs 移除中间人。
  7. 替换算法。

第8章 搬移特性

  1. 搬移函数。
  2. 搬移字段。
  3. 搬移语句到函数 vs 搬移语句到调用者。
  4. 移动语句。
  5. 拆分循环。
  6. 以管道取代循环。
  7. 移除死代码。

第9章 重新组织数据

  1. 拆分变量。
  2. 字段改名。
  3. 以查询取代派生变量。
  4. 将引用对象改为值对象 vs 将值对象改为引用对象。

第10章 简化条件逻辑

  1. 分解条件表达式 vs 合并条件表达式。
  2. 以卫语句取代嵌套条件表达式(Replace Nested Conditional with Guard Clauses)。
  3. 以多态取代条件表达式。
  4. 引入特例。
  5. 引入断言。

第11章 重构API

  1. 将查询函数和修改函数分离。
  2. 函数参数化。
  3. 移除标记参数。
  4. 保持对象完整。
  5. 以查询取代参数 vs 以参数取代查询。
  6. 移除设值函数。
  7. 以工厂函数取代构造函数。
  8. 以命令取代函数 vs 以函数取代命令。

第12章 处理继承关系

  1. 函数上移 vs 函数下移。
  2. 字段上移 vs 字段下移。
  3. 构造函数本体上移。
  4. 以子类取代类型码 vs 移除子类。
  5. 提炼超类。
  6. 折叠继承体系。
  7. 以委托取代子类。
  8. 以委托取代超类。

文章作者: Kiba Amor
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 Kiba Amor !
  目录