代码的坏味道与重构
2025-02-10 15:37:45 0 举报
AI智能生成
代码的坏味道(Code Smells)是指在软件开发过程中,一些潜在的、指示可能存在问题的代码特征。这些特征可能表明代码难以维护、存在效率低下或是未来可能会引发错误。重构(Refactoring)是指在不改变程序外部行为的前提下,改善其内部结构的过程。通过重构,可以消除代码坏味道,提高代码质量,降低复杂度,增强可读性和可维护性。 在重构过程中,开发者会利用一系列的重构手法,比如“抽取方法(Extract Method)”来改善代码模块化;使用“合并变量(Combine Identical Conditional Fragments)”来简化条件判断;或者通过“分解条件语句(Decompose Conditional)”来处理复杂的条件逻辑。重构的目标是让代码保持简洁和透明,以便于未来的开发者更容易理解和扩展。 有效的重构要求开发者具备良好的代码感觉,以及对设计模式和软件设计原则的深刻理解。重构也可能引入风险,因此在整个过程中进行代码审查和测试是至关重要的,以确保重构后代码的正确性。作为一种常见的开发实践,定期的代码审查和小规模的重构可以帮助保持代码库的健康。
作者其他创作
大纲/内容
代码的坏味道
神秘命名
重构手法
改变函数声明
变量改名
字段改名
重复代码
重构手法
提炼函数
移动语句
函数上移
过长函数
重构手法
提炼函数
查询取代零时变量
引入参数对象
保持对象完整
以命令取代函数
分解条件表达式
以多态取代条件表达式
拆分循环
过长参数列表
重构手法
以查询取代参数
保持对象完整
引入参数对象
移除标记参数
函数聚合成类
全局数据
重构手法
封装变量
可变数据
重构手法
封装变量
拆分变量
移动语句
提炼函数
查询函数和修改函数分离
移除设值函数
查询取代派生变量
函数组合成类
函数组合成变换
引用对象改为值对象
发散式变化
重构手法
拆分阶段
搬移函数
提炼函数
提炼类
霰弹式修改
重构手法
搬移函数
搬移字段
函数组合成类
函数组合成变换
拆分阶段
内联函数
内联类
依恋情节
重构手法
搬移函数
提炼函数
数据泥团
重构手法
提炼类
引入参数对象
保持对象完整
基本类型偏执
重构手法
以对象取代基本类型
以子类型取代类型码
重复的switch
重构手法
以多态取代条件表达式
循环语句
重构手法
以管道取代循环
冗赘的元素
重构手法
内联函数
内联类
折叠继承体系
夸夸其谈通用性
重构手法
折叠继承体系
内联函数
内联类
改变函数声明
移除死代码
临时字段
重构手法
提炼类
搬移函数
引入特例
过长的消息链
重构手法
隐藏委托关系
提炼函数
搬移函数
中间人
重构手法
移除中间人
内联函数
内幕交易
重构手法
搬移函数
搬移字段
隐藏委托关系
以委托取代子类
以委托取代超类
过大的类
重构手法
提炼类
提炼超类
以子类取代类型码
异曲同工的类
重构手法
改变函数声明
搬移函数
提炼超类
纯数据类
重构手法
封装记录
移除设值函数
拆分阶段
重构
原则
定义
(名词)对软件内部结构的一种调整,
目的是在不改变软件可观察行为的前提下,
提高其可理解性,降低其修改成本
目的是在不改变软件可观察行为的前提下,
提高其可理解性,降低其修改成本
(动词)使用一系列重构手法,
在不改变软件可观察行为的前提下,调整其结构
在不改变软件可观察行为的前提下,调整其结构
好处
改进软件的设计
是软件更容易理解
帮助找出bug
提高编程速度
时机
预备性重构:让添加新功能更容易
帮助理解的重构:使代码更易懂
捡垃圾式重构
有计划的重构和见机行事的重构
长期重构
code review时重构
挑战
延缓新功能的开发
代码所有权
分支
测试
完备的单元测试有利于重构的实施
遗留代码
数据库
重构方法
日常重构
提炼函数
步骤
创造一个新函数,根据这个函数的意图来对它命名(以它“做什么”来命名,而
不是以它“怎样做”命名)
不是以它“怎样做”命名)
将待提炼的代码从源函数复制到新建的目标函数中
仔细检查提炼出的代码,看看其中是否引用了作用域限于源函数、在提炼出的
新函数中访问不到的变量。若是,以参数的形式将它们传递给新函数
新函数中访问不到的变量。若是,以参数的形式将它们传递给新函数
所有变量都处理完之后,编译
在源函数中,将被提炼代码段替换为对目标函数的调用
测试
查看其他代码是否有与被提炼的代码段相同或相似之处。如果有,考虑使用以
函数调用取代内联代码(222)令其调用提炼出的新函数
函数调用取代内联代码(222)令其调用提炼出的新函数
内联函数
步骤
检查函数,确定它不具多态性
找出这个函数的所有调用点
将这个函数的所有调用点都替换为函数本体
每次替换之后,执行测试
删除该函数的定义
提炼变量
步骤
确认要提炼的表达式没有副作用
声明一个不可修改的变量,把你想要提炼的表达式复制一份,以该表达式的结
果值给这个变量赋值
果值给这个变量赋值
用这个新变量取代原来的表达式
测试
内联变量
步骤
检查确认变量赋值语句的右侧表达式没有副作用
如果变量没有被声明为不可修改,先将其变为不可修改,并执行测试
找到第一处使用该变量的地方,将其替换为直接使用赋值语句的右侧表达式
测试
重复前面两步,逐一替换其他所有使用该变量的地方
删除该变量的声明点和赋值语句
测试
改变函数声明
步骤
简单的做法
如果想要移除一个参数,需要先确定函数体内没有使用该参数
修改函数声明,使其成为你期望的状态
找出所有使用旧的函数声明的地方,将它们改为使用新的函数声明
测试
迁移式做法
如果有必要的话,先对函数体内部加以重构,使后面的提炼步骤易于开展
使用提炼函数(106)将函数体提炼成一个新函数
如果提炼出的函数需要新增参数,用前面的简单做法添加即可
测试
对旧函数使用内联函数(115)
如果新函数使用了临时的名字,再次使用改变函数声明(124)将其改回原来
的名字
的名字
测试
封装变量
步骤
创建封装函数,在其中访问和更新变量值
执行静态检查
逐一修改使用该变量的代码,将其改为调用合适的封装函数。每次替换之后,
执行测试
执行测试
限制变量的可见性
测试
如果变量的值是一个记录,考虑使用封装记录(162)
变量改名
步骤
如果变量被广泛使用,考虑运用封装变量(132)将其封装起来
找出所有使用该变量的代码,逐一修改
测试
引入参数对象
步骤
如果暂时还没有一个合适的数据结构,就创建一个
测试
使用改变函数声明(124)给原来的函数新增一个参数,类型是新建的数据结
构
构
测试
调整所有调用者,传入新数据结构的适当实例。每修改一处,执行测试
用新数据结构中的每项元素,逐一取代参数列表中与之对应的参数项,然后删
除原来的参数
除原来的参数
测试
函数组合成类
步骤
运用封装记录(162)对多个函数共用的数据记录加以封装
对于使用该记录结构的每个函数,运用搬移函数(198)将其移入新类
用以处理该数据记录的逻辑可以用提炼函数(106)提炼出来,并移入新类
函数组合成变换
步骤
创建一个变换函数,输入参数是需要变换的记录,并直接返回该记录的值
挑选一块逻辑,将其主体移入变换函数中,把结果作为字段添加到输出记录
中。修改客户端代码,令其使用这个新字段
中。修改客户端代码,令其使用这个新字段
测试
针对其他相关的计算逻辑,重复上述步骤
拆分阶段
步骤
将第二阶段的代码提炼成独立的函数
测试
引入一个中转数据结构,将其作为参数添加到提炼出的新函数的参数列表中
测试
逐一检查提炼出的“第二阶段函数”的每个参数。如果某个参数被第一阶段用
到,就将其移入中转数据结构。每次搬移之后都要执行测试
到,就将其移入中转数据结构。每次搬移之后都要执行测试
对第一阶段的代码运用提炼函数(106),让提炼出的函数返回中转数据结
构
构
封装
封装记录
步骤
对持有记录的变量使用封装变量(132),将其封装到一个函数中
创建一个类,将记录包装起来,并将记录变量的值替换为该类的一个实例。然
后在类上定义一个访问函数,用于返回原始的记录。修改封装变量的函数,令
其使用这个访问函数
后在类上定义一个访问函数,用于返回原始的记录。修改封装变量的函数,令
其使用这个访问函数
测试
新建一个函数,让它返回该类的对象,而非那条原始的记录
对于该记录的每处使用点,将原先返回记录的函数调用替换为那个返回实例对
象的函数调用。使用对象上的访问函数来获取数据的字段,如果该字段的访问
函数还不存在,那就创建一个。每次更改之后运行测试
象的函数调用。使用对象上的访问函数来获取数据的字段,如果该字段的访问
函数还不存在,那就创建一个。每次更改之后运行测试
移除类对原始记录的访问函数,那个容易搜索的返回原始数据的函数也要一并
删除
删除
测试
如果记录中的字段本身也是复杂结构,考虑对其再次应用封装记录(162)或
封装集合(170)手法
封装集合(170)手法
封装集合
步骤
如果集合的引用尚未被封装起来,先用封装变量(132)封装它
在类上添加用于“添加集合元素”和“移除集合元素”的函数
执行静态检查
查找集合的引用点。如果有调用者直接修改集合,令该处调用使用新的添加/
移除元素的函数。每次修改后执行测试
移除元素的函数。每次修改后执行测试
修改集合的取值函数,使其返回一份只读的数据,可以使用只读代理或数据副
本
本
测试
以对象取代基本类型
步骤
如果变量尚未被封装起来,先使用封装变量(132)封装它
为这个数据值创建一个简单的类。类的构造函数应该保存这个数据值,并为它
提供一个取值函数
提供一个取值函数
执行静态检查
修改第一步得到的设值函数,令其创建一个新类的对象并将其存入字段,如果
有必要的话,同时修改字段的类型声明
有必要的话,同时修改字段的类型声明
修改取值函数,令其调用新类的取值函数,并返回结果
测试
考虑对第一步得到的访问函数使用函数改名(124),以便更好反映其用途
考虑应用将引用对象改为值对象(252)或将值对象改为引用对象(256),明
确指出新对象的角色是值对象还是引用对象
确指出新对象的角色是值对象还是引用对象
以查询取代临时变量
步骤
检查变量在使用前是否已经完全计算完毕,检查计算它的那段代码是否每次都
能得到一样的值
能得到一样的值
如果变量目前不是只读的,但是可以改造成只读变量,那就先改造它
测试
将为变量赋值的代码段提炼成函数
测试
应用内联变量(123)手法移除临时变量
提炼类
步骤
决定如何分解类所负的责任
创建一个新的类,用以表现从旧类中分离出来的责任
构造旧类时创建一个新类的实例,建立“从旧类访问新类”的连接关系
对于你想搬移的每一个字段,运用搬移字段(207)搬移之。每次更改后运行
测试
测试
使用搬移函数(198)将必要函数搬移到新类。先搬移较低层函数(也就是“被
其他函数调用”多于“调用其他函数”者)。每次更改后运行测试。
其他函数调用”多于“调用其他函数”者)。每次更改后运行测试。
检查两个类的接口,去掉不再需要的函数,必要时为函数重新取一个适合新环
境的名字
境的名字
决定是否公开新的类。如果确实需要,考虑对新类应用将引用对象改为值对象
(252)使其成为一个值对象
(252)使其成为一个值对象
内联类
步骤
对于待内联类(源类)中的所有public函数,在目标类上创建一个对应的函
数,新创建的所有函数应该直接委托至源类
数,新创建的所有函数应该直接委托至源类
修改源类public方法的所有引用点,令它们调用目标类对应的委托方法。每次
更改后运行测试
更改后运行测试
将源类中的函数与数据全部搬移到目标类,每次修改之后进行测试,直到源类
变成空壳为止
变成空壳为止
删除源类,为它举行一个简单的“丧礼”
隐藏委托关系
步骤
对于每个委托关系中的函数,在服务对象端建立一个简单的委托函数
调整客户端,令它只调用服务对象提供的函数。每次调整后运行测试
如果将来不再有任何客户端需要取用Delegate(受托类),便可移除服务对象
中的相关访问函数
中的相关访问函数
测试
移除中间人
步骤
为受托对象创建一个取值函数
对于每个委托函数,让其客户端转为连续的访问函数调用。每次替换后运行测
试
试
替换算法
步骤
整理一下待替换的算法,保证它已经被抽取到一个独立的函数中
先只为这个函数准备测试,以便固定它的行为
准备好另一个(替换用)算法
执行静态检查
子主题
搬移特性
搬移函数
步骤
检查函数在当前上下文里引用的所有程序元素(包括变量和函数),考虑是否
需要将它们一并搬移
需要将它们一并搬移
检查待搬移函数是否具备多态性
将函数复制一份到目标上下文中。调整函数,使它能适应新家
执行静态检查
设法从源上下文中正确引用目标函数
修改源函数,使之成为一个纯委托函数
测试
考虑对源函数使用内联函数
搬移字段
步骤
确保源字段已经得到了良好封装
测试
在目标对象上创建一个字段(及对应的访问函数)
执行静态检查
确保源对象里能够正常引用目标对象
调整源对象的访问函数,令其使用目标对象的字段
测试
移除源对象上的字段
测试
搬移语句到函数
步骤
如果重复的代码段离调用目标函数的地方还有些距离,则先用移动语句
(223)将这些语句挪动到紧邻目标函数的位置
(223)将这些语句挪动到紧邻目标函数的位置
如果目标函数仅被唯一一个源函数调用,那么只需将源函数中的重复代码段剪
切并粘贴到目标函数中即可,然后运行测试。本做法的后续步骤至此可以忽
略
切并粘贴到目标函数中即可,然后运行测试。本做法的后续步骤至此可以忽
略
如果函数不止一个调用点,那么先选择其中一个调用点应用提炼函数
(106),将待搬移的语句与目标函数一起提炼成一个新函数。给新函数取个
临时的名字,只要易于搜索即可
(106),将待搬移的语句与目标函数一起提炼成一个新函数。给新函数取个
临时的名字,只要易于搜索即可
调整函数的其他调用点,令它们调用新提炼的函数。每次调整之后运行测试
完成所有引用点的替换后,应用内联函数(115)将目标函数内联到新函数
里,并移除原目标函数
里,并移除原目标函数
对新函数应用函数改名(124),将其改名为原目标函数的名字
搬移语句到调用者
步骤
最简单的情况下,原函数非常简单,其调用者也只有寥寥一两个,此时只需把
要搬移的代码从函数里剪切出来并粘贴回调用端去即可,必要的时候做些调
整。运行测试。如果测试通过,那就大功告成,本手法可以到此为止
要搬移的代码从函数里剪切出来并粘贴回调用端去即可,必要的时候做些调
整。运行测试。如果测试通过,那就大功告成,本手法可以到此为止
若调用点不止一两个,则需要先用提炼函数(106)将你不想搬移的代码提炼
成一个新函数,函数名可以临时起一个,只要后续容易搜索即可
成一个新函数,函数名可以临时起一个,只要后续容易搜索即可
对原函数应用内联函数
对提炼出来的函数应用改变函数声明(124),令其与原函数使用同一个名
字
字
以函数调用取代内联代码
步骤
将内联代码替代为对一个既有函数的调用
测试
移动语句
步骤
确定待移动的代码片段应该被搬往何处。仔细检查待移动片段与目的地之间的
语句,看看搬移后是否会影响这些代码正常工作。如果会,则放弃这项重构
语句,看看搬移后是否会影响这些代码正常工作。如果会,则放弃这项重构
剪切源代码片段,粘贴到上一步选定的位置上
测试
拆分循环
步骤
复制一遍循环代码
识别并移除循环中的重复代码,使每个循环只做一件事
测试
完成循环拆分后,考虑对得到的每个循环应用提炼函数
以管道取代循环
步骤
创建一个新变量,用以存放参与循环过程的集合
从循环顶部开始,将循环里的每一块行为依次搬移出来,在上一步创建的集合
变量上用一种管道运算替代之。每次修改后运行测试
变量上用一种管道运算替代之。每次修改后运行测试
搬移完循环里的全部行为后,将循环整个删除
移除死代码
步骤
如果死代码可以从外部直接引用,比如它是一个独立的函数时,先查找一下还
有无调用点
有无调用点
将死代码移除
测试
重新组织数据
拆分变量
步骤
在待分解变量的声明及其第一次被赋值处,修改其名称
如果可能的话,将新的变量声明为不可修改
以该变量的第二次赋值动作为界,修改此前对该变量的所有引用,让它们引用
新变量
新变量
测试
重复上述过程。每次都在声明处对变量改名,并修改下次赋值之前的引用,直
至到达最后一处赋值
至到达最后一处赋值
字段改名
步骤
如果记录的作用域较小,可以直接修改所有该字段的代码,然后测试。后面的
步骤就都不需要了
步骤就都不需要了
如果记录还未封装,请先使用封装记录
在对象内部对私有字段改名,对应调整内部访问该字段的函数
测试
如果构造函数的参数用了旧的字段名,运用改变函数声明(124)将其改名
运用函数改名(124)给访问函数改名
以查询取代派生变量
步骤
识别出所有对变量做更新的地方。如有必要,用拆分变量(240)分割各个更
新点
新点
新建一个函数,用于计算该变量的值
用引入断言(302)断言该变量和计算函数始终给出同样的值
测试
修改读取该变量的代码,令其调用新建的函数
测试
用移除死代码(237)去掉变量的声明和赋值
将引用对象改为值对象
步骤
检查重构目标是否为不可变对象,或者是否可修改为不可变对象
用移除设值函数(331)逐一去掉所有设值函数
提供一个基于值的相等性判断函数,在其中使用值对象的字段
将值对象改为引用对象
步骤
为相关对象创建一个仓库(如果还没有这样一个仓库的话)
确保构造函数有办法找到关联对象的正确实例
修改宿主对象的构造函数,令其从仓库中获取关联对象。每次修改后执行测
试
试
简化条件逻辑
分解条件表达式
步骤
对条件判断和每个条件分支分别运用提炼函数(106)手法
合并条件表达式
步骤
确定这些条件表达式都没有副作用
使用适当的逻辑运算符,将两个相关条件表达式合并为一个
测试
重复前面的合并过程,直到所有相关的条件表达式都合并到一起
可以考虑对合并后的条件表达式实施提炼函数
以卫语句取代嵌套条件表达式
步骤
选中最外层需要被替换的条件逻辑,将其替换为卫语句
测试
有需要的话,重复上述步骤
如果所有卫语句都引发同样的结果,可以使用合并条件表达式(263)合并
之
之
以多态取代条件表达式
步骤
如果现有的类尚不具备多态行为,就用工厂函数创建之,令工厂函数返回恰当
的对象实例
的对象实例
在调用方代码中使用工厂函数获得对象实例
将带有条件逻辑的函数移到超类中
任选一个子类,在其中建立一个函数,使之覆写超类中容纳条件表达式的那个
函数。将与该子类相关的条件表达式分支复制到新函数中,并对它进行适当调
整
函数。将与该子类相关的条件表达式分支复制到新函数中,并对它进行适当调
整
重复上述过程,处理其他条件分支
在超类函数中保留默认情况的逻辑。或者,如果超类应该是抽象的,就把该函
数声明为abstract,或在其中直接抛出异常,表明计算责任都在子类中
数声明为abstract,或在其中直接抛出异常,表明计算责任都在子类中
引入特例
步骤
给重构目标添加检查特例的属性,令其返回false
创建一个特例对象,其中只有检查特例的属性,返回true
对“与特例值做比对”的代码运用提炼函数(106),确保所有客户端都使用这
个新函数,而不再直接做特例值的比对
个新函数,而不再直接做特例值的比对
将新的特例对象引入代码中,可以从函数调用中返回,也可以在变换函数中生
成
成
修改特例比对函数的主体,在其中直接使用检查特例的属性
测试
使用函数组合成类(144)或函数组合成变换(149),把通用的特例处理逻辑
都搬移到新建的特例对象中
都搬移到新建的特例对象中
对特例比对函数使用内联函数(115),将其内联到仍然需要的地方
引入断言
步骤
如果你发现代码假设某个条件始终为真,就加入一个断言明确说明这种情况
重构API
将查询函数和修改函数分离
步骤
复制整个函数,将其作为一个查询来命名
从新建的查询函数中去掉所有造成副作用的语句
执行静态检查
查找所有调用原函数的地方。如果调用处用到了该函数的返回值,就将其改为
调用新建的查询函数,并在下面马上再调用一次原函数。每次修改之后都要测
试
调用新建的查询函数,并在下面马上再调用一次原函数。每次修改之后都要测
试
从原函数中去掉返回值
测试
函数参数化
步骤
从一组相似的函数中选择一个
运用改变函数声明(124),把需要作为参数传入的字面量添加到参数列表
中
中
修改该函数所有的调用处,使其在调用时传入该字面量值
测试
修改函数体,令其使用新传入的参数。每使用一个新参数都要测试
对于其他与之相似的函数,逐一将其调用处改为调用已经参数化的函数。每次
修改后都要测试
修改后都要测试
移除标记参数
步骤
针对参数的每一种可能值,新建一个明确函数
对于“用字面量值作为参数”的函数调用者,将其改为调用新建的明确函数
保持对象完整
步骤
新建一个空函数,给它以期望中的参数列表(即传入完整对象作为参数)
在新函数体内调用旧函数,并把新的参数(即完整对象)映射到旧的参数列表
(即来源于完整对象的各项数据)
(即来源于完整对象的各项数据)
执行静态检查
逐一修改旧函数的调用者,令其使用新函数,每次修改之后执行测试
所有调用处都修改过来之后,使用内联函数(115)把旧函数内联到新函数体
内
内
给新函数改名,从重构开始时的容易搜索的临时名字,改为使用旧函数的名
字,同时修改所有调用处
字,同时修改所有调用处
以查询取代参数
步骤
如果有必要,使用提炼函数(106)将参数的计算过程提炼到一个独立的函数
中
中
将函数体内引用该参数的地方改为调用新建的函数。每次修改后执行测试
全部替换完成后,使用改变函数声明(124)将该参数去掉
以参数取代查询
步骤
对执行查询操作的代码使用提炼变量(119),将其从函数体中分离出来
现在函数体代码已经不再执行查询操作(而是使用前一步提炼出的变量),对
这部分代码使用提炼函数(106)
这部分代码使用提炼函数(106)
使用内联变量(123),消除刚才提炼出来的变量
对原来的函数使用内联函数(115)
对新函数改名,改回原来函数的名字
移除设值函数
步骤
如果构造函数尚无法得到想要设入字段的值,就使用改变函数声明(124)将
这个值以参数的形式传入构造函数。在构造函数中调用设值函数,对字段设
值
这个值以参数的形式传入构造函数。在构造函数中调用设值函数,对字段设
值
移除所有在构造函数之外对设值函数的调用,改为使用新的构造函数。每次修
改之后都要测试
改之后都要测试
使用内联函数(115)消去设值函数。如果可能的话,把字段声明为不可变
测试
以工厂函数取代构造函数
步骤
新建一个工厂函数,让它调用现有的构造函数
将调用构造函数的代码改为调用工厂函数
每修改一处,就执行测试
尽量缩小构造函数的可见范围
以命令取代函数
步骤
为想要包装的函数创建一个空的类,根据该函数的名字为其命名
使用搬移函数(198)把函数移到空的类里
可以考虑给每个参数创建一个字段,并在构造函数中添加对应的参数
以函数取代命令
步骤
运用提炼函数(106),把“创建并执行命令对象”的代码单独提炼到一个函数
中
中
对命令对象在执行阶段用到的函数,逐一使用内联函数(115)
使用改变函数声明(124),把构造函数的参数转移到执行函数
对于所有的字段,在执行函数中找到引用它们的地方,并改为使用参数。每次
修改后都要测试
修改后都要测试
把“调用构造函数”和“调用执行函数”两步都内联到调用方(也就是最终要替换
命令对象的那个函数)
命令对象的那个函数)
测试
用移除死代码(237)把命令类消去
处理继承关系
函数上移
步骤
检查待提升函数,确定它们是完全一致的
检查函数体内引用的所有函数调用和字段都能从超类中调用到
如果待提升函数的签名不同,使用改变函数声明(124)将那些签名都修改为
你想要在超类中使用的签名
你想要在超类中使用的签名
在超类中新建一个函数,将某一个待提升函数的代码复制到其中
执行静态检查
移除一个待提升的子类函数
测试
逐一移除待提升的子类函数,直到只剩下超类中的函数为止
字段上移
步骤
针对待提升之字段,检查它们的所有使用点,确认它们以同样的方式被使用
如果这些字段的名称不同,先使用变量改名(137)为它们取个相同的名字
在超类中新建一个字段
移除子类中的字段
测试
构造函数本体上移
步骤
如果超类还不存在构造函数,首先为其定义一个。确保让子类调用超类的构造
函数
函数
使用移动语句(223)将子类中构造函数中的公共语句移动到超类的构造函数
调用语句之后
调用语句之后
逐一移除子类间的公共代码,将其提升至超类构造函数中。对于公共代码中引
用到的变量,将其作为参数传递给超类的构造函数
用到的变量,将其作为参数传递给超类的构造函数
测试
如果存在无法简单提升至超类的公共代码,先应用提炼函数(106),再利用
函数上移(350)提升之
函数上移(350)提升之
函数下移
步骤
将超类中的函数本体复制到每一个需要此函数的子类中
删除超类中的函数
测试
将该函数从所有不需要它的那些子类中删除
测试
字段下移
步骤
在所有需要该字段的子类中声明该字段
将该字段从超类中移除
测试
将该字段从所有不需要它的那些子类中删掉
测试
以子类取代类型码
步骤
自封装类型码字段
任选一个类型码取值,为其创建一个子类。覆写类型码类的取值函数,令其返
回该类型码的字面量值
回该类型码的字面量值
创建一个选择器逻辑,把类型码参数映射到新的子类
测试
针对每个类型码取值,重复上述“创建子类、添加选择器逻辑”的过程。每次修
改后执行测试。
改后执行测试。
去除类型码字段
测试
使用函数下移(359)和以多态取代条件表达式(272)处理原本访问了类型码
的函数。全部处理完后,就可以移除类型码的访问函数
的函数。全部处理完后,就可以移除类型码的访问函数
移除子类
步骤
使用以工厂函数取代构造函数(334),把子类的构造函数包装到超类的工厂
函数中
函数中
如果有任何代码检查子类的类型,先用提炼函数(106)把类型检查逻辑包装
起来,然后用搬移函数(198)将其搬到超类。每次修改后执行测试
起来,然后用搬移函数(198)将其搬到超类。每次修改后执行测试
新建一个字段,用于代表子类的类型
将原本针对子类的类型做判断的函数改为使用新建的类型字段
删除子类
测试
提炼超类
步骤
为原本的类新建一个空白的超类
测试
使用构造函数本体上移(355)、函数上移(350)和字段上移(353)手法,
逐一将子类的共同元素上移到超类
逐一将子类的共同元素上移到超类
检查留在子类中的函数,看它们是否还有共同的成分。如果有,可以先用提炼
函数(106)将其提炼出来,再用函数上移(350)搬到超类
函数(106)将其提炼出来,再用函数上移(350)搬到超类
检查所有使用原本的类的客户端代码,考虑将其调整为使用超类的接口
折叠继承体系
步骤
选择想移除的类:是超类还是子类
使用字段上移(353)、字段下移(361)、函数上移(350)和函数下移
(359),把所有元素都移到同一个类中
(359),把所有元素都移到同一个类中
调整即将被移除的那个类的所有引用点,令它们改而引用合并后留下的类
移除我们的目标;此时它应该已经成为一个空类
测试
以委托取代子类
步骤
如果构造函数有多个调用者,首先用以工厂函数取代构造函数(334)把构造
函数包装起来
函数包装起来
创建一个空的委托类,这个类的构造函数应该接受所有子类特有的数据项,并
且经常以参数的形式接受一个指回超类的引用
且经常以参数的形式接受一个指回超类的引用
在超类中添加一个字段,用于安放委托对象
修改子类的创建逻辑,使其初始化上述委托字段,放入一个委托对象的实例
选择一个子类中的函数,将其移入委托类
使用搬移函数(198)手法搬移上述函数,不要删除源类中的委托代码
如果被搬移的源函数还在子类之外被调用了,就把留在源类中的委托代码从子
类移到超类,并在委托代码之前加上卫语句,检查委托对象存在。如果子类之
外已经没有其他调用者,就用移除死代码(237)去掉已经没人使用的委托代
码
类移到超类,并在委托代码之前加上卫语句,检查委托对象存在。如果子类之
外已经没有其他调用者,就用移除死代码(237)去掉已经没人使用的委托代
码
测试
重复上述过程,直到子类中所有函数都搬到委托类
找到所有调用子类构造函数的地方,逐一将其改为使用超类的构造函数
测试
运用移除死代码(237)去掉子类
以委托取代超类
步骤
在子类中新建一个字段,使其引用超类的一个对象,并将这个委托引用初始化
为超类的新实例
为超类的新实例
针对超类的每个函数,在子类中创建一个转发函数,将调用请求转发给委托引
用。每转发一块完整逻辑,都要执行测试
用。每转发一块完整逻辑,都要执行测试
当所有超类函数都被转发函数覆写后,就可以去掉继承关系
工具
idea refactoring
vs code
0 条评论
下一页