[重构] 改善既有代码的设计

在做微服务拆分相关的课题,会涉及到模块化重构和代码重构相关的东西,重构的速度和质量很大程度上取决于代码原本质量的好坏,所以看了一下这本书,有些知识点我也不知道,所以摘抄了其中比较重要的部分规则。

重构——改善既有代码的设计

【美】 Martin Fowler

—— Erich Gamma
《设计模式》第一作者,Eclipse平台主架构师

  • “重构”这个概念来自Smalltalk圈子
  • 重构具有风险

前言

  • 重构(refactoring)是这样一个过程:在不改变代码外在行为的前提下,对代码做出修改该,以改进程序的内部结构。

第1章 重构,第一个案例

  • 一个影片出租店的程序,有三个类,租客、影片、租约,影片共有不同类型,要根据租约时间计算费用,还要计算积分

  • Customer类中计算费用和积分的statement函数做到事情太多,不符合面向对象的精神;当需要改变打印方式时不能服用,只能复制一份再修改,当再需要计算规则时,要保证两边的修改一致

如果你发现自己需要为程序添加一个特性,而代码结构使你无法很方便地达成目的,那就先重构那个程序,是特性的添加比较容易进行,然后再添加特性。

  • 重构第一步:建立可靠的测试环境

    • 要让测试有能力自我检验,否则需要耗费大把时间来回比对,会降低开发速度

    • 好的测试是重构的根本

  • 重构步骤的本质:由于每次修改该的幅度都很小,所以任何错误都很容易发现。不必花费大把时间调试。

重构技术就是以微小的步伐修改程序。如果你犯下错误,很容易便可发现它

  • 为了提高代码的清晰度,大胆地修改变量名称,使代码清楚表达出自己的功能。语言所提供的强类型检查以及自己的测试机制会指出任何遗漏的东西

  • 引入State模式、充分利用继承多态、职责划分要明确

第2章 重构原则

  • 重构不会改变软件可观察行为

  • 为何重构:

    • 改进软件设计
    • 使软件更容易理解
    • 帮助找到bug
    • 提高编程速度
  • 事不过三,三则重构

  • 重构的时机:

    • 添加新功能
    • 修补错误
    • 复审代码
  • 不要告诉经理重构的事:)

  • Kent Beck:间接层的引入允许逻辑共享、分开解释意图和实现、隔离变化、封装条件逻辑

  • 重构的难题:

    • 数据库:可以再对象moxi8ng和数据库模型之间插入一个分隔层,隔离两个模型各自的变化,但会损失性能
    • 修改接口:如果重构手法改变了已发布接口,必须同时维护新旧两个接口;如果要新增异常类型,可以先定义一个异常基类,并确保所有的public函数只在自己的throws子句中声明这个异常
  • 何时不该重构:

    • 既有代码太混乱,不如重写
    • 项目接近最后期限

第3章 代码的坏味道

  • Duplicated Code
  • Long Method
  • Large Class
  • Long Parameter List
  • Divergent Change(发散式变化):一个类职责过多
  • Shotgun Surgery(散弹式修改):一次修改需要在很多不同的类中进行
  • Feature Envy(依恋情结):函数对某个类的兴趣高于对自己所处类的兴趣
  • Data Clumps(数据泥团):数据太多,单独提取到一个类
  • Primitive Obsession(基本类型偏执)
  • Switch Statement:少用switch语句
  • Parallel Inheritance Hierarchies(平行继承体系):当你为某个类增加一个子类,必须也为另一个类相应增加一个子类
  • Lazy Class(冗余类)
  • Speculative Generality(夸夸其谈未来性)
  • Temporary Field:某个实例变量仅为某种特定情况而设——使用Extract Class为这个变量和相关代码造一个新家
  • Message Chains
  • Middle Man:过度委托,类接口中有一半的函数都委托给其他类
  • Inappropriate Intimacy(狎昵关系):子类对超类的了解超过后者的主观愿望
  • Alternative Classes with Different Interfaces:两个函数做同一件事,却有着不同的签名
  • Incomplete Library Class
  • Data Class
  • Refused Bequest(被拒绝的馈赠):子类复用了超类的行为(实现),却不愿意支持超类的借口
  • Comments(过多的注释):当感觉需要撰写注释时,

