0
点赞
收藏
分享

微信扫一扫

重构改善既有代码设计总结之重构手法

史值拥 2022-02-18 阅读 42

106、提炼函数

要点:

(1)将意图和实现分开:如果需要花时间浏览一段代码才能弄清它到底在干什么,就该提炼到一个函数中,并根据期用途来命名(大多数时候根本不需要关心函数如何达成其意图,这是函数体内干的事)

(2)一个大函数中,一段代码上放着一句注释,此时可以将代码提炼到自己函数中,注释往往提示一个好名字

115、内联函数

要点:

(1)手上有一群组织不合理的函数,可以将它们内联到一个大函数中,再以合理的方式重新提炼小函数

(2)间接性可能带来帮助,但非必要的间接性总是让人晕头转向,如果系统中所有函数都是对另一个函数简单的委托,可以使用内联

119、提炼变量(引入解释性变量)

(1)意义:表达式可能非常复杂而难以阅读,这种情况下,局部变量可以帮助我们将表达式分解为比较容易管理的形式。在面对一块复杂逻辑时,局部变量使我能够给其中一部分命名,能让人更好的理解这部分逻辑

(2)如果这段逻辑在更大范围内适用,可以对新的变量使用提炼函数(106)

123、内联变量

(1)意义:在函数内部,变量能给表达式提供有意义的名字,但有时候,这个名字并不比表达式本身更具有表现力。有时候,变量会妨碍重构附近的代码。

124、改变函数声明

(1)意义:一个好的名字能让人一眼看出函数用途,不必看其代码实现(可读性),一个好的方法:先写一句注释描述函数用途,再把注释变成函数名

(2)函数参数的设置也是一样,修改参数列表不仅能增加函数的应用范围,还能改变连接一个模块所需的条件,从而去除不需要的耦合(比如,一个处理电话号码格式的逻辑,参数可以是电话号码,也可以是人,如果是电话号码,可以让逻辑无需了解“人”的概念减少模块间耦合,但如果参数用"人",可以很容易的访问后者的其他属性,当逻辑修改后,不用修改所有的调用处,换句话说提高了函数的封装度)

132、封装变量

(1)创建封装函数,在其中访问和更新变量值

(2)意义:封装提供一个清晰的观测点,由此监控数据的变化和使用情况

140、引入参数对象

要点:

(1)一组数据总是结伴同行,这样一组"数据泥团",用一个数据结构代之

(2)意义:缩短参数列表;所有用到该数据结构的函数通过相同名字来访问,提升代码一致性;更重要的是催生更深层次的改变,将围绕这些数据的共用行为组合起来提升为新的抽象概念

144、函数组合成类

(1)一组函数形影不离的操作同一块数据时,可以组建一类,把数据和行为捆绑到同一个环境中

(2)意义:减少参数的传递,简化函数调用,并且组建的对象可以方便的传递给系统其他部分

149、函数组合成变换

(1)意义:把所有计算派生数据的逻辑收拢到一处,把函数和他们的操作的数据放在一起,避免计算派生逻辑到处重复,收拢到一起也能方便查找

(2)变换:在js中相当于对象,将计算到的派生数据转为对象,java可以用函数组合成类

154、拆分阶段

(1)意义:一段代码在同时处理两件不同的事,把它拆分成各自独立的模块,当需要修改时,不需要考虑另一个主题

162、封装记录

170、封装集合

(1)意义:封装集合时人们常常犯一个错误,只对集合变量的访问进行封装,但依然让取值函数返回集合本身。这使得集合的成员变量可以直接被修改,而封装它的类全然不知。

(2)可以返回一个集合副本,或者返回一个不可变得集合代理

174、以对象取代基本类型

(1)意义:开发初期,我们往往用简单数据项表示简单情况,比如使用数字或字符串等。但随着开发进行,这些简单数据不再简单,比如,一开始用字符串表示电话号码,随后又需要格式化,抽取区号等特殊行为。这类逻辑很快占领代码库,制造重复代码,增加使用成本

(2)一旦发现某个数据的操作不仅仅局限于打印时,就为它创建一个新类,并将业务逻辑添加进去,往往能称为一个有用的工具类

178、以查询取代临时变量

(1)意义:临时变量允许我们引用之前的值,既能解释它的含义,还能避免对代码进行重复计算。但尽管使用方便,很多时候还是值得更进一步,抽取成函数,有利于我们分解冗长的函数,不必将其作为参数传递给提炼出来的小函数,也有利于在提炼得到的函数与原函数之间设立清晰边界(解耦),帮助我们避免依赖和副作用

(2)最好在类中使用,类中提供了一个共同的上下文,如果不在类中,可能会在顶层函数中拥有过多的参数,冲淡提炼函数带来的好处。

