《重构》的读书笔记

总览 第1章 重构,第一个案例 第2章 重构原则 第3章 代码的坏味道 第4章 构筑测试体系 第12章 大型重构359 第13章 重构,复用与现实379 第14章 重构工具401

《重构》的读书笔记

学习基础:

熟悉《设计模式》的基本概念,熟悉基本的Java语法,熟悉Eclipse和JUnit的使用,有相对较好的英语基础。

学习过程:

  • 先看第1章,手工输入实例程序,了解重构的方法和过程。重点是理解重构的思路,最好的理解方式就是通过实践的方式理解。
  • 再看第2~4章,内容为选择性阅读,没兴趣或者看不懂的都可以跳过,因为后面还可以回头再读。
  • 接着第5~12章,最好按顺序把代码一个个输入,再按照作者的步骤重构操作一次,并结合自己以往工作中的实践来理解。

学习目的:

使自己编写的代码更容易被人读懂。

学习感悟:

  • 代码的重构应该是一步步完成的,每次重构的部分不要超过自己的理解能力的5%。虽然这样操作略显繁琐,但是可以减轻头脑重构过程中的记忆强度,减少代码出错的机会。
  • 代码的重构一定要配合JUnit(TDD,测试驱动开发)完成,再加上Git(版本管理)和Eclipse(IDE的重构工具)那就事半功倍了。

学习代码:

Refactored-MartinFowler

总览

  • 第1章(必读),从实例程序出发,了解重构的方法和过程。
  • 第2章,讨论重构的一般性原则、定义和进行重构的原因。
  • 第3章,介绍如何判断问题代码,以及如何用重构改善它们。
  • 第4章,在代码中构建java的测试环境
  • 第5~12章,具体面对的问题和重构的方法。
  • 第13章,Bill Opdyke在商业开发中应用重构
  • 第14章,自动化重构工具(今天看来,已经不是太大问题,Eclipse的Refactor已经非常好用)
  • 第15章,重构的哲学思想

第1章 重构,第一个案例

1.1 (P1)起点

因为代码的结构无法满足添加新功能的需要,因此先进行重构,使代码方便添加新功能,然后再添加新功能。

1.2 (P7)重构的第一步

首先确认自己拥有一套可靠的测试机制,因为重构有可能引入问题,通过测试保证重构没有改变程序功能。

第2章 重构原则

2.1 (P53)何谓重构

重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高理解性和降低修改成本。
重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
定义的扩展:

  • 重构让软件更容易理解和修改
  • 重构不会改变软件的可观察行为,即使改变也只能是微小的影响,软件功能一如既往。

重构的目标:只改变程序内部结构,不添加新的功能

不要一心二用:

  • 添加新功能的时候,不重构已有代码。
  • 重构代码时,不增加新功能。

2.2 (P55)为何重构

  • 重构改进软件设计:
    • 程序的设计质量在没有持续重构的情况下逐渐变差,功能的增加或者修改都可能使代码越来越难以理解和维护,就越难保证最初的设计目标
    • 消除重复的代码一方面是程序运行更快,一方面是方便未来的修改,例如:重构减少代码重复,避免功能的改变需要修改多处代码。
  • 重构使软件更容易理解:
    • 及时填补“想要它做什么”和“告诉它做什么”之间的缝隙。重构的核心就是要“准确说出我所要的”
    • 重新阅读代码的人有可能是自己,也可能是他人。
    • 通过重构可以把自己不熟悉的代码的用途梳理一遍,加深对代码的理解
  • 重构帮助找出bug:
    • 这个是建立在代码容易理解之上的
  • 重构提高编程速度:
    • 重构达到良好的设计,而良好的设计更容易修改代码、增加功能、问题调试。

2.3 (P57)何时重构

重构的三次法则:

  • 第一次开发某个功能的时候以实现为目标。
  • 第二次开发相同功能的时候,克制自己的反感,继续重复实现。
  • 第三次开发相同功能的时候,应该重构。

重构的时间点:

  • 添加功能时重构:
    • 一方面可以帮助理解需要修改的代码
    • 一方面是使现在以及未来增加新功能更加容易。
  • 修补错误时重构:
    • 出现bug的时候,难以找出问题所在的时候,很有可能是代码不清晰导致查找bug的困难。
  • 复审代码时重构:
    • 复审代码有助于知识的传播,有利于代码被编写者之外的人理解。
    • 重构加深了对代码的理解,有利于提升复审代码的能力

复审团队:只要代码作者和一个审查者者。较大的项目可以通过UML图去展示代码的逻辑。