第4章 构筑测试体系

  • 单元测试是为了提高程序员的生产率,是高度局部化的东西;功能测试是用来保证软件能够正常运行的、从客户的角度保障质量,并不关心程序员的生产力。

第5章 重构列表

  • 编译器无法找到通过反射机制得到的引用点

第6章 重新组织函数

  • Extract Method:

    • 函数名很重要
    • 困难点在于局部变量
  • Inline Method

  • Inline Temp: 某个临时变量被赋予某个函数调用的返回值,阻碍了其他重构手法,应该将其内联化

  • Replace Temp with Query: 将临时变量的值的计算提炼到一个独立函数,然后用到该变量的地方日澳用这个函数代替

  • Introduce Explaining Variable: 以变量名解释表达式用途

  • Split Temporary Variable: 临时变量被赋值超过一次,既不用于循环,也不用于收集结果,就针对每次赋值,创造一个新的变量

  • Remove Assignments to Parameters: 移除对参数的赋值,可以将参数标为final,避免对参数赋值

  • Replace Method with Method Object: 将函数放进单独的对象,局部变量会变成对象内字段,就可以在对象中将大型函数分解

  • Substitute Algorithm: 将函数本体直接替换为另一个算法

第7章 在对象之间搬移特性

主要是为了明确职责

  • Move Method

  • Move Field: 如果很多函数都使用了该字段,先运用Self Encapsulat Field

  • Extract Class: 是改善并发程序的一种常用技术,因为可以给提炼后的两个类分别加锁

  • Inline Class

  • Hide Delegate: 在服务类上建立客户所需的所有函数,用以隐藏委托关系。

  • Remove Middle Man: 与Hide Delegate相反

  • Introduce Foreign Method: 当需要为提供服务的累增加一个函数,但你无法修改该这个类时——在客户类中建立一个函数,并以第一参数形式传入一个服务类实例

  • Introduce Local Extension: 当需要为提供服务的累增加一些额外函数,但你无法修改该这个类时——建立一个新类,使它包含这些额外函数,让这个扩展品成为源类的子类或包装类

第8章 重新组织数据

  • Self Encapsulate Field: 使用getter/setter访问字段,子类可以通过覆写getter改变获取数据的途径,还支持延迟初始化

  • Replace Data Value with Object

  • Change Value to Reference: 从一个类衍生出许多相等的实例,希望将它们替换为同一个对象:使用工厂方法代替构造函数,缓存产生的对象

  • Change Reference to Value: 有一个引用对象,很小且不可变,且不易管理——将它变成一个值对象;建立equals和hashCode方法

  • Replace Array with Object:

String[] row = new String[2];
row[0] = "Liverpool";
row[1] = "15";
====>>>
Performance row = new Performance();
row.setName("Liverpool");
row.setWins("15");
  • Duplicate Observed Data: 有一些领域数据置身于GUI控件中,而领域函数需要访问这些数据——将该数据复制到一个领域对象中,建立一个Obeserver模式,用以同步领域对象和GUI对象内的重复数据

  • Change Unidirectional Assciation to Bidirectional(将单向关联改为双向关联): User和Order之间的关系

  • Change Bidirectional Assciation to Unidirectional(将双向关联改为单向关联): 避免僵尸对象

  • Replace Magic Number with Symbolic Constant(以字面常量取代魔法数)

  • Encapsulate Field: 将公有字段改成私有,加setter/getter方法

  • Encapsulate Collection: 只返回集合的只读副本,在这个类中提供添加/移除集合元素的函数

  • Replace Record with Data Class

  • Replace Type Code with Class

  • Replace Type Code with Subclasses(以子类取代类型码)

  • Replace Type Code with State/Strategy

  • Replace Subclass with Field(以字段取代子类)

第9章 简化条件表达式

  • Decompose Conditional (从if/then/else三个段落分别提炼出独立函数)

  • Consolidate Conditional Expression: 如果有好几个条件测试的操作都相同,那么条件可合并成一个独立函数

  • Consolidate Duplicate Conditional Fragments: 在每个条件表达式的分支上都有相同的一段代码->经这段代码移到条件表达式之外

  • Remove Control Flag: 减少flag,多用return/break

  • Replace Nested Conditional with Guard Clauses: 如果某个个条件极其罕见,就应该单独检查该条件,并在该条件为真时立即返回,这样的单独检查被称为“卫语句”

  • Replace Conditional with Polymorphism

  • Introduce Null Object: Null Object -> Special Case

  • Introduce Assertion

