重构—改善既有代码的设计
2021-07-26 08:26:29 1 举报
AI智能生成
重构
作者其他创作
大纲/内容
重构原则
What 重构
在不改变软件可观察行为的前提下,调整其结构
Why 重构
重构改进软件设计
重构使软件更容易理解
重构帮助找到bug
重构提高编程速度
良好设计是维持软件开发速度的根本
When 重构
三次法则
“事不过三,三则重构”
添加功能时重构
修补错误时重构
复审代码时重构
重构的难题
数据库
1. 应用程序与数据库紧密耦合
2. 数据迁移
修改接口
不要过早发布接口
throws 特殊场景
为package定义一个异常基类,所有public 函数的throws 声明该异常。随心所欲地用异常子类
难以通过重构手法完成的设计改动
先想象重构的情况,选最简单的设计
何时不该重构
重构之前,代码必须起码能够在大部分情况下正常运作
项目已经临近最后期限,应该避免重构
重构与设计
重构改变了预先设计的角色
保证预先做出的设计正确无误,这个压力太大了
重构与性能
编写快速软件的秘诀
先写出可调试的软件,然后调整它以求获得足够速度
编写快速软件三种方法
时间预算法
用于性能要求极高的实时系统
持续关注法
感觉很有吸引力,但通常不会起太大作用
关注10%核心代码优化
先实现功能,再调优瓶颈
重构起源何处
代码坏味道
Duplicated Code(重复代码)
同一个类的两个函数含有相同的表达式
Extract Method
两个互为兄弟的子类含有相同表达式
代码完全一样
先子类Extract Method,再Pull Up Method
代码有差异
先子类Extract Method,再Form Template Method
两个毫不相关的类
Extract Class,将重复代码提炼到一个独立类中
Long Method(过长函数)
小函数的价值
解释能力、共享能力、选择能力
共识
程序愈长愈难理解
让小函数容易理解的真正关键——一个好名字
积极地分解函数
每当感觉需要以注释来说明点什么的时候,就把需要说明的东西写进一个独立函数中,以其用途命名
“做什么”VS“如何做”
重构方案
90%场景下,Extract Method
大量参数和临时变量
Replace Temp with Query 消除临时元素
Introduce Parameter Object 和 Preserve Whole Object 将过长的参数列表变简洁
杀手锏:Replace Method with Method Object
条件表达式和循环
Decompose Conditional 处理条件表达式
将循环和其内的代码提炼到一个独立函数中
Large Class(过大的类)
多次使用Extract Class 或Extract Subclass
小技巧
先确定客户端如何使用它们,然后运用Extract Interface 为每个函数提炼出一个接口
Long Parameter List(过长参数列)
Replace Parameter with Method:向已有的对象发出一条请求取代一个参数
Preserve Whole Object:将来自同一对象的一堆数据收集起来,并以该对象替换
Introduce Parameter Object:某些数据缺乏合理的对象归属,引入一个“参数对象”
Divergent Change(发散式变化)
某个类经常因为不同的原因在不同的方向上发生变化
eg.新加入一个数据库,必须修改这三个函数...
Extract Class:每个类应对一类变化
Shotgun Surgery(散弹式修改)
遇到某种变化,必须在许多不同的类中做许多小修改
Move Method 和 Move Field把所有需要修改的代码放进同一个类
没有合适的类,运用Inline Class:把一系列相关行为放进同一个类
PK
Divergent Change(发散式变化)
一个类受多种变化的影响
Shotgun Surgery(散弹式修改)
一种变化引发多个类相应修改
Feature Envy(依恋情结)
对象技术的要点:将数据和对数据的操作行为包装在一起
坏味道:函数对某个类的兴趣高过对自己所处类的兴趣
Move Method:把函数移至它该去的地方
如果只是函数中一部分,先Extract Method,再Move Method
Data Clumps(数据泥团)
两个类中相同的字段、多个函数签名中相同的参数。这些总是一起出现的数据应该拥有属于它们自己的额对象
Extract Class:将相同字段提炼到一个独立对象中
Introduce Parameter Object 或 Preserve Whole Object 缩短参数列表
评判办法
删掉众多数据中的一项,如果它们不再有意义——应该为它们产生一个新对象
Primitive Obsession(基本类型偏执)
对象技术的新手通常不愿意在小任务上运用小对象
结合数值和币种的 Money类
由一个起始值和一个结束值组成的 Range类
电话号码或邮政编码等等特殊字符串
Replace Data Value with Object:将数据值替换为对象
Replace Type Code with Class:将类型码替换为类
Replace Type Code with Subclass 或 Replace Type Code with State/Strategy:如果有类型码相关的条件表达式
如果在参数列表中看到基本类型,Introduce Parameter Object
如果有从数组中挑选数据,Replace Array with Object
Switch Statements(Switch 语句)
switch 语句的问题在于重复,switch 语句散布于不同地点。如果添加一个case 语句,就必须找到所有switch 语句并修改
一看到switch 语句,就应该考虑以多态来替换它。问题是多态该出现在哪儿?
Extract Method 将switch 语句提炼到一个独立函数中,再Move Method 将它搬移到需要多态性的那个类里
决定是否使用 Replace Type Code with Subclasses 或 Replace Type Code with State/Strategy
完成继承结构后,用Replace Conditional with Polymorphism
Parallel InheritanceHierarchies(平行继承体系)
每当为某个类增加一个SubClass,必须也为另一个类相应增加一个SubClass
让一个继承体系的实例引用另一个继承体系的实例。
再接再厉运用Move Method 和 Move Field,将引用端的继承体系消弭于无形
Lazy Class(冗赘类)
某个类原本对得起自己的身价,但重构使他缩水,请让这个类庄严赴义吧
某些子类没有做足够的工作,Collapse Hierarchy
几乎没用的组件,Inline Class
Speculative Generality(夸夸其谈未来性)
我想我们总有一天需要做这事
某个抽象类没有太大作用,Collapse Hierarchy
不必要的委托,Inline Class 解除
多余的参数,Remove Parameter
函数名带有多余的抽象意味,Rename Method
类或函数仅测试用例使用,连测试用例一起删除
Temporary Field(临时字段)
对象内某个实例变量仅为某种特定情况而设
让人迷惑不解,通常认为对象在任何时候都需要它的所有变量
Extract Class 将这个实例变量和相关代码抽离单独类
Introduce Null Object,在“变量不合法”的情况下创建Null 对象
类中有一个复杂算法,这些字段只在使用该算法时才有效
Message Chains(过度耦合的消息链)
用户向一个对象请求另一个对象,然后再向后者请求另一个对象,然后再请求另一个对象...
Hide Delegate,可以重构消息链上的任何一个对象,但这会将一系列对象变成Middle Man
消息链最终得到什么对象,Extract Method 把使用该对象的代码提炼到一个独立函数
再Move Method 把这个函数推入消息链
Middle Man(中间人)
对象的基本特征之一就是封装(对外部隐藏其内部细节),封装往往伴随委托
过度运用委托,某个类接口有一半的函数都委托给其他类
Remove Middle Man,直接和真正的对象打交道
如果“不干实事”的函数只有少数,InlineMethod 把它们放进调用端
Replace Delegation with Inheritance 把Middle Man 变成实责对象的子类
Inappropriate Intimacy(狎昵关系)
两个类过于亲密,花费太多时间去探究彼此的private成分
采用Move Method 和Move Field 帮它们划清界限
运用Chang Bidirectional Association to Unidirectional,让一个类对另一个斩断情丝
Extract Class 把两者共同点提炼到一个安全地点,共同依赖这个新类
Hide Delegate 让另一个类来为它们传递相思情
Alternative Classes with Different Interfaces(异曲同工的类)
Incomplete Library Class(不完美的库类)
库类构筑者没有未卜先知的能力
如果只修改类的一两个函数,Introduce Foreign Method
如果要添加一大堆额外的行为,Introduce Local Extension
Data Class(数据类)
拥有一些字段,以及用于访问(读写)这些字段的函数,除此之外没有其他功能
如果含有容器类字段,是否有恰当的封装,Encapsulate Collection 将其封装起来
对于那些不该被其他类修改的字段,Remove Setting Method
找出获取/设置函数被其他类调用的地点,Move Method 把那些调用行为搬移到Data Class
Refused Bequest(被拒绝的遗赠)
子类应该继承超类的函数和数据,但如果它们不需要继承,怎么办?
传统方案
新建一个兄弟类,Push Down Method 和 Push Down Field 到兄弟类
超类只持有所有子类共享的东西
建议
Replace Inheritance with Delegation
Comments(过多的注释)
如果你需要注释来解释一块代码做了什么,试试Extract Method
如果函数已经提炼出来,但还是需要注释来解释其行为,试试Rename Method
如果需要注释说明某些系统的需求规格,试试Introduce Assertion
重新组织函数
Extract Method(提炼函数)
过长的函数或需要注释才能理解其用途的代码
Inline Method(内联函数)
一个函数的本体与名称同样清楚易懂
Inline Temp(内联临时变量)
一个临时变量,只被一个简单表达式赋值一次,而它妨碍了其他重构手法
Replace Temp with Query(以查询取代临时变量)
一个临时变量保存某一个表达式的运算结果
Introduce Explaining Variable(引入解释性变量)
难于理解的复杂表达式
Split Temporary Variable(分解临时变量)
某个变量被赋值超过一次,它既不是循环变量,也不被用于汇总结果
Remove Assignments to Parameters(移除对参数的赋值)
对入参进行赋值
Replace Method with Method Object(以函数对象取代函数)
在一个大型函数中,由于局部变量的使用导致无法采用Extract Method
Substitute Algorithm(替换算法)
把某个算法替换成另一个更清晰的算法
在对象之间搬移特性
Move Method(搬移函数)
某个函数与另一个类进行更多交流
Move Field(搬移字段)
某个字段被另一个类更多地用到
Extract Class(提炼类)
某个类做了应该由两个类做的事
新建一个类,将相关的字段和函数搬移到新类
Inline Class(内联类)
某个类没有做太多事情
将这个类的所有特性搬移到另一个类中,删除原类
Hide Delegate(隐藏“委托关系”)
通过一个委托类来调用另一个对象
Remove Middle Man(移除中间人)
某个类做了过多的简单委托动作
Introduce Foreign Method(引入外加函数)
需要为提供服务的类增加一个函数,但你无法修改这个类
Introduce Local Extension(引入本地扩展)
需要为提供服务的类增加一个函数,但你无法修改这个类
扩展一个子类
重新组织数据
Self Encapsulate Field(自封装字段)
直接访问一个字段,但与字段之间的耦合关系逐渐变得笨拙
间接访问变量的好处:子类可以通过覆盖而改变取数的途径,还支持更灵活的数据管理方式(如,延迟初始化)
直接访问变量的好处:代码比较容易阅读
Replace Data Value with Object(以对象取代数据值)
有一个数据项,需要与其他数据和行为一起使用才有意义
Change Value to Reference(将值对象改为引用对象)
从一个类衍生出许多彼此相等的实例,希望将他们替换为同一个对象
Change Reference to Value(将引用对象改为值对象)
有一个引用对象,很小且不可变,而且不易管理
Replace Array with Object(以对象取代数组)
有一个数组,其中的元素各自代表不同的东西
Duplicate Observed Data(复制“被监视数据”)
一些领域数据置身于GUI控件中,而领域函数需要访问这些数据
Change Unidirectional Association to Bidirectional(将单向关联改为双向关联)
两个类都需要使用对方特性,但其间只有一个单向连接
Change Bidirectional Association to Unidirectional(将双向关联改为单向关联)
两个类之间有双向关联,但其中一个类如今不再需要另一个类的特性
Replace Magic Number with Symbolic Constant(以字面常量取代魔法数)
Encapsulate Field(封装字段)
存在一个public 字段
Encapsulate Collection(封装集合)
函数直接返回了一个集合
Replace Record with Data Class(以数据类取代记录)
Replace Type Code with Class(以类取代类型码)
类之中有一个数值类型码,但它不影响类的行为
Replace Type Code with Subclasses(以子类取代类型码)
有一个不可变的类型码,它会影响类的行为
Replace Type Code with State/Strategy(以State/Strategy取代类型码)
有一个类型码,它会影响类的行为,但你无法通过继承消除它
Replace Subclass with Fields(以字段取代子类)
简化条件表达式
Decompose Conditional(分解条件表达式)
有一个复杂的 if-else 语句
Consolidate Conditional Expression(合并条件表达式)
有一系列条件测试,都得到相同结果
Consolidate Duplicate Conditional Fragments(合并重复的条件片段)
在条件表达式的每个分支上有相同的一段代码
Remove Control Flag(移除控制标记)
以break 或return 取代控制标记
Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件表达式)
函数中的条件逻辑使人难以看清正常的执行路径
Replace Conditional with Polymorphism(以多态取代条件表达式)
Introduce Null Object(引入Null 对象)
一而再,再而三地检查对象是否null
Introduce Assertion(引入断言)
简化函数调用
Rename Method(函数改名)
函数的名称未能揭示函数的用途
Add Parameter(添加参数)
某个函数需要从client端得到更多信息
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(隐藏函数)
有一个函数,从来没有被其他类用到
Replace Constructor with Factory Method(以工厂函数取代构造函数)
希望在创建对象时不仅仅是做简单的创建动作
Encapsulate Downcast(封装向下转型)
某个函数返回的对象,需要由函数调用者执行向下转型(downcast)
Replace Error Code with Exception(以异常取代错误码)
某个函数返回一个特定的代码,用来表示某种错误情况
Replace Exception with Test(以测试取代异常)
面对一个可以预先检查的条件,却抛出了一个异常
处理概括关系
Pull Up Field(字段上移)
两个子类拥有形同的字段
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(以继承取代委托)
两个类之间使用委托关系,并经常为整个接口编写许多极简单的委托函数
大型重构
0 条评论
下一页
为你推荐
查看更多