程序难以修改的原因:

  • 难以阅读的程序
  • 逻辑重复的程序
  • 添加新特性需要修改已有代码的程序
  • 带复杂逻辑判断的程序

重构的目标:

  • 代码容易阅读
  • 所有逻辑都只有唯一地点指定
  • 新的改动不会危及现有行为
  • 尽可能简单表达逻辑

2.4 (P60)怎么对经理说

  • 懂技术的经理,很容易沟通;
  • 追求质量的经理,介绍重构对质量的帮助;
  • 追求进度的经理,则自己安静地重构。因为重构可以最快的完成任务,就是对经理最大的帮助。
间接访问

很多时候重构都为程序引入间接访问:

  • 把大型对象拆分成小对象
  • 把大型函数拆分为小型函数。

间接访问的价值:

  • 允许逻辑共享:一个函数在不同地点被调用。子类共享超类的方法。
  • 分开解释意图和实现:通过类名和函数名解释自己的意图
  • 隔离变化:在不同地方使用同一个对象,需要修改一处逻辑,那么可以做出子类,并在需要的时候修改这个子类。
  • 封装条件逻辑:运用多态。将条件逻辑转化为消息模式。

减少间接层的条件:当间接层只在一处使用,那么需要将其消除。

2.5 (P62)重构的难题

数据库重构:

  • 存在问题:
    • 程序与数据库耦合在一起。
    • 数据迁移。
  • 解决方案:
    • 在非关系型数据库,可以在数据库和对象模型中插入一个分离层,隔离两者之间的变化

接口重构

  • 对于已经发布的接口需要可能需要维护旧接口和新接口,用deprecated修饰旧接口。
  • 不发布新接口,在旧接口中调用新接口。
  • 假如新接口抛出编译时异常,那么可以在旧接口中调用新接口并将编译时异常转化为运行时异常。

不重构的条件:

  • 重构之前,代码在大部分情况下都能够正常运行,就可以重构,否则应该是重写。
  • 到了Deadline,应该避免重构。

2.6 (P66)重构与设计

重构与设计是彼此互补的:

  • 设计应该在编码之前,但是设计总有缺陷,随着对问题认识的逐渐深入,通过重构可以改善设计的质量。
  • 重构减轻了设计的难度和压力,在程序不断修改的过程中逐步完善程序的设计。

2.7 (P69)重构与性能

重构是有可能导致程序运行变慢的,但是不需要在设计和编码时就考虑性能问题。例如:实时程序的编写:

  • 首先写出可调的程序
  • 然后调整它以达到性能的要求。
    • 经过分析大部分程序的主要时间是消耗在小部分代码上,所以不用对所有代码进行优化。
    • 性能优化放在开发的后期,利用分析工具找出消耗大量时间的代码,然后集中优化。

第3章 代码的坏味道

3.1 (P76)Duplicated Code(重复代码)

  • 同个类两个函数存在相同表达式:Extract Method(提炼函数)
  • 互为兄弟类内存在相同表达式:
    • Extract Method→PullUp Method(函数上移)
    • 如果代码只是相似:先运用Extract Method(提炼函数)分开再Form TemPlate Method(塑造模板函数)
  • 两个毫不相干的类存在重复代码:Extract Class(提炼类)

3.2 (P76)Long Method(过长函数)

原则:尽量利用函数名称来解释用途,而不是注释。
关键:代码主要用来描述“做什么”,而不是描述“怎么做”。例如:getAge()表达获取年龄,而today-birthday就增加了理解的间接性,虽然看代码的人也能明白含义,但是就需要多想一下,并且birthday有可能表达的不是某个人的出生日期呢,而是某个买回来的产品的呢?那可能表达的就是使用时长了。
具体情况:

  • 函数有大量参数和临时变量:Extract Method(提炼函数)
  • 用Replace Temp with Query(以查询取代临时变量)消除临时变量
  • 用Introduce Parameter Object(引入参数对象)或者Preserve Whole Object(保持对象完整)来将多长的参数列表变得简洁一点。
  • 如果按照上述步骤还存在太多变量和参数就需要用到Replace Method with Method Object(以函数对象取代函数)
  • 条件表达式可以用Decompose Conditional(分解条件表达式)解决
  • 可以将循环内的代码提炼为函数。

3.3 (P78)Large Class(过大的类)

有时候类并非在所有时刻都使用实例变量:使用Extract Method和Extract Subclass(提炼子类)

