《重构改善既有代码的设计》 ,作者是Martin Fowler,被软件开发者称为“教父”,其建立的敏捷开发彻底改变了人类开发软件的模式,从传统的以文档为驱动的、笨重的软件开发模式转化为以核心需求为中心,”可以让汽车一边跑,一边换轮子“的敏捷开发模式,《重构》也被称为是软件开发的不朽经典,被誉为金字塔顶端的书,在分析重构原理和具体实践方式的同时,向程序员提供了一种优秀的编程习惯和编程态度。
1、什么是重构---what?
-
重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
-
重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
一个任何一个傻瓜都能写出计算机可以理解的代码。唯有写出人类容易理解的代码,才是优秀的程序员。重构,不仅仅是整理代码,更像是“擦掉窗户上的污垢,使你看得更远“。
2、为什么要重构---why?
-
无法快速承接新的需求,在全新系统做两周的需求,在现有系统中需要1个月或者持平。
-
代码中存在太多的”坏味道“,牵一发而动全身,只要发布新功能上线就会出问题。
-
重构的意义在于将眼光放长远,而不仅限于当前暂时的提高开发效率。
-
重构改进软件设计,使代码具有更强的可读性(一段时间后,自己可能会忘了自己之前写代码的逻辑,或是不久之后有其他程序员需要修改你的代码)
-
提高代码的可扩展性(添加新功能,可以尽可能的不修改原有的代码,提高编程速度);
-
降低复杂性(过于冗余的代码,很容易让人看的头昏脑涨);
3、如何进行重构---how?
-
重构的基础---->可靠的测试环境,“小步快跑,快速验证”
-
重构的分类:
1)重新组织函数:
很多时刻,代码中的”坏味道“来源于过长的类,包含太多信息,又被函数错综复杂的逻辑掩盖,不一鉴别。
分类
方法
使用说明
重新组织函数
提炼函数:将一段代码放进一个独立的函数中,并用函数名称解释该函数的用途
处理临时变量:
1、消灭临时变量:分解临时变量(针对每次赋值,创造一个独立的临时变量)或者以查询取代临时变量(如果临时变量保存某一表达式的结果,将表达式提炼到独立函数中);
2、临时变量难以替换:以函数对象取代函数(将函数放进单独的对象,在对象中分解小的函数)
内联函数:函数的本体和名称同样清楚,在函数调用点插入函数本体,然后移除该函数
对于递归调用等复杂情况,不建议使用该重构
以函数对象取代函数(将函数放进一个单独的对象中,然后可以在同一个对象中将这个大型函数分解为多个小型函数)
如果临时变量只被赋值一次,可先使用内联临时变量;
如果临时变量被赋值超过一次,先使用分解临时变量;
替换算法:将函数本体替换为另一个算法
2)在对象之间搬移特性:
”决定把责任放在哪儿?“是对象设计过程中很重要的一件事。
分类
方法
使用说明
在对象之间搬移特性
提炼类:某个类做了两个类做的事情,建立一个新类,将相关字段和函数从旧类搬移到新类
对于每一个字段:搬移字段
对于必要的函数:搬移函数
将类内联化:某个类没有做太多事情
隐藏委托关系:客户通过一个委托类来调用另一个对象,在服务类上建立客户所需的所有函数,用以隐藏委托关系
与此相反,如果某个类做了太多的简单委托动作,需要让客户直接调用受委托类:移除中间人
3)重新组织数据:
处理数据的重构手法
分类
方法
使用说明
重新组织数据
针对简单数据类型:
以对象取代数据值(将数据项变成对象)
将值对象改为引用对象(从一个类中衍生出许多彼此相等的实例,替换为同一个对象)将引用对象改为值对象(引用对象很小且不变,不易管理的情况下,变为一个值对象)
使用过程中,常用到自封装字段,为字段建立取值/设值函数,并且只以这些函数来访问字段
针对魔法数:以字面常量取代魔法数
如果魔法数是类型码,使用以类取代类型码
针对封装的数据:
如果一个类公开了任何public的数据,应该使用封装将其包装起来,如果是集合,使用封装集合,如果是一整条记录,以数据类取代记录(类似于PO类)
针对类型码:(与实例所属之类型相关的某些东西)
类型码不影响宿主类的行为,用类取代即可,但如果影响,需要借助以多态取代表达式来处理,在此之前,需要为每一种类型码建立一个子类;
魔法数:拥有特殊意义,却又不能明确表现出这种意义的数字。
-
4)简化条件表达式
分类
方法
使用说明
简化条件表达式
分解条件表达式(从if、else中分别提炼出独立函数)
如果存在嵌套的条件逻辑,可以使用以卫语句取代嵌套条件表表达式(标示出特殊情况后,以break或return语句取代控制标记)
合并条件表达式(将有相同结果的条件表达式合并为一个)
合并后可以使用提炼类来进行进一步重构
以多态取代表达式(将条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数)
使用之前,必须要继承结构,建立继承可以使用以子类取代类型码
卫语句:如果某个条件极为罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回,这样的检查称为”卫语句“。
-
5)简化函数调用---接口
分类
方法
使用说明
简化函数调用
更改函数声明:
过程中可以使用保持对象完整来缩短参数列;
如果不存在该参数,可以引入参数对象;
如果函数的参数来自该函数可获取的一个对象,可以以函数取代参数;
如果函数完全取决于参数值而采取不同行为,针对该参数的每一个可能值建立独立函数;(以明确函数取代参数)
使用令函数携带参数来为数个相似函数添加参数,将它们合并在一起;
构造函数:
在派生子类的过程汇总以工厂函数取代类型码
结构改进:
移除设置函数、隐藏函数(private)
对于既返回对象状态又返回对象状态值的函数分开;
类中某个字段在对象创建时设置,将不再改变,去掉该字段的所有设置函数;
处理异常:
受控异常--->可以建立一个合适的异常;非受控异常--->抛出
6)处理继承关系
分类
方法
使用说明
处理继承关系
移动:
处理继承关系主要是字段及函数在继承体系之中移动,对于构造函数,如果在各子类中的构造函数本体几乎完全一致,可以构造函数本体上移,对于构造函数下移,可以使用以工厂函数取代构造函数
建立新类:
使用过程中会涉及到移动,参见上一栏;如果有一些子类以相同的顺序执行类似的操作,可以塑造模板函数。
修改继承为委托:
以委托取代继承、以继承取代委托
某个子类只是用超类接口中的一部分--->以委托取代继承
需要使用受委托中的所有函数--->以继承取代委托
另外IntelliJ和Eclipse中也有重构菜单栏,里面包含了一些上面常用到的重构手法:
7)大型重构:
分类 | 方法 | 使用说明 |
---|---|---|
处理混乱的继承体系 | 梳理并分解继承体系(Tease Apart Inheritance) | 某个继承体系同时承担两项责任:建立两个继承体系, 并通过委托关系让其中一个可以调用另一个。 |
处理过程式代码 | 将过程化设计转化为对象设计(Convert Procedural Design to Objects) | 有一些传统过程化风格的代码:将数据记录变成对象, 将大块的行为分成小块, 并将行为移入相关对象中. |
处理复杂的类 | 提炼继承体系(Extract Hierarchy) | 某个类做了太多工作, 其中一部分工作是以大量条件表达式完成的:建立继承体系, 以一个子类表示一种特殊情况. |
4、何时何地重构---when and where?
-
”两顶帽子“理论:添加新功能、重构,时刻谨记所要做的事情是什么,不要边写代码边重构,带上写代码的帽子,觉得需要重构了,再带上重构的帽子去做,如此反复。
-
三次法则:事不过三,三则重构。第一次做某件事情的时候只管去做,第二次做类似的事情的时候会产生反感,但无论如何还是可以去做,第三次再做类似的事,就应该重构。
坏味道
常用重构
注意事项
重复代码
1、同一个类两个函数含有相同的表达式--->提炼函数
2、两个互为兄弟的子类含有想同的表达式--->提炼函数,然后函数上移;如果只是相似,并非完全相同,可以先提炼函数,然后可以构造模板函数
3、两个毫不相关的类--->提炼类
过长函数
1、大多数场合--->提炼类
2、如果函数内有大量的参数和临时变量--->以查询取代临时变量消除临时变量;或引入参数对象、保持对象完整简化过长的参数列;或以函数对象取代函数;
3、提炼条件表达式时候--->分解条件表达式
1、遵循原则:每当感觉需要以注释来说明点什么的时候,就需要把说明的东西写到独立函数中,并以用途命名。
2、注释、条件表达式、循环是提炼的信号;
过大的类
提炼类、提炼子类、提炼接口
过长的参数列
-
把握重构节奏:何时开始、何时结束、何时前进、何时等待?
1) 发现痛点果断重构。重构分大小,不要把重构想象成都是庞大、旷日持久的工程而不愿开始,每天甚至每个小时都可以完成一项小重构。
2)”没有任何度量规矩比得上一个坚实广博者的直觉“,《重构》这本书只能帮助我们发现”这里有一个可以用重构解决的问题“,但决定何时开始重构、何时停止重构还需要培养自己的判断力,一个不断积累的过程。
-
代码质量是系统稳定性的根基:
1)意识:团队成员的共同责任,系统代码质量关系到每个人工作成果产出。如果代码质量不高,新功能也很难写出高质量的代码。
2)能力:学习改进系统质量的方案。关键系统质量:可用性、可修改性、性能、安全、可测试性和易用性;学习系统架构设计。明确系统架构目标(高可用SLA、高扩展性AKF)和商业架构目标(低成本、高收益:系统生命周期长。多快好省:缩短上市时间、减少开发时间、重用现有元素)
3) 行动:系统升级不断维护现有系统,根据业务不断修正和发现潜在的问题,保证系统质量,在关键新功能排期的时候,必须为现有系统改造流出必要的时间。