引言
  Martin Fowler的《重构:改善既有代码的设计》一书从2003年问世至今已有十几年时间了,按照计算机领域日新月异的变化速度,重构已经算是一门陈旧的技术了。但是陈旧并不代表不重要,恰恰随着演进式设计被越来越广泛的使用,重构技术已经被认为是现代软件开发中的一项必备的基本技能!所以在任何软件开发团队中,你都会不时听到或看到和重构相关的代码活动。然而对于这样一种被认为应该是如同“软件开发中的空气和水”一样的技术,在现实中却比比皆见对重构的错误理解和应用。首先是不知道重构使用的正确场合,总是等到代码已经腐化到积重难返的时候才想起重构;其次面对一堆的代码坏味道没有选择标准、无从下手;接下来修改代码的过程中不懂得安全、小步的重构手法,总是大刀阔斧地将代码置于危险的境地,很难再收回来;后要么构建、测试失败后无法恢复只能推到重来,或者终结果只是将代码从一种坏味道修改到了另一种坏味道!
  总结以上问题,一部分原因是因为没有正确的理解重构,不知道重构的起点和目标,对重构的对象和目标没有衡量和比较的标准;其次是因为没有掌握形式化的重构手法和步骤,重构过程往往只是跟着感觉走;后实践重构的过程中,没有先理顺自己的开发、构建和测试环境,导致重构成本很高! 对于开发、构建和测试环境的问题,C/C++领域尤其严重,除了没有像Java领域那么好用的自动化重构工具,很多开发人员连一个好用的IDE都找不到,更不要说普遍认知的构建速度慢,自动化测试匮乏等等问题!
  本文站在作者学习和实践重构的基础上,为大家梳理重构技术,带领大家重新认识重构的目标和起点,重构手法背后的原理以及实践方式。后介绍在实践中高效实施C/C++重构的经验、技巧和工具。
  什么是重构?
  重构的定义
  Martin Fowler在《重构:改善既有代码的设计》一书中给出了重构的两个定义.
  第一个是名词形式:
  Refactoring: 对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本.
  第二个是动词形式:
  Refactor: 使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构.
  重构的目标
  重构的目标是什么? 重构的目标绝不是将代码从别人的taste改成自己的taste,也不是将代码从一种坏味道改到另一种坏味道!
  Matin Fowler利用上面两个定义,指出了重构的目标:
  · 不改变软件可观察行为
  · 提高软件可理解性
  · 降低软件修改成本
  而对于上述目标,我们再深入一点分析,发现其实已经有更经典的定义. 那是Kent Beck的简单设计四原则:
  · Pass All Test: 通过全部测试;
  · No Duplication: 没有重复(DRY)
  · Reveals Intent: 程序表达意图,易于理解
  · Has no superfluous parts: 没有冗余,或者YAGNI原则
  上述四条的重要程度依次降低.
  到目前为止,简单设计四原则是对”什么是好的软件设计”好的定义!
  简单设计四原则第一条定义好的软件首先应该通过所有测试,即正确满足所有功能需求.而重构的目标中基本的是”不改变软件的可观察行为”,也是说:
  1) 重构后的软件不能破坏原来所有测试!
  Matin定义的重构的其它两条目标,对应了简单设计原则的第2和第3条:
  2) 重构应该消除重复: 降低软件修改成本;
  3) 重构应该让程序显示表达意图: 提高软件可理解性;
  后,我们把简单设计四原则的后一条也加入重构的目标:
  4) 重构应该消除冗余:降低软件不必要的复杂度.
  所以以后当我们再来讨论重构的目标,或者评判重构有没有收益的时候,用简单设计四原则来衡量它.
  从哪里开始?
  对于重构的目标达成一致后,我们回到起点:什么样的软件需要重构? 以及什么时候进行重构?
  对于第一个问题,由于我们重构的目标是使软件满足简单设计四原则,那么任何违反简单设计四原则的代码都应该是我们重构的目标.例如1)代码很容易出现bug,导致测试失败! 或者 2)代码存在知识重复使得不易修改! 或者 3)代码写的晦涩非常难以理解! 或者 4)代码存在过度设计,存在冗余导致复杂!
  现实中可能有一堆的代码问题等待我们解决,而时间、成本、人力是有限的,所以我们需要从有价值,没有争议的部分开始重构. 由于简单设计四原则的重要程度是依次降低的,对于四条原则的判定从上往下也是逐渐主观化,所以我们选择重构的代码的优先级顺序也是按照它们破坏简单四原则的顺序依次降低! 如果一坨代码存在很多重复,另外一坨代码不易理解,那么我们优先选择去解决重复代码的问题,因为按照简单四原则消除重复更重要,也更容易被客观评价.
  在《重构》一书中Martin为了避免引起所谓编程美学的含混争辩,总结了代码的22条坏味道. 在实践中我们一般都是从某一代码坏味道着手重构的,但是对于优先重构哪个坏味道,我们遵守上面描述的原则.
  对于进行重构的时机,Matin给出:
  · 重复地做某一件事情的时候 (三次法则)
  · 添加新功能的时候
  · 修改Bug的时候
  · Code Review的时候
  事实上在我的工作过程中,重构是随时随地进行的. 尤其对于采用演进式设计方法论,重构和代码开发是紧密结合难以分割的,甚至很多时候只有依托重构才能完成代码的开发.
  重构的手法
  明白了起点和目标,下来重要的是掌握完成这一过程的手段! 而重构的手法则是带领我们正确到达目标的工具.
  很多人认为学习重构只要掌握背后的思想足够了,其详细繁琐的操作手法并不重要.于是乎现实中我们看到很多人在实际操作重构的过程中章法全无,一旦开始半天停不下来,代码很多时候处于不可编译或者测试不能通过的状态,有时改的出错了很难再使代码回到初始状态,只能推倒重来! 实际上重构是一项非常实践性的技术,能够正确合理地使用重构操作,安全地,小步地,高效地完成代码修改,是评价重构能力的核心标准.
  那么什么才是正确的重构手法?
  Martin对重构的第二个定义中提到使用一系列的重构手法,但是对这一系列的重构手法却没有概括.
  而William Opdyke在他的论文”Refactoring Objected-Oriented Frameworks”里面对重构给出了如下定义:
  重构:行为保持(Behavior Preservation)的程序重建和程序变换.
  在论文里面将重构手法定义为一些程序重建或者程序变换的操作,这些操作满足行为保持(Behavior Preservation)的要求. 论文里面对行为保持的定义如下:
  Behavior Preservation : For the same set of input values,the resulting set of output values should be the same before and after the refactoring.
  也是说存在一系列代码变换的操作,应用这些操作之后,在相同的输入条件下,软件的输出不会发生变化. 我们把满足上述要求的代码操作称之为代码等价变换操作. 在William Opdyke的论文中针对C++提出了26种低层次的代码等价变换操作(例如: 重命名变量,为函数增加一个参数,删除一个不被引用的类…). 按照一定设计好的顺序组合上述低层次的代码等价变换操作,我们可以完成一次安全的代码重构,保证代码重构前后的行为保持要求.
  这里代码等价变换的过程. 类似于初等数学中的多项式变换.例如对于如下公式变化:

  每一步我们运用一次多项式等价变换公式,一步一步地对多项式进行化简,每次变换前后多项式保持等价关系.