在做微服务拆分相关的课题,会涉及到模块化重构和代码重构相关的东西,重构的速度和质量很大程度上取决于代码原本质量的好坏,所以看了一下这本书,有些知识点我也不知道,所以摘抄了其中比较重要的部分规则。
文章目录
重构——改善既有代码的设计
【美】 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章 总结
大多数时候,“得道”的标志是:你可以自信地停止重构
和别人一起重构,可以收到更好的效果
两顶帽子:开发与重构,无论什么时候,只能戴一顶