类中有太多代码:

  • Extract Class(提炼类)
  • Extract Subclass(提炼子类)
  • Extract Interface(提供接口)分解类的行为。存在GUI的时候,可以Duplicate Observed Data(复制“被监视数据”),分离数据和行为到业务模型中去。

3.4 (P78)Long Parameter List(过长参数列)

  • 如果可以调用已有对象获取的话可以使用Replace Parameter with Methods(以函数取代参数)
  • 将来自同一对象的数据收集起来,以该对象替代:Preserve Whole Object(保持对象完整)
  • 如果几个参数总是同时出现,那么可以考虑Introduce Parameter Object(引入参数对象)

3.5 (P79)Divergent Change(发散式变化)

不同的变化影响着相同的类发生改变,即变化的认知有分歧(Divergent)。通过Extract Class把不同的功能封装到不同的类中,使每个类只因一种变化而需要修改

3.6 (P80)Shotgun Surgery(霰弹式修改)

相同的变化会涉及到多个类发生修改,类似霰弹枪射击的效果。
可以通过Extract Method,Move Method,Inline Class把一种变化产生的多个修改移到同一个类中。

对比:

  • Divergent Change(发散式变化)是一个类受到的多个变化影响;
  • Shotgun Surgery(霰弹式修改)是一个变化引起多个类需要修改。

3.7 (P80)Feature Envy(依恋情结)

类中的某个函数对其他类的依赖度过高,则应该通过Move Method(移动函数)将它搬移到合适的类中。

3.8 (P81)Data Clumps(数据泥团)

数据项总是成群结队出现,通过Extract Class将它们提炼到一个独立对象中,从而缩短参数列表,简化函数调用。

判断数据项是否相关的方法:如果这些数据项不在一起时就失去了明确的含义,那么就可以把它们提炼成一个新的对象。

3.9 (P81)Primitive Obsession(基本类型偏执)

  • 有些字段可以用对象表示更准确Replace Data Value with Object(以对象取代数据值)
  • 对于不影响行为的类型码可以Replace Type Code with Class(以类取代类型码)
  • 影响行为的类型码可以Replace Type Code with Subclasses(以子类取代类型码),类型码在运行时会变化就用Replace Type Code with State/Strategy(以State/Strategy取代类型码)

3.10 (P82)Switch Statements(switch惊悚现身)

  • 使用Replace Type Code with Subclasses(以子类取代类型码)或者Replace Type Code with State/Strategy(以State/Strategy取代类型码)
  • 轻量级的解决方法:Replace Parameter with Explicit Methods(以明确函数取代参数)

3.11 (P83)Parallel Inheritance Hierarchies(平行继承体系)

每当为一个类增加子类必须也为另外一个类增加一个子类,那么就让一个继承体系的实例引用另一个继承体系的实例。

3.12 (P83)Lazy Class(冗赘类)

没用的类,使用Inline Class(内联类)或者Collapse Hierarchy(折叠继承体系)来解决

3.13 (P83)Speculative Generality(夸夸其谈未来性)

  • 为未来设计的类,使用Inline Class(内联类)或者Collapse Hierarchy(折叠继承体系)来解决
  • 为未来设计的函数参数,使用Remove Parameter(移除参数)
  • 函数名称啰嗦,使用Rename Method(函数改名)

3.14 (P84)Temporary Field(令人迷惑的暂时字段)

对象中某个字段仅为特定情况而设。使用Extract Class(提炼类)将这个字段提取出来

3.15 (P84)Message Chains(过度耦合的消息链)

消息链:用户通过一个对象获取另一个对象,再通过获取的对象请求另一个对象,如此操作就是消息链。采取这种方式意味着客户代码将与查找过程中的导航结构紧密耦合,可以使用Hide Delegate(隐藏“委托关系”)进行重构。但是谨慎处理!

3.16 (P85)Middle Man(中间人)

过度委托形成中间人:Remove Middle Man(移除中间人)

如果中间人还有其他行为,Replace Delegation with Inherited(以继承取代委托)

3.17 (P85)Inappropriate Intimacy(狎昵关系)

  • 两个类相互依赖过多,花费大量时间去获取对方的private成员内容,使用Move Field(移动字段)和Move Method(移动方法)减少耦合性,或用Change Bidirectional Association to Unidirectional(将双向关联改为单向关联)
  • 如果两个类无法移动相同数据和函数,可以使用Extract Class(提炼类),让他们使用新类进行交互。

3.18 (P85)Alternative Classes with Different Interfaces(异曲同工的类)

两个函数做了相同的事情却有不同的函数名称

3.19 (P86)Incomplete Library Class(不完美的库类)