182、提炼类

(1)意义:一个类应该是一个清晰的抽象,只处理一些明确的责任。一个类如果维护大量函数和数据,往往让人不能理解,考虑将部分责任分离出去。

186、内联类

(1)如果一个类不再承担足够责任,不再有单独存在的理由,可以将该类塞进另一个类中

(2)手上有两个类,想重新安排它们肩负的职责,可以先内联类,再提炼类去分离职责会更简单

189、隐藏委托关系

(1)意义:“封装”意味着每个模块都应该尽可能少了解系统的其他部分,一旦发生变化,需要了解这一变化的模块就会比较少

(2)如果某些客户端先通过服务对象的字段得到另一个对象,然后调用后者的函数,那么客户端必须知晓这一层委托关系。如果委托类修改了接口,变化就会波及所有客户端。我们可以在服务对象上放置一个简单的委托函数,将委托关系隐藏起来,去除这种依赖

192、移除中间人

198、搬移函数

(1)意义:一个函数频繁引用其他上下文中的元素,而对自身上下文的元素却关心盛少,可以将其与更亲密的元素会和,起到封装效果,让系统别处减少对当前模块的依赖

207、搬移字段

223、移动语句

要点:

(1)将存在关联的东西一起出现,可以使代码更容易理解,比如,有几行代码取用了一个数据结构,最好让他们在一起出现,一般这项重构是"提炼函数"之前的准备工作

227、拆分循环

(1)意义:使代码结构清晰;如果一次循环做了两件不同的事,当需要修改循环时,你需要同时理解这两件事情

(2)一般拆分循环后,紧接着对每个循环提炼函数(106)

(3)关于性能,一般循环本身很少称为性能瓶颈,如果循环真的称为性能瓶颈再合并到一起

231、以管道取代循环

(1)意义:管道运算如filter、map等,代码的可读性更强,只需从头到位阅读一遍代码,就能弄清对象在管道中的变换过程

237、移除死代码

240、拆分变量

(1)意义:同一个变量承担多个不同的事情,会让阅读者难以理解

(2)场景一:同一个局部变量被赋值多次,且意义不同

(3)场景二:对输入参数赋值,变量是以输入参数的形式声明又在函数内部被再次赋值

248、以查询取代派生变量

(1)意义:对数据的修改常常导致代码的各个部分以丑陋的形式相互耦合,在一处修改数据,却在另一处造成难以发现的破坏

(2)有些变量可以很容易的随时计算出来,用计算替代变量可以消除可变性,计算常能更清晰的表达数据的含义,也能避免“源数据修改,忘记更新派生变量”

252、将引用对象改为值对象

(1)意义:如果希望几个对象之间共享一个对象,那么就用引用对象,如果不希望这个对象创建后被修改,则认为该对象是值对象

(2)值对象可以先移除所有设值函数,将字段初始化放在构造函数中,如果想改变字段的值,则重新创建一个新对象,并添加一个equal方法,判断两个值对象相等等于判断对象中的属性相等

260、分解条件表达式

(1)意义:可以突出条件逻辑,更清楚的表明每个分支的作用,提升代码可读性

(2)对条件判断和每个分支分别运用提炼函数(106)手法

263、合并条件表达式

(1)含义:有一串条件检查,检查条件各不相同,但最终行为却一致。可以使用逻辑或和逻辑与将它们合并为一个条件表达式

(2)意义:使得检查的用意更清晰(合并后的条件检查表述“实际上只有一次条件检查,只不过有多个并列条件检查而已”),其次,这项重构往往可以为使用提炼函数(106)做好准备,将检查条件提炼为一个独立函数对理清代码意义非常有用(提高代码可读性)

266、以卫语句取代嵌套条件表达式

(1)意义:给某个分支以特别的重视,卫语句告诉读者:“这种情况不是本函数的核心逻辑,如果发生了,请做一些必要的整理工作,然后退出”

(2)条件表达式通常有两种风格。第一种:两个分支都属于正常行为。第二种:只有一个分支是正常,另一个是异常情况

272、以多态取代条件表达式

意义:有时用条件逻辑本身的结构足以表达,但使用类和多态能把逻辑的拆分表述的更清晰

(1)场景一:多个不同的操作,都基于相同类型代码的switch语句,比如获取鸟的飞行速度和羽毛特征,都根据鸟类型的switch语句获取

(2)场景二:某个对象与另外一个对象大体类似,但又有一些不同之处;比如一家评级机构,对航行进行投资评级,盈利潜力分数评级和船长历史航行评级中都在询问“是否到过中国的航程”以及“船长是否曾去过中国”,那么可以用继承和多态将“中国因素”的特殊逻辑从基础逻辑中分离出来,这些重复的“中国因素”会混淆视听,让基础逻辑难以理解

