重新组织函数

Tip

最好能把大的修改拆成小的步骤,所以如果你既想修改函数名,又想添加参数最好分成两步来做。

不论何时,如果遇到了麻烦,请撤销修改,并改用迁移式做法)

中文名 英文名 时机 做法 意义  
变量改名 Rename Variable 1 变量/常量的名字不足以说明字段的意义
2 垃圾命名
3 如果在另一个代码库中使用了该变量,这就是一个"已发布变量"(published variable),此时不能进行这个重构。
①先用封装变量手法封装
②找到所有使用该变量的代码,修改变量名
③测试
④只作用于某个函数的直接替换即可
⑤替换过程中可以以新名字作为过渡。待全部替换完毕再删除旧的名字
表意准确  
以查询取代临时变量 Replace Temp with Query 1 修改对象最好是一个类(这也是为什么提倡class,因为类可以开辟一个命名空间,不至于有太多全局变量)
2 有很多函数都在将同一个值作为参数传递
3 分解过长的冗余函数
4 多个函数中重复编写计算逻辑,比如讲一个值进行转换(好几个函数内都需要这个转换函数)
5 如果这个值被多次修改,应该将这些计算代码一并提炼到取值函数
①检查是否每次计算过程和结果都一致(不一致则放弃)
②如果能改为只读,就改成只读
③将变量赋值取值提炼成函数
④测试
⑤去掉临时变量

检查变量在使用前是否已经完全计算完毕,检查计算它的那段代码是否每次都能得到一样的值。
if (isCalcXx)
可读性  
           
封装变量 Encapsulate Variable 1 当我们在修改或者增加使用可变数据的时候
2 数据被大范围使用(设置值)
3 对象、数组无外部变动需要内部一起改变的需求时候,最好返回一份副本
①创建封装函数(包含访问和更新函数)
②测试
③控制变量外部不可见(private、protected)
④测试
不可变性是强大的代码防腐剂。
数据被使用得越广,就越是值得花精力给它一个体面的封装。
 
拆分变量 Split Variable 1 一个变量被应用到两种/多种的作用下
2 修改输入参数的值
重复】{
①在变量第一次赋值的地方,为函数取一个更加有意义的变量名(尽量声明为const)
②在第二次赋值地方声明该变量
③以该变量第二次赋值动作为界,修改此前对该变量的所有引用。让他们引用新的变量
④测试}
让代码更容易读懂  
封装记录 Encapsulate Record 1 可变的记录型结构
2 一条记录上有多少字段不够直观
3 有需要对记录进行控制的需求(个人理解为需要控制权限、需要控制是否只读等情况)
4 需要对结构内字段进行隐藏
①首先用封装变量手法将记录转化为函数(旧的值的函数)
②声明一个新的类以及获取他的函数
③找到记录的使用点,在类内声明设置方法
④替换设置值的方法
⑤声明一个取值方法,并替换所有取值的地方
⑥测试
⑦删除旧的函数
⑧当我们需要改名时,可以保留老的,标记为不建议使用,并声明新的名字进行返回
获取与修改分离  
           
提炼变量 Extract Variable 1 一段又臭又长的表达式
2 在多处地方使用这个值(可能是当前函数、当前类乃至于更大的如全局作用域)
3 如果这个变量名在更宽的上下文中也有意义,我就会考虑将其暴露出来,通常以函数的形式。
①确保要提炼的表达式,对其他地方没有影响
②声明一个不可修改的变量,并用表达式作为该变量的值
重复】{
③用新变量(或函数)取代原来的表达式
④测试
}
局部变量可以帮助我们将表达式分解为比较容易管理的形式  
内联变量 Inline Variable 1 变量没有比当前表达式有什么更好的释义
2 变量妨碍了重构附近代码
3 有一个临时变量,只被一个简单表达式赋值一次
①检查确认变量赋值的右侧表达式不对其他地方造成影响
②确认是否为只读,如果没有声明只读,则要先让他只读,并测试
重复】{
③找到使用变量的地方,直接改为右侧表达式
④测试
}
⑤删除该变量的声明点和赋值语句
⑥多个内联变量在一起,可以用提炼函数取代临时变量
简化调用链  
           
提炼函数 Extract Function 1 当一段大函数内某一部分代码在做的事情是同一件事,并且自成体系,不与其他掺杂时
2 当代码展示的意图和真正想做的事情不是同一件时候
3 如果变量按值传递给提炼部分又在提炼部分被赋值,就必须多加小心

4 如果想不出一个更有意义的名称,这就是一个信号,可能我不应该提炼这块代码。
①以他要做什么事情来命名的函数待提炼代码复制到这个函数
②检查这个函数内的代码的作用域、变量
③编译查看函数内有没有报错
④替换源函数的被提炼代码替换为函数调用
⑤测试
⑥替换其他代码中是否有与被提炼的代码相同或相似之处
将意图与实现分开  
内联函数 Inline Function 1 函数内代码直观表达的意思与函数名字相同
2 有一堆杂乱无章的代码需要重构,可以先内联函数,再通过提炼函数合理重构
3 非多态性函数(函数属于一个类,而这个类被继承)
①检查多态性(如果该函数属于某个超类,并且它具有多态性,那么就无法内联)
②找到所有调用点
③将函数所有调用点替换为函数本体(非一次性替换,可以分批次替换、适应新家、测试)
④删掉该函数的定义(也可能会不删除,比如我们放弃了有一些函数调用,因为重构为渐进式,非一次性)
⑤检查上下文有没有导致重复的变量名
间接性可能带来帮助,但非必要的间接性总是让人不舒服  
           