库函数功能不足,需要增加一些自定义的功能:

  • 需要加入少量操作,使用Introduce Foreign Method(引入外加函数)
  • 需要加入大量操作,使用Introduce Local Extension(引入本地扩展)

3.20 (P86)Data Class(幼稚的数据类)

幼稚的数据类:只有数据没有行为的类,其他类需要对该类的数据进行取值设值操作

  • 使用Encapsulate Field(封装字段)和Encapsulate Collection(封装集合)对字段进行合理地封装
  • 对于不该被其他类修改的字段:Remove Setting Method(移除设值函数)

3.21 (P87)Refused Bequest(被拒绝的遗赠)

如果子类不愿意接受超类的所有定义,应该使用Replace inherited with Delegation(以委托取代继承)来处理子类

3.22 Comments(过多的注释)87

使用Extract Method(提炼方法)来解决注释过多问题,注释更多应该说明的是“怎么做”,而不是“做什么”,例如:对一个排序函数说明其采用二分法排序,而不是说明它是个排序函数,因为这个说明在函数名称中已经具备。

第4章 构筑测试体系

4.1 自测试代码的价值89

  • 确保所有测试都完全自动化,让它们检查自己的测试结果;
  • 一套测试就是一个强大的bug探测器,能够大大缩减查找bug所需要花费的时间。
    • 因为代码刚刚写完,测试出现问题后,心里很清楚自己修改或者添加了哪些东西,可能会在哪里出现了问题。

      4.2 JUnit测试框架91

  • 频繁地运行测试;
  • 每次编译前都进行一次测试;
  • 每天至少执行一次所有的测试。

    4.3 添加更多测试97

  • 编写一个测试并运行起来,好过将所有的测试编好了一起运行。
  • 测试特别需要注意可能出错的边界条件;
  • 对于可能出错的地方,还需要检查是否抛出了预期的异常;
  • 测试不能解决所有bug,但是可以大大减少bug的数量。

第12章 大型重构359

大型重构是程序开发必将遇到的,只是不知道在什么时间,用什么样的方式经历。例如:随着时间的推移,河道必定会被水草和垃圾所堵塞,你可以固定时间清淤,也可以放任自流直到崩溃。崩溃后依然会面临总结经验教训,再次重构系统。
大型重构很难给出具体的操作案例,因为每个大型案例相对于自身来说都是惟一的,是无法复制和重现的。可以复制与重现的都是这些大型重构中蕴含的具体的细节,因此这章主要讲的是思想和理念上的内容。
四个大型重构:

  • Tease Apart Inheritance(362)用于处理混乱的继承体系
    • 某个继承体系同时承担两项责任
    • 建立两个继承体系,其中一个通过委托调用另一个
  • Convert Procedural Design to Objects(368)如何重构过时的编码技术遗留下来的程序
    • 传统过程化风格的代码
    • 将数据记录变成对象,将大块的行为分成小块,再将它们移入到相关对象中
  • Separate Domain from Presentation(370)将业务逻辑与用户界面分隔开来
    • 用户界面类中包含了业务逻辑
    • 将业务逻辑剥离到业务类中,参考:MVC模式
  • Extract Hierarchy(375)将复杂的类转化为一群简单的子类,从而简化系统。
    • 某个类做了太多工作
    • 某个类的部分工作是由大量的条件表达式完成的
    • 建立继承体系,使用子类表示每一种特殊情况

第13章 重构,复用与现实379

作为一个博士写的内容,仍然具有学术性较强的风格,可以当作历史资料了解一下重构的发展过程,也可以对重构的思想有更多理论上的认识。

安全重构(391)

安全重构的四条篱笆:

  • 相信你自己的编码能力;
  • 相信你的编译器能捕捉你遗漏的错误;
  • 相信你的测试套件能捕捉你和编译器都遗漏的错误;
  • 相信代码复审能捕捉你、编译器和测试套件都遗漏的错误。注:没有100%安全的重构,但是可以通过以上的条件满足你对安全性的最低要求。

重构工具

  • Eclipse(或其他IDE)自带的重构工具:Refactor;
  • Java(或其他编译器)自带的分析工具:lint;
  • JUnit等自动化的测试工具。

第14章 重构工具401

相对于10多年前写的内容,现在许多IDE都已经提供了对大部分重构功能的支持。但是了解重构的基本理念,对于正确地使用重构工具会有很大的帮助。因为成功的重构不依赖于工具,而决定于人,当人做出了正确的决定,合理地使用重构工具辅助自己,才能保证重构的完成。

转载于:https://www.cnblogs.com/zhuyx/p/10278392.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值