注:文章的大部分内容来自参考文献提到的三本书,结构上是基于个人对原著理解以及对遗留代码这个命题的相关问题进行逻辑上的重组,供分享和个人日后翻阅。
遗产 VS 遗留
遗产是已经死亡的事物存留下的依旧影响着世界的那部分,能留下遗产的生命是优秀的。而在软件产业中,“遗留”这个词语用来形容那些已经失去活力但是依旧运行的代码,遗留代码通过过去的决策持续影响着那些深陷其中的人。“遗留代码”一词严格的定义是“从其他人那儿得来的代码”,常常是“无法理解的、难以修改的代码”的代名词,而《修改代码的艺术》的作者Michael C.Features给“遗留代码”的定义是“就是那些没有编写相应测试的代码”,这个定义简洁明了,Features认为那些没有编写测试的代码都是遗留代码,因为没有编写测试的代码不管我们有多细心地去编写它们,不管它们有多漂亮,面向对象或封装良好,只要没有写测试,我们实际上就不知道修改后的代码是变得更好了还是更糟了,反之,有了测试,我们就能迅速、可验证地修改代码的行为。
这件事情似乎没有被重视
我们可以基于曾经看过的或购买过的软件开发相关的图书得出这样一个结论——业界大多数相关书籍都是关于原生开发的,这些书籍教你如何从无到有地创建出一个新的应用程序,然而实际情况是,真正身处业界的我们大部分的时候面对的却是对既有代码修改,包括:添加特性、寻找bug以及重构别人的代码。我们称硬件为“硬”是因为它是固定的,没有工具是无法调整的,我们称软件为“软”是指它由思想而生、通过代码来表达,被加载到硬件中然后行使一些职责,可是,极具讽刺意义的是,代码在编写完成脱离开发者之后变得比硬件还难修改。
遗留代码的成本
美国国家标准与技术研究所2002年的报告“软件测试基础性的缺乏对经济的冲击”发现,软件缺陷对美国经济每年造成近600亿美元的损失。在《软件项目的失败造成十亿美元级别的损失:更高的评估与计划可以用于改善》中,作者格罗拉斯的研究发现”基本上认同“软件开发失败的损失在”每年500亿到800亿美元之间”。美国国家标准与技术研究所(NIST)称“软件之所以充满了错误,是因为其不断增长的复杂性,软件产品的规模不再是数以千计的代码行,而是以百万计。软件开发者已经在排查与修复的工作上花费了近80%的开发成本”。正如下图所展示,差不多60%的成本花在了优化上,17% 花在了错误修复上。为何软件维护成本如此之高?简单来说,是因为我们没有对可维护性给予足够的重视。
软件维护的成本
我们如何应对
《重构-改善既有代码的设计》这本书是来自世界级软件开发大师Martin Fowler,正如书中给出的定义,重构是“对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本”的一门技术。通过对既有软件的重构,我们可以提高软件的可维护性。其背后的理念是,如果我们编写测试以确保现有行为不变,并在重构过程中的每一步都小心验证其行为的不变性的话,我们就可以在不改变软件行为的前提下通过重构使其更具备更好的可维护性。对!重构的第一步是,先检查自己是否已经有了一套可靠的测试集,而且这些测试必须有自我检验能力。这个时候,我相信大多数同学头大了,我承认,按照Features对“遗留代码”的定义,我们的代码都是遗留代码,而且我们每天都在发布遗留代码。
《重构:改善既有代码的设计》
测试集是什么我怎么不知道
对系统进行改动有两种主要方式,我喜欢将它们称为“编辑并祈祷”和“覆盖并修改”,遗憾的是,前一种方式几乎可算是业界的标准做法。使用这种方式来进行改动时,先是仔细地计划你所要进行的改动,并确保自己理解了将要修改的代码,然后再开始改动。结束之后,运行修改后的系统,看看所做的改动是否已经生效,再然后就是对系统整体的修复,以确保改动没有破坏什么东西。可以看出,这是一件需要专业水平的工作,在事情一开始就需要小心,而当改动变的非常具有侵入性的时候,你还需要格外地小心,因为这个时候出错的可能性就更大了。而“覆盖并修改”则是另一种方式,它背后的理念在于,在我们改动软件的时候张开一张安全网,这里所谓的安全网,像张斗篷一样“盖”在我们进行修改的代码上,以确保糟糕的改动不会泄露出去并感染到软件的其他部分。这里的“覆盖”指的是用用测试来覆盖它,当对一段代码有一组良好的测试覆盖时,我们就可以放心地对他进行修改,并快速检验出修改是好是坏,可问题是,我们的遗留代码没有测试覆盖。。。
先让代码变得容易测试
一般来说,只有在设计系统的时候就考虑到了测试,这样的系统才容易添加测试,那么问题来了,如果系统本身就很容易测试,很可能已经有测试了,现在也不用操这份心了。因此,我们陷入了一个恶性循环的陷阱之中,重构依赖测试,而遗留代码不仅没有测试,而且很难写测试,这可怎么办?Feathers在《修改代码的艺术》这本书中给出了一个可行的思路——我们可以找到程序的接缝,在接缝处插入测试,从而让系统置于测试覆盖之下,如果系统没有测试,则需要运用重构手法创造出接缝,注意,创造接缝的重构本身没有测试保障,因此具有一定的风险,然而我们别无选择,事情必须往好的方向上发展。什么是接缝?接缝指的是程序中一些特殊的点,在这些点上你无需做任何修改就可以达到改动程序的目的。这么描述可能有些抽象,我们看下面具体的例子,假设我们想在测试期间不执行PostReceiveError这个函数,因为这个函数会跟另一个系统进行交互,使测试变得困难,因此我们可以通过一些手段让运行期在执行这个函数的时候实际上走另外一段逻辑,比如Mock掉这个方法,我相信对于Mock大家都很熟悉。
PostReceiveError接缝
重构的原动力
重构的目的是为了加快开发速度、提升系统可维护性,但是仍旧很多人认为,花在重构上的时间会拖慢新功能的开发进度,他们的理由是重构需要”人力成本“,而当下资源不足,这可能是人们没有做重构或是没有充分做重构最大的阻力所在。那么,什么时候做重构呢?有一种情况比较好判断,比如马上添加的功能非常非常小,这个时候我们可以选择先把新功能加上,先不做大的重构,做这个决定需要判断力,这个判断过程很难描述和量化,因此判断方法也很难传递。但是有一个思路可以借鉴,回归到重构的起点,我们可以始终从纯粹的经济学角度来考量,之所以对遗留代码进行重构是因为它让我们更快更好——更快地添加功能、更少的bug。
参考文献:
《修改软件的艺术》
《修改代码的艺术》
《重构-改善既有代码的设计》