改变函数声明 Change Function Declaration 1 函数名字不够贴切函数所做的事情
2 函数参数增加
3 函数参数减少
4 函数参数概念发生变化
5 函数因为某个参数导致的函数应用范围小(全局有很多类似的函数,在做着类似的事情)
①对函数内部进行重构(如果有必要的话)
②使用提炼函数手法,将函数体提炼成一个新函数(加后缀:old、new)
③在新函数内做我们的变更(新增参数、删除参数、改变参数释义等)
④改变函数调用的地方(如果是新增、修改、删除参数)
⑤测试
⑥对旧函数使用内联函数来调用或返回新函数
⑦如果使用了临时名字,使用改变函数声明将其改回原来的名字(这时候就要删除旧函数了)
⑧测试
下一次再看到这段代码时,我就不用再费力搞懂其中到底在干什么。

修改参数列表不仅能增加函数的应用范围,还能改变连接一个模块所需的条件,从而去除不必要的耦合
 
引入参数对象 Import Object Parameter 1 一组参数总在一起出现
2 函数参数过多
①创建一个合适的数据结构(对象或数组)——如果已经有了,可以略过
②使用改变函数声明手法给原函数增加一个参数为我们新的结构
③测试
④旧数据中的参数传到新数据结构(变更调用方)
重复】{
⑤删除一项旧参数,并将之使用替换为新参数结构
⑥测试
}
简化参数  
           
函数组合成类 Combine Functions into Class 1 一组函数(行为)总是围绕一组数据做事情
2 客户端有许多基于基础数据计算派生数据的需求
3 一组函数可以自成一个派系,而放在其他地方总是显得不够完美

4 如果在另一个代码库中使用了该变量,这就是一个"已发布变量"(published variable),此时不能进行这个重构。
①如果这一组数据还未做封装,则使用引入参数对象手法对其封装
②运用封装记录手法将数据记录封装成数据类
③使用搬移函数手法将已有的函数加入类(如果遇到参数为新类的成员,则一并替换为使用新类的成员)
④替换客户端的调用
⑤将处理数据记录的逻辑运用提炼函数手法提炼出来,并转为不可变的计算数据
类能明确地给这些函数提供一个共用的环境,在对象内部调用这些函数可以少传许多参数,从而简化函数调用,并且这样一个对象也可以更方便地传递给系统的其他部分  
函数组合成变换 Combine Functions into Transform 1 函数组合成变换手法时机等同于组合成类的手法,区别在于其他地方是否需要对源数据做更新操作。
5 在软件中,经常需要把数据"喂"给一个程序,让它再计算出各种派生信息,这些派生数值可能会在几个不同地方用到(重复)。
3

4 如果在另一个代码库中使用了该变量,这就是一个"已发布变量"(published variable),此时不能进行这个重构
①声明一个变换函数(工厂函数)
②参数为需要做变换的数据(需要deep clone)
重复】{
③计算逻辑移入变换函数内(比较复杂的可以使用提炼函数手法做个过渡)
④测试
}
有了变换函数,我就始终只需要到变换函数中去检查计算派生数据的逻辑,避免到处重复  
           
拆分循环 Split Loop 1 一个循环做了多件不相干事
2 不为别的,就因为这样可以只循环一次。
3 不用担心多循环一次带来的性能问题
4
①复制循环
②如果有副作用则删除单个循环内的重复片段
提炼函数
④优化内部
确保每次修改时你只需要理解要修改的那块代码的行为  
以管道替代循环 Replace loop with pipe 1 一组虽然在做相同事情的循环,但是内部过多的处理逻辑,使其晦涩难懂
2 可以使用array_map、array_filter、collect等函数简化的场景
3不合适的管道(如过滤使用some)
①创建一个新变量,用来存放每次行为处理后,参与循环的剩余集合
②选用合适的管道,将每一次循环的行为进行搬移
③搬移完所有的循环行为,删除整个循环
简化代码  
将查询函数和修改函数分离 Separate Query from Modifier 1 一个函数既有返回值又有设置值
2 某个函数既做查询,又做更新。
建立不同的函数,按职责分离代码,注意逻辑顺序

①复制一份目标函数并改名为查找函数的名字
②将被复制的函数删除设置值的代码
③将调用者替换为新函数,并在下面调用原函数
④删除原函数返回值
⑤将原函数和新函数中的相同代码进行优化
单一职责  
           
移动语句 AA 移动语句一般用于整合相关逻辑代码到一处,这是其他部分手法的基础
2 <代码相关逻辑整合一处方便我们对这部分代码优化和重构
①确定要移动的语句要移动到哪(调整的目标是什么、该目标能否达到)
②确定要移动的语句是否搬移后会使得代码不能正常工作,如果是,则放弃
便于封装  
移除死代码 AA 1 代码随着迭代已经变得没用了。
2 即使这段代码将来很有可能还会使用,那也应该移除,毕竟现在版本控制很实用。
①如果不可以外部引用,则放心删除(如果可能将来极有可能会启用,在这里留下一行注释,标示曾经有过这段代码,以及它被删除的那个提交的版本号)
②如果外部引用了,则需要仔细确认还有没有其他调用点(有eslint规则限制的话。其实可以先删了,看有没有报错)
检查引用,减少干扰  
【函数组合成变换】的替代方案是【函数组合成类】,后者的做法是先用源数据创建一个类,再把相关的计算逻辑搬移到类中。

这两个重构手法都很有用,我常会根据代码库中已有的编程风格来选择使用其中哪一个。

不过,两者有一个重要的区别:如果代码中会对源数据做更新,那么使用类要好得多;如果使用变换,派生数据会被存储在新生成的记录中,一旦源数据被修改,我就会遭遇数据不一致。