【读书笔记】重构 改善既有代码的设计

第一版序

  1. 设计模式为重构提供了目标

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

第一章

  1. 重构的第一步:我得确保即将修改的代码拥有一组可靠的测试

  2. 提炼时,考虑哪些变量会离开原来的作用域,对局部变量考虑移除从而使代码提炼更简单

  3. 函数内的临时变量实质上是鼓励你写长而复杂的函数,因此可以考虑用函数赋值给临时变量,或者使用明确在声明的函数

  4. 对于重构过程中的性能问题:多数情况下可以忽略,如果重构引入了性能消耗,先完成重构,再做性能优化

  5. 重构早期的一般步骤:为原函数添加足够的结构,以便更好的理解,看清逻辑结构。拆分为更小单元后才是考虑复用性,考虑复用性时可以拆分为多个文件

  6. 作者偏向于:小的命名良好的函数。但不绝对,好代码的检验标准是:人们是否能轻而易举地修改它

第二章

  1. 重构(名词):对软件内部结构地一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本

  2. 重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构

  3. 重构的关键:运用大量微小且保持软件行为的步骤,一步步达成大规模的修改。因此重构过程中,代码很少进入不可用的状态,即使重构没有完成,也可以时刻停下来

  4. 结构调整(restructuring)泛指对代码库进行的各种形式的重新组织或清理,重构则是特定的一类结构调整

  5. 重构和性能优化又很多相似之处:两者都需要修改代码,并且两者都不会改变代码的整体功能;两者的差别在于:重构是为了让代码"更容易理解,更易于修改"。这可能使程序运行得更快,也可能使程序运行得更慢。在性能优化时,只关心让程序运行得更快,最终得到得代码有可能更难理解和维护,对此要有心理准备

  6. 使用重构技术开发软件时,可以把时间分配为两种行为:添加新功能和重构

  7. 重构的目的:(1):重构改进软件的设计;(2):重构使软件更容易理解;(3):重构帮助找到bug;(4):重构提高编程速度

  8. Kent Beck:“我不是一个特别好的程序员,我只是一个有着一些特别好的习惯的还不错的程序员”

  9. 重构的时机:(1):预备性重构-让添加新功能更容易;(2):帮助理解的重构:使代码更易懂;(3):捡垃圾式重构-每次经过这段代码时都把他变得更好一点;(4):有计划的重构和见机行事的重构:

  10. 编写软件的错误理解:要添加新功能就应该增加新功能。正确的理解:软件永远不应该被视为"完成",每当需要新能力时,软件就应该做出相应的改变

  11. 笑死了的歪招:很多经理和客户不具备这样的技术意识(设计耐久性假说,重构长期看有利于项目发展),他们不理解代码库的健康对生产率的影响,这种情况下我会给团队一个较有争议的建议:不要告诉经理!

  12. 何时不该重构:(1):需要重构的代码被隐藏在一个API下;(2):重写比重构简单

  13. 如果一块代码很少碰触,它也不经常会带来麻烦,那就不倾向于重构它;如果一块代码还没想清楚如何优化,那么可以选择延迟重构

  14. 有人试图用"整洁的代码","良好的工程实践"之类道德理由来论证重构的必要性,我认为这是一个陷阱,重构的意义不在于把代码库打磨的闪闪发光,而是纯粹经济角度出发的考量。我们之所以重构,因为它能让我们更快——添加功能更快、修复bug更快。重构应该总是由经济利益驱动!

  15. 团队内:代码库不要指定唯一的所有者,推荐团队代码所有制。团队之间推荐类似开源的模型:B团队的成员也可以在一个分支上修改A团队的代码,然后把提交发送给A团队去审核

  16. 特性分支:各自分支开发,完成和合并到主分支。基于主干开发(推荐):每天至少向主干集成一次

  17. 绝大多数情况下:如果想要重构,得现有可以自测试得代码

  18. 遗留系统的测试:先找到程序的接缝,在接缝处插入测试

  19. 对数据库重构推荐:分散到多次生产发布来完成,即使某次修改在生产数据库上造成了问题,也比较容易回滚。比如要修改一个字段名,第一次提交时会新添加一个字段但不使用它,然后再修改数据写入逻辑,使其同时写入新旧两个字段。随后就可以修改读取数据的地方,让他们逐个改为使用新字段。修改完成后使用一段时间确认无问题后,删除旧字段。这种方式也叫并行修改,也叫扩展协议

  20. 对于架构,倾向于等一等,待到对问题理解更充分,再来着手解决。即:演进式架构

  21. 三大实践:自测试代码、持续集成、重构

  22. 编写快速软件的方法:(1):时间预算法-分解你的设计时间,给每个组件预先分配一定的资源,包括时间和空间占用;(2):持续关注法-任何程序员在任何时间做任何事时,都要设法保持系统的高性能;(3):性能提升法-编写构造良好的程序,不对性能投以特别的关注,直至进入性能优化阶段(开发后期)。此时,优先用一个度量工具来监控程序的运行,找出哪些地方大量小号时间和空间,然后针对性的优化

