重构技巧列表
重新组织函数
技巧名称 | 说明 | 动机 | 备注 |
---|---|---|---|
extract method | 将代码放进独立的函数中,并让函数名解释该函数的用途 | 1.如果每个函数的粒度都很小,那么被复用的概率更大;2.高层的函数读起来像一系列注释(注释后的代码更容易提炼);3.函数粒度小也更容易被覆写 | 提炼函数 |
inline method | 函数本体和名称一样通俗易懂,在函数调用点插入函数本体 | 提取函数固然好,但非必要的间接性会增加阅读成本 | 内联函数 |
inline temp | 将所有对某个临时变量的引用替换为对他赋值的查询语句(不适合耗费性能的操作) | 多半可视为replace temp with query 的一部分使用;妨碍其他重构手法,使用内联 | 内联临时变量 |
replace temp with query | 程序以一个临时变量来保存某一表达式的运行结果,将表达式提炼到独立函数中去,将这个临时变量替换为对新函数的调用 | 1.临时变量都是暂时的,所以只能在所属函数内使用;2.因为使用域的限制导致想要再次访问该临时变量则需要重复劳作;3.该技巧可以给 extract method 做准备 | 已查询取代临时变量 |
introduce explaining variable | 将一个复杂表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途 | 1.表达式有可能非常复杂而难以阅读,临时变量可以将表达式分解为比较容易管理的形式;2.在条件语句中,可以用此重构手法将条件子句进行提炼 | 引入解释性变量 |
split temporary variable | 你的程序有某个临时变量被赋值超过一次,它既不是循环变量也不是用于收集统计的,针对每次赋值创建一个独立变量 | 如果一个临时变量被赋值了多次,应该拆分成多个临时变量,确保只有一个单一职责;同一个临时变量承担多个职责会令阅读者糊涂 | 分解临时变量 |
remove assignments to parameters | 代码对一个参数进行赋值,以一个临时变量取代该参数的位置 | 对参数进行赋值,会降低代码的清晰度 | 移除对参数的赋值 |
replace method with mehtod object | 你有一个大型函数,其中对局部变量的使用使你无法采用extract method,将函数移动到一个类中,并将局部变量变成对象内字段,在同一个类中拆解函数 | 针对某个函数中局部变量泛滥成灾的现象 | 以函数对象取代函数 |
substitute algorithm | 将函数本体替换成另一种算法 | 解决问题有好几种方法,解决一个问题有更清晰简单的方式时,替换成新的方法吧 | 替换算法 |
在对象之间搬移特性
技巧名称 | 说明 | 动机 | 备注 |
---|---|---|---|
move method | 有个函数与所驻类之外的另一个类交流更多,在最常引用该函数的类型建立一个行为一样的函数,将旧函数变成一个委托函数 | 重构理论的重要支柱,如果一个类的行为太多或者某个行为和另外的类高度耦合,那么这些行为就要考虑移动了(寻找这样的函数:使用另一个领域比使用所驻类领域对象还要多) | 搬移函数 |
move field | 程序中的某个字段被另一个类使用的次数更加频繁,将该字段搬移到使用次数更频繁的类中 | 随着系统的发展,你会发现自己需要新的类,并将现有的工作责任拖到新的类中 | 搬移字段 |
extract class | 某个类做了两个类应该做的事情,新建一个类将字段和方法移动到新类中 | 一个类应该是清楚的抽象,处理一些明确的责任,随着系统的不断状态,类会变得过于复杂(先考虑职责,在将不同职责的字段和函数提炼到新的类中) | 提炼类 |
inline class | 某个类没有做太多的事情,将这种类所得的特性搬移到另一个类中 | 一个类不在承担足够的责任,不在有独立存在的理由(一开始庞大,可能由于重构导致单薄),将这个类的特性迁移到使用它最多的类中 | 将类内联化 |
hide delegate | 客户通过一个委托类来调用另一个对象 | 调用客户不想因委托的变化而进行修改,在委托类上建立客户所需要的所有行数,客户可通过委托类调用另一个类 | 隐藏委托关系 |
remove middle man | 某个类做了太多的简单委托,让客户直接调用受托类 | 针对 hide delegate 付出的代价,每当客户要使用受托类的新特性,我们不得不在中间服务中增加简单的函数,随着受托类越来越庞大,这一过程痛苦不堪 | 移除中间人 |
introduce local extension | 你需要为服务提供一些额外函数,但是你无法修改这个类 | 继承这个类,并且利用父类的函数,提供额外的功能 | 引入本地扩展 |
重新组织数据
技巧名称 | 说明 | 动机 | 备注 |
---|---|---|---|
self encapsulate field | 即使在类内,为这个字段设置取值/设值方法,并以这些函数访问这些字段 | 截然不同的观点:1.类内可以自由访问,2.即使在类内也应该通过函数访问;先使用观点1,当访问存在装饰时使用观点2 | 自封装字段 |
replace data value with object | 你有一个数据项,需要与其他数据和行为一起使用才有意义,将数据项变成对象 | 如果某个字段出现了特殊的行为,短时间可以分散在各个类中,但是坏味道从此开始,你需要将这个字段封装成类,将特殊的行为包装在内 | 以对象取代数据值 |
chanage value to reference | 从一个类衍生出来的各种相等的实例,将他们替换成相同的对象 | 并不好区分,引用对象在系统中就是独一份的 | 将值对象改为引用对象 |
replace array to object | 你有一个数组,其中的元素各自代表不同的东西 | 数组应该只用于以某种顺序容纳一组相似对象 | 以对象替换数组 |
duplicate observed data | 有一些数据置于gui组件中,而领域函数需要访问这些数据,将数据复制一份到领域对象中,使用观察者模式更新这些数据 | 一个良好的系统,需要将界面数据和处理业务的代码分开,但有时候数据无法分割(比如基于swing的界面编程) | 复制被监制的数据 |
replace magic number with sysbolic constant | 魔法值,即有特殊意义,却又不能明确表现出这种意义的数字 | 以字面常量替换魔法值 | |
replace type code with class | 针对类型码数值的传参,编译器看到并进行检查的始终是背后的那个code,大大降低代码的可读性,从而成为bug之源 | 以类取代类型码 | |
replace type code with subclass | 有一个不可变的类型码,它会影响类的行为,以子类取代这个类型码 | 如果类型码不会影响行为,用上面方法(replace type code with class)即可;如果类型码会影响行为,最好是借助多态来处理变化行为 | 以子类取代类型码 |
replace type code with state/strategy | 有一个影响类行为的类型码,在对象的生命周期中发生变化,无法通过继承去消除它 | 本重构和replace type code with subclass相似,但是类型码在对象生命周期中变化/宿主类无法被继承,可以使用该重构 | 以状态模式/策略模式替换类型代码 |
replace subclass with field | 各个子类的唯一差别只在 返回常量函数 的差别上,在超类中增加一个相应的字段,然后销毁子类 | 增加子类的目的是为了增加新的特性/改变其行为,如果子类中只有常量函数,那么考虑去除这样的子类避免因继承带来的额外复杂性 | 以字段取代子类 |
简化条件表达式
技巧名称 | 说明 | 动机 | 备注 |
---|---|---|---|
decompose conditional | 你有一个复杂的条件语句,从段落中分别提炼出独立的函数 | 1.复杂的条件语句是导致复杂度上升的原因之一;2.针对不同的条件分支,我们可以写测试来执行不同的分支行为,但是不明白为什么这样执行;3.分解出独立的函数,并根据分支行为给函数命名,这样可以大大提升可读性 | 分解条件表达式 |
consolidate conditional expression | 你有一系列条件表达式,都等到相同的结果 | 检查条件各不相同,但最终行为却一致,合并成一个条件并提炼成函数表达式 | 合并条件表达式 |
consolidate duplicate conditional fragments | 在各条件分支中有着相同的一段代码 | 一组条件表达式的所有分支上都执行了同一段代码,将这段代码搬移到条件表达式外面 | 合并重复的条件片段 |
remove control flag | 在一系列布尔表达式中,某个变量带有控制标记的作用(这个变量可以结束某个流程) | java中可以使用continue/break语句替换 | 移除控制标记 |
replace nested conditional with guard clauses | 函数中的条件逻辑使人难以看清正常的执行路径(条件语句嵌套) | 条件表达有两种形式:1.条件语句所有分支都属于正常行为;2.条件中只有一种情况是正常行为,其他情况不常见;其中后者可以使用该重构 | 使用卫语句处理特殊情况 |
replace conditional with polymorphism | 有个条件表达式,根据对象类型的不同而选择不同的行为 | 多态的存在可以使你不必编写明显的条件语句(但是继承也会增加复杂性,要预测行为是否经常增加减少来判断是否真的适合多态来替换) | 以多态取代条件表达式 |
introduce assertion | 某一段代码需要对程序状态作出某种假设 | 当条件为真时某段代码才能正常运行,引入断言来排除不正常的情况(先行判断不正常情况发生时是否确实当作异常处理) | 引入断言 |
简化函数调用
技巧名称 | 说明 | 动机 | 备注 |
---|---|---|---|
rename method | 函数的名称未能揭示函数的用途 | 极力提倡的一种编程风格,将复杂的处理过程分解成小函数,但是如果处理不好使你费尽周折却弄不清楚这些小函数各自的职责,所以给函数一个通俗易懂的名字 | 函数改名 |
add parameter | 某个函数的改动需要更多的信息 | 不多说 | 添加参数 |
remove parameter | 某个函数已经不再使用某个参数 | 程序员不太愿意删除无用参数。千万不要打这样的如意算盘:多余的参数在程序中不会引发任何的问题,未来的时间可能还用得上 | 移除参数 |
separate query from modifier | 某个函数既返回了对象状态值又修改了对象状态值 | 如果一个函数没有任何副作用,即只有单一职责的接口(对于某些业务查询和更新行为确实会存在并存),我们可以任意调用它而不用操心太多地方 | 分离查询函数和修改函数 |
paramaterize method | 若干函数做了类似的工作 | 你可能会发现这样的函数:有着类似的行为,但是因为某些值导致行为些许的不同,将这些值作为参数传入合并这些行为类似的函数 | 令函数携带参数 |
replace paramater with explicit methods | 有一个函数,行为完全取决与传入的参数 | 针对不同的分支行为建立不同的函数,调用方不用再考虑传入赋予参数什么值调用(与paramaterize method 恰恰相反) | 以明确函数取代参数 |
preserve whole object | 你从某个对象中取出若干值作为某个函数的参数 | 改用传整个对象,1.减少参数列表,长的参数列表也是降低代码可读性的祸首;2.函数需要多余信息时参数列表可以不用跟着变更; | 保持对象完整 |
replace paramater with methods | 对象调用某个函数,并将获得结果作为函数的参数 | 如果获取到的结果只是服务于该函数(后续程序中并没有使用到),那么去掉参数,函数内部直接调用前一个函数 | 以函数取代参数 |
introduce paramater object | 某些参数总是自然的同时出现 | 如果某些参数总是出现的频率非常高,那么他们有可能是属于同一个类属性的,用对象包装它们 | 引入参数对象 |
hide methods | 如果一个函数从来没有被其他类调用过 | 函数的可见度应该符合其作用域范围 | 隐藏函数 |
处理概括关系
技巧名称 | 说明 | 动机 | 备注 |
---|---|---|---|
pull up field | 两个子类拥有相同的字段 | 如果子类是分别开发的,或者在重构过程中组合起来的,可能存在相同的字段;1.字段名字相同,这种很好区分,2.字段的使用方式相同,这种需要观察字段在函数中的作用。既能够减少重复的字段,还可以将重复的行为提到超类中 | 字段上移 |
pull up method | 有些函数在各个子类中产生相同的结果 | 避免重复行为是很重要的,虽然重复的行为也能照常运行,但是你就会面临修改了一个但是遗漏了另一个的风险 | 函数上移 |
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 | 和上述重构手法相反 | 一般遇到某个类使用了受托类的所有函数,并且花了很大的力气编写了极其简单的委托函数 | 以继承替代委托 |
大型重构
技巧名称 | 说明 | 动机 | 备注 |
---|---|---|---|
tease apert inheritance | 少用继承,多用委托 | 庞大复杂的继承体系要小心(java 7 由于 Collections 复杂的继承体系,导致无法新增加特性,只能使用 default 关键字) | 梳理并分解继承体系 |
converter procedural design to object | – | – | 将过程化设计转为对象设计 |
separate domain from presentation | mvc模式就是很好的例子,jsp就是反例 | – | 将领域对象和表述/显示分离 |