重构(Refactor)这个词语我想在程序员圈子里面属于超高频词汇了,对于任何系统,只要它有问题,许多程序员就会想:“我们得找时间重构它的代码”。仿佛重构是一种魔法,可以解决一切问题,包括我开始也是这么想的。
近期因为工作关系,重构二字可以说是天天都听到了,因此国庆假期抽空通读了知名读物《重构 改善既有代码的设计》,书中对重构的观点和指导方法,着实让我开了眼界,因此写篇笔记来记录一下感想。
重构作为名词时的意思是:在不改变软件可观察行为(也可理解不改变原有功能)的前提下,重构是对程序内部结构的一种调整,这种调整的目的是
提高代码的可理解性和降低修改成本
。
重构作为动词时的意思是:在不改变软件可观察行为的前提下,通过一系列重构手法(如提炼函数,使用多态等),来调整代码结构。
看完定义以后,是不是觉得跟以前自己所理解的“重构”的定义有点似是而非,接下来说下我之前对重构理解的几个误区。
重构=重写
很多人认为一个接口/程序的代码写得不好,我们对它进行了完全的重写,这就叫做重构。实际上这里有两个误会:
1、重构实际上是调整代码的组织结构,来达到两个目的:提升可理解性和降低修改成本。调整结构是指在原代码的基础上做局部的调整,完全重写理论上是抛弃原有代码重新开发,两者是完全不同的概念。
2、重构肯定需要重写部分代码,但是这里的重写应该是按照一系列重构手法(如上文提到的提炼函数,使用多态等,下文不再赘述),来达到提高可理解性和降低修改成本。如果重写时,没有采用重构手法去进行代码结构的设计,那么这次重写实际上并没有达到重构的目标。
重构=提高性能
重构的目标是提升可理解性和降低修改成本,性能方面很可能是反而下降的。举个例子:switch语句改用多态去实现,很可能需要创建更多对象,降低了性能。
但是即便如此,我们也不应该以性能为借口而去放弃重构,否则“软件工程”就无法存在了。实际上,在我们工作中,程序消耗的80%~90%性能都在那10%~20%的代码块中,因此性能优化这件事情应该去找到那10%~20%的代码来着重优化(这时候采用的就不一定是重构手法了),对于剩下的大部分代码,我们则应该尽量提高可理解性和降低修改难度来节省开发成本和提高工程质量。
重构需要专人/专门的时间去做
“这代码太烂了,我们以后重构掉他吧!”
相信类似这句话,大家听过太多了,我自己也想过或者说过好多次,实际上这个误区跟重写是类似的。
由于代码的设计很可能一旦写完,到下一次迭代就开始腐化了(因为我们几乎无法预测之后要加什么功能),如果每次迭代都只是单纯地加代码,而不去调整原有的结构来优化设计,只想着以后再调整,那么假以时日这些代码就会变得难以理解和修改,维护成本水涨船高,程序员苦不堪言。
这里就涉及到了重构的时机问题,书中作者的观点是重构应该是在每次迭代时都进行的,也就是每次加功能时,如果发现原先的设计已经腐化了,就尽快进行调整,以保证代码始终保持合理健康的设计。
当然这里涉及到了一个问题,就是调整结构不可避免地会动到以前实现完成的功能,如何在不改变可观察行为的前提下去完成这件事,后续会讲解。
其实在前文就已经提到了,重构这件活实际上应该放在软件的迭代周期中一直做,换句话说,也就是开发过程中,如果需要涉及到的代码的设计已经腐化了,那么就应该及时重构它,通过这样的局部重构迭代,来保证整体代码设计是合理的。
重构的主要挑战
延缓新功能开发
这是大部分人不愿意重构的理由,事实上从短期来看确实如此,但如果一个程序需要长期迭代,那么持续重构反而会提高开发效率。
回顾我们的实际工作,在我们开始一个新项目时,开发功能总是非常快的,但越到项目的后期,想要往代码库中加功能就会变得举步维艰,但如果能保持一直重构的话,那么代码始终会保持合理的设计,新增功能时的困难也会更加小,两者的对比可以用下图来说明。
举个夸张的例子,比如代码仓库A,从不考虑提炼函数,同一个功能始终都是复制粘贴代码,那么在N个版本后,该仓库到处都是重复代码,一旦要修改,就要整个代码仓库大改,那么开发工作将举步维艰,而代码仓库B,则总将可复用代码聚合到函数中,那么修改时只要修改其中一个代码片段即可,开发工作能顺利进行。
引用书中的一句话,重构其实是从经济效益出发的,是为了提高程序员的开发效率而存在的工具。所以如果是长期迭代的项目,重构不仅不是延缓新功能开发的事情,反而是保持良好开发效率的法宝。
自测试不完备
这应该是很多项目难以重构的原因,前文提到因为要调整代码结构,势必会涉及已有功能,如果希望由人工测试来覆盖所有修改到的地方都是正确的,那从成本角度来看是不现实的,搞不好还会惹得QA同事一肚子怨气。
如果有完备的自测试代码,情况就不同了,重构完成后只要执行自测试代码,保证结果OK,那么我们重构时就可以放心去干了。
另外这里提到的自测试代码,跟单元测试还是有些许差别,自测试代码是更加高层的测试,换句话说,自测试代码是为了确保修改前后,程序的可观察行为没有发生改变,即黑盒测试。而单元测试更多是偏向白盒测试,两者不能一概而论。
最后非常建议没有看过《重构 改善既有代码的设计》的同学去读一下这本书,毕竟我文笔拙劣,并不能将原书精华100%还原。
书的前三章就能让人对“重构”这件事情有新的见解,第四章的测试部分,则详细介绍了应该如何去设计有效的自测试代码,之后就全都是实战环节,教授各种重构手法,颇有重构独孤九剑的感觉,非常值得一看。