第三章

  1. 可能是坏代码:(1):神秘的命名;(2):重复的代码;(3):过长的函数;(4):过长的参数列表;(5):全局数据;(6):可变数据;(7):发散式变化;(8):霰弹式修改;(9):依恋情结;(10):数据泥团;(11):基本类型的偏执;(12):重复的switch;(13):循环语句;(14):冗赘的元素;(15):夸夸其谈的通用性;(16):临时字段;(17):过长的消息链;(18):中间人;(19):内幕交易;(20):过大的类;(21):异曲同工的累;(22):纯数据类;(23):被拒绝的遗赠;(24):注释

  2. 命名是编程中最难的两件事之一。另一件是缓存失效(一致性问题)

  3. 分解函数的原则:每当感觉需要以注释来说明点什么的时候,我们就需要把说明的东西写进一个独立函数中,并以其用途(而非实现手法)命名

  4. 条件表达式和循环也是常常是提炼的信号。此外循环也可以拆分为多个独自的任务

  5. 针对全局数据首要的防御手段是封装变量

  6. 可变数据可以使用移动语句和提炼函数尽量把逻辑从处理更新操作钟的代码钟搬移出来

  7. 发散式变化是指某个模块经常因为不同的原因在不同的方向上发生变化。这种情况常用策略就是拆分阶段

  8. 霰弹式修改指的是如果遇到遇到某种变化,你都必须在许多不同的类内做出许多小修改。这种情况需要搬移函数和搬移字段把所有需要修改的代码放在同一个模块内,常用的策略就是使用内联

  9. 依恋情结指的是一个函数跟另一个模块中的函数或者数据交流格外频繁,远胜于在自己所处模块内部的交流。这种情况常用的策略就是提炼函数

  10. 数据泥团是多个类或者函数有相同的参数,可以用一个类

  11. 可是使用类对象取代基本类型,将原本单独存在的数据值替换为对象

  12. 重复的switch可以使用多态去处理

  13. 循环语句可以使用管道操作。例如 filter或者map

第四章

  1. 类应该包含自己的测试代码

  2. 确保所有测试都完全自动化,让它自己测试结果

  3. 撰写测试代码的最好时机是在开始动手编写代码之前。这种方式叫测试驱动开发(TDD)

  4. 写测试时,不要使用共享测试函数,这会导致新的bug

  5. 考虑可能出错的边界条件,把测试火力集中在那儿

第五章

第六章

  1. 提炼函数(Extract Function)

    1. 反向重构:内联函数
    2. 动机:将意图和实现分开。如果你需要花费时间去浏览一段代码才知道其用途,那就应该将其提炼到一个函数中,并根据其所做的事为其命名。短函数常常能让编译器的优化功能更好运转,所以不必担心函数过短的问题
    3. 做法:略(代码抽取较简单)
  2. 内联函数(Inline Function)

    1. 反向重构:提炼函数
    2. 动机:去除无用的间接层。对Java来说就是无用的继承了
    3. 做法:略
  3. 提炼变量(Extract Variable)

    1. 反向重构:内联变量
    2. 动机:
    3. 做法:方法内局部变量
  4. 内联变量(Inline Varibale)

    1. 反向重构:提炼变量
    2. 动机:
    3. 做法:例如返回值直接返回,不必建造新对象
  5. 改变函数声明(Change Function Declaration)
    1.
    2. 动机:修改参数列表
    3. 做法:可以让旧的公共方法调用新的方法

  6. 封装变量(Encapsulate Variable)
    1.
    2. 动机:代码防腐
    3. 做法;封装

  7. 变量改名(Rename Variable)
    1.
    2. 动机:使用更合适的命名
    3. 做法:略

  8. 引入参数对象(Introduce Parameter Object)
    1.
    2. 动机:减少多个方法使用多个参数
    3. 做法:构造新的对象

  9. 函数组合成类(Combin Function into Class)
    1.
    2. 动机:
    3. 做法:一组函数总是在操作同一块数据,可以变成一个类。不仅减少参数传递,也能提供一个公共环境处理这些数

  10. 函数组合成变换(Combine Functions into Transform)
    1.
    2. 动机:
    3. 做法:与上边类似,但区别在于:因为使用变化,派生数据存在副本中,原数据修改就会导致数据不一致

  11. 拆分阶段(Split Phase)
    1.
    2. 动机:一段代码在处理两件事情的时候,建议拆分成两段
    3. 做法:独立成函数

第七章

  1. 封装:其实就是Java对象的创建,暴露getter/setter/构造器,设置对应访问控制符。目的:隐藏非临时变量,隐藏临时变量,隐藏类内部细节

  2. 以对象取代基本类型:类似于 DDD 的做法,在对象领域内处理一些简单的逻辑

  3. 提炼类:子类的创建

  4. 隐藏委托关系:多对象调用时,确定暴露区间。例子:manager = aPerson.department.manager; => manager = aPersion.manager;class persion{get manager(){return this.department.manager;}}。即如果客户端通过服务对象得到另外一个对象,调用后者函数

第八章

  1. 搬移:改变代码位置,移除死代码

  2. 拆分循环:每个循环有各自的作用;唯一一个从来没这么用过的方法

第九章

  1. 重新组织:如果一个变量被赋值多次说明有多个责任,需要进行拆分

  2. 可变数据:可变数据时软件中最大的错误源头之一。即Java中可变参数列表,个人用重载解决

  3. 值对象和引用对象的选择:引用对象代表共享

第十章

  1. 分解条件表达式:复杂的条件判断单独抽为一个方法,降低圈复杂度。即:if中超长的判断条件

  2. 使用卫语句

  3. 以多态取代条件表达式

  4. 引入为空判断的统一处理逻辑

  5. 引入断言

第十一章

  1. 将查询和修改方法分开

  2. 移除标记参数:根据参数每一种可能新建一个明确函数,例如代码中多参数时的null,true,false,则为三种函数

  3. 以工厂函数取代构造函数。(1):无法根据环境和参数信息返回子类实例或代理对象;(2):构造函数名字是固定的;(3):构造函数需要通过特殊的操作符来调用

第十二章

  1. 继承

  2. 方法,字段,构造器等移动到父类或子类

  3. 提炼超类即抽象类,折叠继承体系即子类和抽象类合并

  4. 以委托取代子类,策略模式

参考

  • 常见重构方法大汇总
  • 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值