重构:
【名词】对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
【动词】使用一系列重构手法,在不改变软件观察行为的前提下,调整其结构
【意义】重构使软件更容易理解,填补“想要他做什么”,和“准确说出我所要的”之间的间隙
【范围】在不同的领域中有着不同的重构手法,例如多线程环境和单线程环境,函数式编程和命令式编程语言等,更多要求的是你自己本身具有一定的创造力,发现适合你的重构技巧。
理念篇:
代码是随着需求的易变性,不同的抽象的模型,从而具有变化性和自生长性的,把软件工程师比喻为城市规划师,对软件的规划就要有了,那么对软件的演进也是要有;
软件系统管理的最佳手段,必然是重构了,我们知道治理代码的时候,一般想到的是坏味道代码的治理,也就会用到重构手段去把坏味道代码改为好味道代码;但其实对于变化来说,意味着好的代码也需要重构,只是好的代码会更容易重构;
- 如果你想给程序添加一个特性但程序因没有良好的结构而难以进行,那么先重构程序使得其容易添加特性然后再添加特性;
- 重构前先保证是否有一套测试框架,这些框架要有一定的自己检测能力;
- 重构技术是以微小的步伐修改程序,如果你发现错误可以很容易发现它;
- 重构要经常切换两顶帽子,尽量不要同时进行,要么加新特性,要么调整结构;
- 当你感觉需要撰写注释的时候,请先尝试重构,试着让所有注释都变得多余;
重构时机:
什么时候应该重构呢,当闻到代码“坏味道”的时候,以下是味道:
- 神秘命名;
- 重复代码;
- 过长函数;
- 过长参数列表;
- 过长的类
- 全局数据:可以通过建立一个类,然后把行为搬迁过去;
- 可变数据;
- 发散式变化:不同的变化都在改同一个地方;
- 霰弹时修改:同一个变化修改不提的地方;
- 依恋情结:某函数和外部的依赖远多于在内部的依赖,那么把这份依恋转移过去;
- 数据泥团:请把相关数据集合在一起;
- 重复的switch;
- 沉赘的元素:多此一举的类啊,这样;
- 过度设计;
- 被拒绝的遗赠:子类继承了多余的东西,可以尝试弄个兄弟类类装;
重构技术篇:
Extract Method
- 定义:有一段代码可以组织在一起被单独提取出来;
- 意义:什么时候才要提炼函数呢?将意图和实现分离,这点也是用意图取代注释的体验
- 名称:很多小型函数有很多好处,而且只有在你为小型函数真正命好名的时候才会凸显其作用,如果你想不出一个好名称,那就不要提
- 长度:函数长度其实不是问题,问题在于函数名称和函数语义之间的距离;既然函数名称比函数体还长也没关系。另外,如果函数体本身清晰易读,就没有必要提取了,关键在于意图;
- 难点:局部变量。
Inline Method
- 通常没必要的间接层只会是累赘;
- 如果小函数划分混乱的时候,也可以应该方法先合并为大函数,再拆分小函数;
Replace Temp With Query ,临时变量坏处多,用查询改变临时变量是好的,可能会增加性能开销,但不要担心,优化的时候才是你需要担心的,阻塞才是要害怕的;
- 定义:将一个表达式提炼到一个独立的函数中,将这个临时变量的所有引用点替换为对新函数的调用,此后,新函数就可被其他函数使用。
- 查询:查询就是赋值给临时变量的那个表达式,该表达式可以利用Extract Method被提炼出一个函数;
- 问题:临时变量的问题在于,他们只是暂时的,而且只能在所属函数中使用,它会驱使你写长函数,而且让代码不清晰,不整洁。
Extract Variable
Inline Variable
Encapsulate Variable 封装变量:把变量或者变量集合的访问封装到函数中,控制访问,也可以用作发挥重构调整 数据元素
Introduce Parameter Object
Combine Function into Class
- 几个函数一直在操作相同的数据,那么可以把这些函数和数据组合成类
- 类无需做参数传递,而是操作对象内部属性,封装性高
- 如果操作的参数多,可以先做引入参数记录手法
Combine Function into Transform
Encapsulate Record
Encapsulate Collection
Replace Primite with Object
Extract Class
Inline Class
Hide Delegate
Remove Middle Man
Split Loop
Split Variable
Replace Nested Conditional with Guard Clauses
重构前:
function getPayAmount(){
let result;
if(isDead)
resule = deadAmount();
else{
if(isSepareted)
result=separetedAmount();
else{
if(isRetired)
result = retiredAmout();
else{
result = normalPayAmount();
}
}
}
return result;
}
重构后:
function getPayAmount(){
if (isDead) return deadAmount();
if (isSepareted) return separeteAmount();
if (isRetired) return retiredAmount();
return normalPayAmount();
}
Replace Type Code with State/Strategy
- 如何去掉switch语句,switch一般是根据状态而选择行为,有必要的时候,去掉它,状态机中就是如此,但比较简单的可以暂时先不急着下手;其实就是用好状态模式提供的多态能力;
Introduce Explaining Variable
Split Temporary Variable,分解临时变量,临时变量有各种不同用途,其中某些用途会很自然导致临时变量被多次复赋值,如“循环变量”、“结果收集变量”就是两个例子;分解临时变量就是要注意,一个变量不应该承担一个以上的责任。
Remove Assignments to Parameters,移除对参数的赋值;一般参数职责为:被传入的某些东西;
Separate Query From Modifier
Remove Flag Argument
- 这其实也是减少参数的方式之一,
核心手法:以多态取代条件表达式
Replace Candition with Polymorphism ,复杂的条件表达式是编程中最难理解的东西之一,很多时候他们可以拆分为不同的场景(或者叫高阶用例),从而拆分复杂的逻辑条件;使用类和多态可以使得拆分更清晰;
做法
- 如果现有的类不具有多态行为,先用工厂函数创建之,另工厂函数返回适当的对象实例
- 在调用方法代码中使用工厂函数获取对象实例
- 将带有逻辑表示分支的代码转移到超类中
- 如果条件表达式还没提炼至独立函数,就对其使用提炼函数,使得分段
- 任意选一个子类,在其中建立一个函数,使之覆盖超类中容纳条件表达式的那个函数;将与该子类相关的条件表达式分支复制到新函数中
- 重复以上过程知道分支处理完毕
- 在超类中保持默认的逻辑,或者声明为Abstrace
核心手法:引入特例对象
这个手法出自引入null对象,一种常见的重复代码是这样的:一个数据结构的使用者,都在检查某个特殊的值,并且当这个值出现时所作的处理也都相同,那么就可以把他们收拢在一起。
- 其中一个好的处理方式,就是引入特例对象
- 第二就是把数据结构封装为一个对象
可以有用的关联重构手法有:《提取函数》《函数组合成类》,或者《函数组合成变换》。
- 提取函数:在散落各处的检查特例的代码,都提取为函数。
核心精华:重构就是调整元素
重构的作用,就是调整程序中的元素,函数的调整相对容易一些,因为函数只有一种用法,那就是调用,在改名或者搬移函数的过程中,总是可以比较容易地保存旧函数作为转发函数,从而简化重构过程,但调整程序数据就要麻烦得多,因为没有办法设计这种转发机制,如果我把数据搬走,我就必须修改所有对它的引用,这也是为啥全局变量是大麻烦的原因;
重新组织数据转化为重新组织函数了;
核心精华:模块化、上下文环境与元素重整
(摘-重构2-198页)任何函数都必须具备上下文环境才能存活,该环境可能是全局的,但它更可能是某种模块所提供;对于面向对象的程序设计语言,类是最主要的模块化手段;而对于函数式语言而言,通过函数嵌套,外层函数也能是内层函数的上下文环境;不同语言的模块化机制不同,但他们的共同点都是为函数提供了一个可以赖以存活的上下文环境;例如Java的包访问权限,其实也是证明了包,其实也是一个上下文环境;
模块化是优秀软件设计的核心所在,模块不仅仅指的是包、还可以是文件、类、作用域、函数等,好的模块化能够让我在修改程序时只需理解程序的一小部分,为了设计出高度模块化的程序,我得保证互相关联的软件要素都能集中到一起,并确保块与块之间的联系易于查找、直观易懂。同时,我对于模块设计的理解并不是一成不变的,随着我对问题的理解和加深,我会知道哪些软件要素如何组织最为恰当。要将这些理解反映到代码上,就得不断地搬迁这些元素;而重构,就是调整元素;
-------------------------------- 优秀、是一种习惯 、、、、、、、、、、、、、、、