重构
概念:在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构。
重构技术就是以微小的步伐修改程序。如果你犯下错误,很容易便可发现它。
第一章 重构,第一个案列
- 何时重构:如果你发现自己需要为程序添加一个特性,而代码结构使你无法和方便的达成目的,那就先重构那个程序,使特性的添加比较容易进行,然后再添加特性。
- 重构的步骤
- 第一个步骤永远相同:我得为即将修改的代码建立一组可靠的测试环境,这些测试必须有自我检验能力
- 分解并重组
- 找出函数内的局部变量和参数,任何不会被修改的变量都可以被当成参数传入新的函数,如果只有一个变量会被修改,可以将他作为返回值。
- 去除临时变量
- 运用多态和继承
第二章 重构原则
- 概念
- 名词概念:对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
- 动词概念:使用一系列重构手法,在不改变软件可观察行为的前提下,改变其结构。
- 重构的目的
- 改进软件设计:消除重复代码
- 使软件更容易理解:利用重构来协助理解不熟悉的代码,然后理解其用途,然后进行修改,使其能更好的反映出我的理解
- 帮助找Bug:对代码进行重构,深入理解代码行为,做出假设。
- 提高编程速度:良好的设计是快速开发的根本
- 什么时候重构
- 三次法则:事不过三,三则重构(消除重复代码)
- 添加新功能时:代码的设计无法帮助我轻松添加我所需要的新特性
- 修补错误时
- 复审代码时
- 结论:我们希望程序长这样
- 容易阅读
- 所有逻辑都只在唯一地点指定
- 新的改动不会危及现有行为
- 尽可能简单表达条件逻辑
- 重构的难题:在重构时,应该实时掌握其过程,注意寻找可能引入的问题
- 接口的修改:让旧接口调用新接口,留下旧函数,调用新函数。旧的接口或者函数可以使用Java自带的 @Deprecation注解。所以不要过早的发布接口,请修改你的代码所有权政策,使重构更顺畅
- 不应该重构:当代码实在太混乱的时候,可能重写比重构更有效,此时应该放弃重构
第三章 代码坏味道
- 重复的代码:如果在一个以上的地点看到相同的程序结构,那么可以肯定,设法将他们合二为一,使程序变得更好
- 同一个类的两个函数含有相同的表达式
- 两个互为兄弟的子类含有相同的表达式,应该将其抽取并放到父类中
- 如果两个毫不相关的类出现同样的代码,可以将重复代码提炼到一个独立的类中
- 函数体过长
- 类过大
- 参数列表过长:封装成对象来进行传递
- 异曲同工的类:应该重新思考其用途并重新命名。
- 过多的注释:当感觉需要些注释的时候,先尝试重构,让所有的注释都变得多余,尽量运用重构手法让我们不写注释。
第四章 构筑测试体系
- 测试的最终目的:确保所有的测试都完全自动化,让他们自己检查测试结果,一套测试就是一个强大的Bug侦测器,它能够极大的缩减找Bug的时间。
- Junit测试:主要用来编写单元测试
- 频繁的运行测试,每次编译请把测试也考虑进去,每天至少执行每个测试一次
- 每当收到一个Bug报告,应当先写一个单元测试来暴露Bug
- 单元测试和功能测试
- 单元测试:编写单元测试的目的是为了提高生产效率
- 功能测试:用于保证软件能正常运作。
- 测试应该放在你最担心出错的部分,这样才能从中得到最大的利益
- 考虑可能出错的边界条件,把测试火力集中在此
- 总结:请构筑一个良好的Bug检测器并经常运行它,这对任何开发工作都大有裨益,并且是重构的前提
第五章 重构列表
- 重构的记录格式
- 名称:建造一个重构词汇表
- 概要:简短的概要
- 用一句话介绍这个重构能帮我们解决的问题
- 一段简短陈述,介绍该做的事
- 一副速写图,展现重构前后实例
- 动机:告诉你“为啥子需要这个重构”,还有什么情况下“不应该使用这个重构”
- 做法:介绍如何一步一步的进行重构
- 范例:用一个简单的例子说明该重构
- 重构的基本技巧:小步前进,频繁测试
第六章 重新组织函数
总体:重构手法
- Extract Method(提炼函数):把一段代码从原先的函数中提取出来,放到一个单独的函数中
- 做法:
- 创造一个新函数,根据意图来命名(以它“做什么”来命名)
- 将被提炼代码中需要读取的局部变量,作为参数来传递
- 做法:
- Inline Method(内联函数):将一个函数调用的动作替换为函数本体
- 做法:
- 检查函数,确定它不具有多态性(如果有子类来继承该函数,是不能做内联的)
- 找出该函数的所有被调用点
- 将这个函数的所有调用点都替换为函数的本体
- 做法:
- Inline Temp(内联临时变量):有一个临时变量,只被一个简单的表达式赋值了一次
- 检查给临时变量赋值的语句,确保等号右边的表达式没有副作用
- 如果该变量没有被声明为final类型,那么可以声明为final,然后编译,这样的目的是确保该变量是否真的被只赋值了一次
- 找到该变量的引用点,替换为临时变量的赋值表达式
- Replace Temp with Query(以查询取代临时变量):以临时变量保存表达式的结果
- 做法:
- 找出只被赋值一次的变量
- 声明为final类型
- 将该临时变量右边的表达式提炼到一个函数中去
- 做法:
- Introduce Explain Variable(引入解释性变量):将复杂的表达式的结果放入一个临时变量来解释其用途
- 做法:
- 声明一个final类型的临时变量,将待分解的复杂表达式中的一部分运算结果赋值给它
- 将表达式中的“运算结果”这一部分,替换为上述临时变量
- 做法:
- Split Temporary Variable(分解临时变量):针对每次赋值,创造一个独立,对应的临时变量
- 做法:如果一个变量被赋值超过一次,意味着在函数中承担了一个以上的责任,那么就应该分解临时变量
- 在待分解临时变量的声明及第一次赋值处,修改其名称
- 为新的变量声明为final
- 以该临时变量的第二次赋值处为界,修改其所有的名称为新定义的变量
- 做法:如果一个变量被赋值超过一次,意味着在函数中承担了一个以上的责任,那么就应该分解临时变量
- Remove Assignments to Parameters(移除对参数的赋值)
- 做法:
- 建立一个临时变量,把待处理的参数值赋予它
- 以对参数的赋值为界,将其后所有对此参数的引用点,全部替换为对临时变量的引用
- 修改赋值语句,使其改为对新建的临时变量赋值
- 做法:
- Replace Method with Method Object(以函数对象取代函数):将这个函数单独放到一个对象中,如此一来局部变量就成了成员变量,然后将这个大型函数分解为多个小型函数
- 做法:
- 建立新类,根据待处理函数的用途来命名
- 在新类中建立一个原对象的final变量,用来保存原先大型函数所在的对象
- 建立大型函数中每个临时变量作为成员变量
- 做法:
- Substitute Algorithm(替换算法)
- 做法:
- 准备好另一个算法,并使之通过编译
- 针对现有测试,执行上述算法。如果结果与原本结果相同,重构结束
- 如果不同,那么以旧算法为参考
- 做法:
第七章 在对象之间搬移特性
概念:在对象的设计过程中,“决定把责任放在哪儿”即使不是最重要的事情,也是最重要的事情之一”
- Move Method(搬移函数):在程序中,有个函数与其所驻类之外的类有更多的交流:调用后者,或者被后者调用。
- 做法:在该函数最常引用的类中建立一个有着类似行为的函数。将旧函数变为单纯的委托函数,或是将就函数移除
- 检查源类中被源函数所使用的一切特性(包括字段和函数),考虑是否应该被搬移
- 检查源类中子类和超类是否有该函数的声明(多态性)。
- 在目标类中建立新函数,被搬移源函数的函数体
- 修改源函数,使之成为一个纯委托函数
- Move Filed(搬移字段):在程序中,某个字段被其所驻类之外的另一个类用得更多。
- 做法:在目标类新建一个字段,修改源字段的所有用户,令他们改用新字段
- 如果该字段是public的,应该先进行封装起来
- 在目标类中建立相同的字段,并提供set/get方法
- 删除源中的字段
- Extract Class(提炼类):某个类做了应该由两个或多个类做的事情
- 做法:建立一个新类,将相关的字段和函数提炼到新的类中
- 决定如何分解类的责任
- 建立一个新类,用于表现出旧类中分解出来的责任
- 建立两个类之间的访问连接关系
- Inline Class(将类内联化):类没有做太多事情。
- 做法:将这个类中的所有特性搬移到另一个类中,然后移除源类
- 在目标类上什么源类的协议,将其中的所有函数委托至源类
- 修改所有源类的引用点,改为目标类的引用
- 运用Move Filed和Move Method将源类的变量和函数移动到目标类中
- Hide Delegate(隐藏“委托关系”):通过一个委托类来调用另一个对象
- 做法:在服务的类上建立所需的所有函数,用以隐藏委托关系
- 对于每一个委托关系中的函数,在服务对象端建立一个简单的委托函数
- 修改为调用服务对象提供的函数
网友评论