第10章 简化函数调用

  • Rename Method

  • Add Parameter

  • Remove Parameter

  • Separate Query from Modifier: 某个函数既返回对象状态值,又修改该对象状态->建立两个不同的函数,其中一个负责查询,另一个负责修改

  • Parameterize Method: 多个函数做了类似工作,只是函数本体中包含了不同的值->建立单一函数,以参数表达那些不同的值

  • Replace Parameter with Explicit Methods: 函数完全取决于参数值而采取不同的行为->针对该参数的每一个可能值,建立一个独立函数

  • Preserve Whole Object: 传递整个对象代替传递这个对象的若干属性

  • Replace Parameter with Methods: 如果函数可以通过其他途径获得参数值,就不应该通过参数获得该值,把参数的计算放到被调用的函数中

  • Introduce Parameter Object: 某些参数总是很自然地同时出现->以一个对象取代这些参数

  • Remove Setting Method: 某个字段对象创建时设置,之后不再改变

  • Hide Method: 从没被其他类用到的将函数修改为private

  • Replace Constructor with Factory Method: 希望在创建对象时不仅是做简单的建构动作

  • Encapsulate Downcast:

Object lastReading(){
    return reading.lastElement();
}
=====>>>>>
Reading lastReading(){
    return (Reading)reading.lastElement();
}
  • Replace Error Code with Exception: 返回一个特定的代码,用以表示某种错误情况->改动异常

  • Replace Exception with Test: 修改调用者,在调用函数之前先做检查

第11章 处理概括关系

  • Pull Up Filed: 两个子类有相同的字段

  • Pull Up Method: 有些函数在各个子类中产生完全相同的结果

  • Pull Up Constructor Body:
    子类不能继承超类的构造函数

  • Push Down Method

  • Push Down Field

  • Extract Subclass: 类中的某些特性只被某些实例用到

  • Extract Superclass: 两个类有相似特性

  • Extract Interface: 若干客户使用类接口中的同一子集,或者两个类的接口有部分相同

  • Collapse Hierarchy: 超类和子类没有太大区别->将它们合为一体

  • Form Template Method: 子类某些函数以相同顺序执行,丹各个操作的细节不同

  • Replace Inheritance with Delegation: 子类只是用超类接口的一部分,或者不需要继承而来的数据

  • Replace Delegation with Inheritance

第12章 大型重构

  • Tease Apart Inheritance(梳理并分解继承体系): 某个继承体系同时承担两项责任 -> 建立两个继承体系,并通过委托关系让其中一个可以调用另一个

  • Convert Procedural Design to Objects(将过程设计转化为对象设计): 传统过程化风格的代码 -> 将数据记录变成对象,将大块的行为分成小块,并将行为移入相关对象之中

  • Separate Domain from Presentation(将领域和表述/显示分离): 某些GUI类之中包含了领域逻辑 -> 将领域逻辑分离出来,为它们建立独立的领域类

SQL语句获取的数据一定是领域数据

最大风险:展示逻辑和领域逻辑混淆

  • Extract Hierarchy: 某个类做了太多工作,其中一部分工作是以大量条件表达式完成的 -> 建立继承体系,以一个子类表示一种特殊情况

第13章 重构,复用与现实

  • 为什么开发者不愿意重构程序
    • 如何重构,在哪里重构
    • 重构以求短期利益
    • 降低重构带来的开销
    • 安全地进行重构:程序属性(包括作用域和类型等)在重构之后仍然保持不变

第14章 重构工具

由于弥补设计错误所需的成本降低了,需要预先做的设计也就更少了

  • 重构工具的技术标准

    • 程序数据库:执行一次搜索动作,可以找到任何程序元素的交叉引用
    • 解析树
    • 准确性
  • 重构工具的实用标准

    • 速度
    • 撤销
    • 与其他工具集成

第15章 总结

大多数时候,“得道”的标志是:你可以自信地停止重构

和别人一起重构,可以收到更好的效果

两顶帽子:开发与重构,无论什么时候,只能戴一顶