289、引入特例(引入Null对象)

(1)意义:一种常见的重复代码是这种情况,一个数据结构的使用者都在检查某个特殊的值,并当这个特殊值出现时所做的处理也都相同。如果发现有多处以同样方式应对一个特殊值,我就会想把这个逻辑处理收拢到一处。

(2)做法:创建一个特殊对象,其中包括所有共用行为所对应的函数。

302、引入断言

306、将查询函数和修改函数分离

(1)任何有返回值得函数,都不应该有看得到的副作用(有种常见的优化方法,将查询结果缓存于某个字段中,这样后续的重复查询就可以大大加快,虽然这种做法改变了对象中缓存的状态,但这一修改是察觉不到,因为不论如何查询,总是活得相同结果)

(2)如果有看得到的副作用,则将查询动作从修改动作中分离出来

310、函数参数化(令函数携带参数)

(1)意义:如果两个函数逻辑非常相似,只有一些字面量值不同,可以将其合并成一个函数,以参数形式传入不同的值,从而消除重复

314、移除标记参数

(1)意义:标记参数会让人难以理解,应该怎么调用,需要调用者弄清标记参数有哪些可用值

(2)针对参数的每一种可能值,新建一个明确函数

319、对象完整性

(1)从一个结构中导出几个值,再把几个值一起传递给一个函数,可以将整个对象传递给函数

(2)可以更好的应对变化:如果将来被调用函数需要从对象中导出更多数据时,不用修改参数列表,并且可以缩短参数列表,使函数更易看懂

(3)如果很多函数在使用对象中的同一组数据,并且部分数据的逻辑重复,可以将这些处理逻辑搬移到完整对象中去

324、以查询取代参数

(1)意义:函数参数列表应该总结该函数的可变性,标识出函数可能体现出行为差异的主要方式,但参数列表应该避免重复(如果调用函数时,传入一个值,而这个值由函数自己来获取同样容易),参数列表越短,越容易理解

(2)如果可以从一个参数推导出另一个参数,那么就没有必要传递两个参数

(3)移除参数可能会给函数体增加不必要的依赖关系,需要慎重以查询取代参数

327、以参数取代查询

(1)意义:改变代码的依赖关系,让目标函数不在依赖某个元素,可以把这个元素以参数形式传递给该函数,从而将处理引用关系的责任转交给函数调用者

331、移除设值函数

(1)不希望对象创建之后,字段被修改,可以移除设值函数,将字段初始化在构造函数中完成

337、以命令取代函数

(1)与普通函数相比,命令对象提供更大的控制灵活性和更强的表达能力,还支持一些附加操作,例如撤销。而且命令对象可以拆解复杂的函数

(2)为函数创建一个空类,并考虑将每个参数创建一个字段,并在构造函数中添加对应参数

344、以函数取代命令

(1)意义:命令对象为处理复杂计算踢动强大的机制,可以轻松将原本复杂的函数拆解为多个方法,彼此间通过字段共享状态;拆解后的方法可以分别调用;但大多数时候,我只想调用一个函数,如果该函数不是太复杂,就该考虑将其变为普通函数

350、函数上移

(1)意义:避免重复代码,避免修改其中一个却未修改另一个的风险

(2)找出不同子类函数,而它们可以通过某种形式的参数调整称为相同函数,可以先应用函数参数化(310),然后上移。

(3)函数上移中,如果被提升的函数引用了子类中特有的特性,此时可以字段上移(353)和函数上移(350)将这些特性提升到超类

(4)如果两个函数工作流程大致相似,但实现细节略有差异,可以塑造模板函数,构造出相同的函数,再提升它们

359、函数下移

361、字段下移

362、以子类取代类型码

(1)表现分类关系的第一种工具是类型码字段,可能实现为枚举、符号、字符串或者数字。大多时候有这些类型码就够了,但我们可以往前一步,引入子类。继承有两个好处,首先可以用多态处理条件逻辑,如果几个函数都在根据类型码取值采取不同行为,多态就特别有用。可以用多态取代条件表达式(272)。另外,有些字段或函数只对特定类型码有意义,可以将字段下移(361)把这样的字段放在合适的子类中去。

375、提炼超类

380、折叠继承体系

(1)意义:在重构类继承体系时,我们经常把函数和字段上下移动。随着继承体系演化,发现一个类与其超类没有多大差别,把值得再作为独立的类存在,可以把超类和子类合并起来

381、以委托取代子类

399、以委托取代超类

举报

相关推荐

0 条评论