# 《重构-改善既有代码的设计第二版》
# 导读
这是我看重构2积累下来的心得体会,并不适合所有人,我也只记录常用的重构手法与思想。
这本书写的挺好的,但是建议有以下基础的人阅读
- 看过设计模式并实际运用过(涉及到设计模式)。
- 在编码时经常写测试(涉及到自测试)。
- 至少有1年经验(能看懂js的api与机制)。
- 写过OOP相关的项目(涉及到class等)。
# 第一章 重构,第一个示例
- 需求来的时候,一般是先设计后编码,但是总不会有十全十美的设计,所以重构就可以来解决这些问题。
- 在需要重构时,一般是把功能先写完整,测试也不能少,最后在进行重构。
- 大多数情况下重构并不会减少代码量,反而会增加代码量,但是会换取较高的可读性、可维护性等。
- 重构时可以不用先考虑性能问题,例如大量计算、循环等,如果遇到性能问题的时候,那么重构完成以后在进行性能的调优。
- 重构可以先从核心代码进行,减少作用域内的临时变量。
# 第二章 重构的原则
- 重构不应该很大,而是应该小。
- 重构的代码大多数行为会不变,但是也会出现极少数情况。
- 两顶帽子: 新功能与重构。
- 消除重复代码,确定所有事物和行为在代码中只表述一次,这正是优秀设计的根本。
- 重构可以更快速地开发程序。
- 通过投入精力改善内部设计,增加了软件的耐久性,从而可以更长时间地保持开发的快速。
- 事不过三,三则重构。
- 长函数拆分成短函数,分离逻辑,如果能保证单一性就更好了。
- 重构应该跟随功能进行,有可能会花很长时间,但是又有紧急任务需要完成,那么能花费一点时间来清理,通常是值得了。
- 肮脏的代码必须重构,但漂亮的代码也需要很多重构,因为需求是有变化的。
- 每次要修改时,首先令修改很容易(警告:这件事有时会很难),然后再进行这次容易的修改。
- 发现一堆凌乱的代码,如果不需要它的话就不用重构,只有当需要理解工作原理时,重构才会有价值。
- 自测试代码是极限编程的一个重要组成部分。
- 自测试代码、持续集成、重构有很强的协同能力,这是敏捷开发的基础。
- 除了对性能有严格要求的实时系统,其他任何情况下“编写快速软件”的秘密就是:先写出可调优的软件,然后调优它以求获得足够的速度。
- 哪怕你完全了解系统,也请实际度量它的性能,不要臆测。臆测会让你学到一些东西,但十有八九你是错的。
# 第三章 代码的坏味道
- 对于庞大的switch语句,其中的每个分支都应该通过提炼变成独立的函数调用。如果有多个switch语句基于同一个条件 进行分支选择,就应该使用以多态取代条件表达式。
- 循环:你应该将循环和循环内的代码提炼到一个独立的函数中。如果你发现提炼出的循环很难命名,可能是因为其中做了几件不同的事。请勇敢地使用拆分,将其拆分成各自独立的任务。
- 编程方式:函数式编程——完全建立在 数据永不改变 的概念基 础上:如果要更新一个数据结构,就返回一份新的数据副本,旧的数据仍保持不变。
- 发散式变化:某个模块经常因为不同的原因在不同的方向上发生变化,这就是需要拆分的提示。
- 霰弹式修改:每遇到某种变化,你都必须在许多不同的类内做出许多小修改,这就是需要拆分的提示。
- 如果在继承时,不需要用超类的接口而且又不实现,那么这种继承关系可能并不需要。
- 当你感觉需要撰写注释时,请先尝试重构,试着让所有注释都变得多余,如果不知道该怎么做,那么这才是使用注释的良好时机。
# 第四章 构筑测试体系
- 确保所有测试都完全自动化,让它们检查自己的测试结果。
- 一套测试就是一个强大的bug侦测器,能够大大缩减查找bug所需的时间。
- TDD(测试驱动开发):撰写测试代码的最好时机是在开始动手编码之前。需要添加特性时,先编写相应的测试代码。编写测试代码其实就是在问自己:为了添加这个功能,我需要实现些什么?编写测试代码还能把注意力集中于接口而非实现(这永远是一件好事)。预先写好的测试代码也为工作安上一个明确的结束标志:一旦测试代码正常运行,工作就可以结束了。
# 第五章 介绍重构名录
- 名称
- 速写
- 动机
- 做法
- 范例
# 第六章 第一组重构
- 浏览一段代码,理解其作用,然后将其提炼到一个独立的函数中,并以这段代码的用途为这个函数命名。
- 应该始终遵循性能优化的一般指导方针,不用过早担心性能问题。
- 小函数得有个好名字才行,所以必须在命名上花心思。
- 改进函数名字的好办法:先写一句注释描述这个函数的用途,再把这句注释变成函数的名字。
- 修改函数参数列表不仅能增加函数的应用范围,还能改变连接一个模块所需的条件,从而去除不必要的耦合。
# 第七章 封装
- 尝试把动态数据的声明组合成类进行使用。
- 保证提取出来的类的单一性。
- 如果类的职责出现了不明确的行为,那么请尝试再次提取,保证类的单一性。
- 避免封装的源数据直接返回,因为如果客户端更改的话会影响其他代码,如果不是源数据不是很多的话,建议返回源数据的副本。
- 如果临时变量是有意义的话,可以单独提取成方法。
# 第八章 搬移特性
- 如果修改一条记录时, 总是需要同时改动另一条记录,那么说明很可能有字段放错了位置。
- 如果 我更新一个字段时,需要同时在多个结构中做出修改,那也是一个征兆,表明该 字段需要被搬移到一个集中的地点,这样每次只需修改一处地方。
- 正常情况下让存在关联的东西一起出现,可以使代码更容易理解。
- 可以尝试把循环代码改为管道代码(链式调用api)。
# 第九章 重新组织数据
- 保证变量在同一个上下文中的单一性,即每个变量只承担一个责任。
- 将引用对象改为"值"对象: 在进行类属性赋值时候 可以把可变对象重构为不可变对象,保证数据永远都是新数据。
- 将值对象改为引用对象: 如果一组列表中 列表项的属性是重复的(类似于订单列表中的 下单人ID), 那么可以单独提取出来成为一个对象来引用,要不然每次去修改的时候会去遍历修改,而且一个没有修改到就会导致数据不一致的BUG。
# 第十章 简化条件逻辑
- 使用卫语句取代嵌套条件表达式。
- 以多态取代条件表达式。
- 使用断言assert, 不要滥用断言。断言不应该用来检查所有“我认为应该为真”的条件,应该只用来检查“必须为真”的条件。
# 第十一章 重构API
- 明确表现出“有副作用”与“无副作用”两种函数之间的差异,是个很好的想法。
- 如果遇到一个既有返回值又有副作用的函数,试着将查询动作从修改动作中分离出来。
- 尽量移除标记参数(直接传入字面量值来影响函数内部控制流), 可多写一些函数来调用。
- 透明性:不论任何时候,只要传入相同的参数值,该函数的行为永远一致。
- 可以使用命令模式来代替某些参数很多的函数。
# 第十二章 处理继承关系
- 如果子类的属性或方法有相同的,可以提升到超类。
- 如果超类的一些函数对子类并不适用,就说明不应该通过继承来获得超类的功能。