第1章 重构,第一个案例 1 1.1 起点 1 1.2 重构的第一步 7 1.3 分解并重组statement() 8 1.4 运用多态取代与价格相关的条件逻辑 34 1.5 结语 52 第2章 重构原则 53 2.1 何谓重构 53 2.2 为何重构 55 2.3 何时重构 57 2.4 怎么对经理说 60 2.5 重构的难题 62 2.6 重构设计 66 2.7 重构与性能 69 2.8 重构起源何处 71 第3章 代码的坏味道 75 3.1 Duplicated Code(重复代码) 76 3.2 Long Method(过长函数) 76 3.3 Large Class(过大的类) 78 3.4 Long Parameter List(过长参数列) 78 3.5 Divergent Change(发散式变化) 79 3.6 Shotgun Surgery(霰弹式修改) 80 3.7 Feature Envy(依恋情结) 80 3.8 Data Clumps(数据泥团) 81 3.9 Primitive Obsession(基本类型偏执) 81 3.10 Switch Statements(switch惊悚现身) 82 3.11 Parallel InheritanceHierarchies(平行继承体系) 83 3.12 Lazy Class(冗赘类) 83 3.13 Speculative Generality(夸夸其谈未来性) 83 3.14 Temporary Field(令人迷惑的暂时字段) 84 3.15 Message Chains(过度耦合的消息链) 84 3.16 Middle Man(中间人) 85 3.17 Inappropriate Intimacy(狎昵关系) 85 3.18 Alternative Classes with Different Interfaces(异曲同工的类) 85 3.19 Incomplete Library Class(不完美的库类) 86 3.20 Data Class(纯稚的数据类) 86 3.21 Refused Bequest(被拒绝的遗赠) 87 3.22 Comments(过多的注释) 87 第4章 构筑测试体系 89 4.1 自测试代码的价值 89 4.2 JUnit测试框架 91 4.3 添加更多测试 97 第5章 重构列表 103 5.1 重构的记录格式 103 5.2 寻找引用点 105 5.3 这些重构手法有多成熟 106 第6章 重新组织函数 109 6.1 Extract Method(提炼函数) 110 6.2 Inline Method(内联函数) 117 6.3 Inline Temp(内联临时变量) 119 6.4 Replace Temp with Query(以查询取代临时变量) 120 6.5 Introduce Explaining Variable(引入解释性变量) 124 6.6 Split Temporary Variable(分解临时变量) 128 6.7 Remove Assignments to Parameters(移除对参数的赋值) 131 6.8 Replace Method with Method Object(以函数对象取代函数) 135 6.9 Substitute Algorithm(替换算法) 139 第7章 在对象之间搬移特性 141 7.1 Move Method(搬移函数) 142 7.2 Move Field(搬移字段) 146 7.3 Extract Class(提炼类) 149 7.4 Inline Class(将类内联化) 154 7.5 Hide Delegate(隐藏“委托关系”) 157 7.6 Remove Middle Man(移除中间人) 160 7.7 Introduce Foreign Method(引入外加函数) 162 7.8 Introduce Local Extension(引入本地扩展) 164 第8章 重新组织数据 169 8.1 Self Encapsulate Field(自封装字段) 171 8.2 Replace Data Value with Object(以对象取代数据值) 175 8.3 Change Value to Reference(将值对象改为引用对象) 179 8.4 Change Reference to Value(将引用对象改为值对象) 183 8.5 Replace Array with Object(以对象取代数组) 186 8.6 Duplicate Observed Data(复制“被监视数据”) 189 8.7 Change Unidirectional Association to Bidirectional(将单向关联改为双向关联) 197 8.8 Change Bidirectional Association to Unidirectional(将双向关联改为单向关联) 200 8.9 Replace Magic Number with Symbolic Constant(以字面常量取代魔法数) 204 8.10 Encapsulate Field(封装字段) 206 8.11 Encapsulate Collection(封装集合) 208 8.12 Replace Record with Data Class(以数据类取代记录) 217 8.13 Replace Type Code with Class(以类取代类型码) 218 8.14 Replace Type Code with Subclasses(以子类取代类型码) 223 8.15 Replace Type Code with State/Strategy(以State/Strategy取代类型码) 227 8.16 Replace Subclass with Fields(以字段取代子类) 232 第9章 简化条件表达式 237 9.1 Decompose Conditional(分解条件表达式) 238 9.2 Consolidate Conditional Expression(合并条件表达式) 240 9.3 Consolidate Duplicate Conditional Fragments(合并重复的条件片段) 243 9.4 Remove Control Flag(移除控制标记) 245 9.5 Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件表达式) 250 9.6 Replace Conditional with Polymorphism(以多态取代条件表达式) 255 9.7 Introduce Null Object(引入Null对象) 260 9.8 Introduce Assertion(引入断言) 267 第10章 简化函数调用 271 10.1 Rename Method(函数改名) 273 10.2 Add Parameter(添加参数) 275 10.3 Remove Parameter(移除参数) 277 10.4 Separate Query from Modifier(将查询函数和修改函数分离) 279 10.5 Parameterize Method(令函数携带参数) 283 10.6 Replace Parameter with Explicit Methods(以明确函数取代参数) 285 10.7 Preserve Whole Object(保持对象完整) 288 10.8 Replace Parameter with Methods(以函数取代参数) 292 10.9 Introduce Parameter Object(引入参数对象) 295 10.10 Remove Setting Method(移除设值函数) 300 10.11 Hide Method(隐藏函数) 303 10.12 Replace Constructor with Factory Method(以工厂函数取代构造函数) 304 10.13 Encapsulate Downcast(封装向下转型) 308 10.14 Replace Error Code with Exception(以异常取代错误码) 310 10.15 Replace Exception with Test(以测试取代异常) 315 第11章 处理概括关系 319 11.1 Pull Up Field(字段上移) 320 11.2 Pull Up Method(函数上移) 322 11.3 Pull Up Constructor Body(构造函数本体上移) 325 11.4 Push Down Method(函数下移) 328 11.5 Push Down Field(字段下移) 329 11.6 Extract Subclass(提炼子类) 330 11.7 Extract Superclass(提炼超类) 336 11.8 Extract Interface(提炼接口) 341 11.9 Collapse Hierarchy(折叠继承体系) 344 11.10 Form Tem Plate Method(塑造模板函数) 345 11.11 Replace Inheritance with Delegation(以委托取代继承) 352 11.12 Replace Delegation with Inheritance(以继承取代委托) 355 第12章 大型重构 359 12.1 Tease Apart Inheritance(梳理并分解继承体系) 362 12.2 Convert Procedural Design to Objects(将过程化设计转化为对象设计) 368 12.3 Separate Domain from Presentation(将领域和表述/显示分离) 370 12.4 Extract Hierarchy(提炼继承体系) 375 第13章 重构,复用与现实 379 13.1 现实的检验 380 13.2 为什么开发者不愿意重构他们的程序 381 13.3 再论现实的检验 394 13.4 重构的资源和参考资料 394 13.5 从重构联想到软件复用和技术传播 395 13.6 小结 397 13.7 参考文献 397 第14章 重构工具 401 14.1 使用工具进行重构 401 14.2 重构工具的技术标准 403 14.3 重构工具的实用标准 405 14.4 小结 407 第15章 总结 409
一直很喜欢重构这本书,但是由于自己记性不太好,书看过之后其中的方法总是记不住,于是想如果有电子版的重构书就好了,工作中遇到重构的问题可以随时打开查阅。在网上搜索了许久,发现重构这本书有英文chm版本的,而中文版的电子书只有扫描的PDF版本,用起来非常不方便。于是萌生想做一本重构工具书的想法,本来打算自己重新将重构书的内容再整理归类一下,后来发现原书的目录编排就很适合做工具书,包括坏味道分类,重构手法归类等,都有了一个比较系统的整理。因此,我利用空余时间制作了这样的一本中文的chm版重构,希望对大家有所帮助,也算对中国软件业做出一点小小的贡献。 本书基本上是取自”重构”中文版一书的内容,但格式上参照的是chm英文版的格式,还有一些格式小修改,比如第一章的重构前后代码对比。因为时间匆促,个人能力有限,本书难免存在一些缺漏,如果大家发现有问题,随时可以给我发邮件,我会尽快更新错误的内容。 最后再次感谢几位大师 Martin Fowler、Kent Beck等,还有翻译的侯捷和熊节先生,为我们带来这么精彩的一本书。谢谢。 免责声明:本书仅供个人学习研究之用,不得用于任何商业目的,不得以任何方式修改本作品,基于此产生的法律责任本人不承担任何连带责任。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值