遗留代码不可避免
在我们的职业生涯中,有很多时候必须忍受遗留代码。或许,你接受一份新的工作,遗留代码是你的第一个任务;或许,你们公司重组,并且有个产品最终在 你这里完成。不论什么缘由,事实就是这样。你想编写一些新的优秀的代码,但是现在负责的是对于你来说全新的完全不熟悉的一段代码。这个代码看起来相当复 杂、陌生,但你却不得不接受这项工作。
事实上,要是我扩展一下这个定义,你就能将任何一段先前编写的代码看作遗留代码。你是否曾经尝试回顾六个月前你编写的代码?忍受你自己所写的代码并非总是易事,更何况对于别人所写的代码。如果你不遵循一些基本的指导原则,这两种情况都是相当有挑战的。传统的方法是,在你竭尽全力避免非有意的间接损害的同时,适当做些修改。不幸的是,因为对代码的不熟悉,当你改变一个数据结构或者更新一个变量时,你无法确信将要发生什么。
与其在这种充满危险的境况里盲目地徘徊,还不如制定一个处理的策略。不要只是作出改变后就期待万事如意。相反,瞄准目标,用“BAT”将它击出棒球场。
处理问题时,你可以三管齐下,构建(build),自动化(automate)并且测试(test)。对遗留代码可以使用这个 BAT,并且给自己设置一个安全网。BAT 方法将确保代码如你所期的那样继续工作。它可以“捕获”非有意的副作用,帮助你“消灭”它们。
构建
要解决的第一个问题就是构建。除非你能可靠地构建它,不然测试一个产品很困难。先解决如何在你的电脑上干净地构建该产品,然后再编写构建脚本。有 时候这是个不成问题的问题,但通常构建不是总能够如期待的一样的干净。构建通常局限于单一机器或者一个特殊的环境。在团队中,当代码在代码所有者间传递 时,很容易积累一些临时的构建需求。每个所有者可以添加他 / 她自己的特殊需求,并且混合在一起。当你接手这摊子事情时,厨房里可能已经有了很多的厨子。
一个复杂的构建能够引发整个产品一连串的问题。
当一件事情困难时,人们就很少去完成它。当一个构建困难时,人们就很少去构建它。这是人的天性。运行一个干净构建的能力正在成为一种黑色艺术,在你的工作环境中,只有少部分人才能掌握它。
因为你无法测试你未构建的事物,使测试变得不频繁。当人们最终运行他们的测试时,他们发现了更多的缺陷…不频繁的测试使得缺陷有更多的时间积聚。如果你每天都运行测试,那么只需要你报告当天的缺陷。如果你等 6 个月后去测试,你将会有很多的问题去处理。
因此测试变得繁重。测试者厌烦了测试周期内的所有工作,所以他们试图避免测试工作。没人喜欢记录十几个或者几百个缺陷这种无聊的工作。
开发者开始畏惧测试周期,因为他们感觉自己象被所有的缺陷报告进行轰炸。所以开发者开始怨恨,并且叨扰测试者。这使得测试周期更加痛苦。这真是一个恶性反馈循环。
复杂的构建给整个产品生命周期带来一些问题。所以必须要保证构建是干净的。
当任何人都能构建时,任何人也就都能进行测试,于是也就会更加频繁地运行测试,产生出更小范围的缺陷报告。一次少量的工作就意味着更少的琐事。任何人都愿意搬动一桶涂料并且不用三思,但是如果让人去搬五百桶涂料,看看他们会说什么。
你的目标就是创建一个能够在任何开发机器上易于运行、易于维护的干净的构建。使用一个构建脚本工具或者语言,例如 Rake、Ant、Maven 或者 Nant。这些高层次的构建语言使你能聚焦于构建自己的应用程序,而不是构建、语言或者平台等细节。
当你可以仅用一句命令(如 ant all)来构建产品,你就能继续到下一步。确保在不止一台机器上测试这一步。
我想请你试着用 BAT 方式来处理遗留代码。 看看与日常工作相比的区别是什么,是否需要用不同的手段处理工作。
自动化
现在你已经可以在任何一台机器上自动构建你的产品了,那么让我们来使它自动化吧。自动化的目标是自动化整个构建,在一台干净的机器上,在测试周期内,使人为干预最小化。使万事自动化并非都有可能。但是我们希望达到的目标是,把那些可以被合理的自动化执行的东西都写入脚本。
有时,安装和配置一个软件要比编写一个脚本来自动安装和配置更为容易。只需一次性安装的应用程序当然是首选。诸如编译器,运行库和已存的数据集都属于这一 类。不要试图花费两个小时去复制已存的数据集。但是,如果你能在 30 秒内重建一个代表性的数据集,那么你便应该从头开始。以一个干净的已知的数据集开始的 好处非常大,但是并非总是符合实际。不要因为重建你的数据,将 15 分钟的测试变为一个小时。
要确保记录下任何一个手动步骤及所有指令,它们可以为其他想复制运行环境的人提供帮助。
另一方面,为什么你应该整天监视着全部的构建?时间是宝贵的。IDE 或多或少都是可以进行增量构建(incremental build)和运行细粒度单元测试(small unit test)。很多时候,这种部分覆盖(partial coverage)已经够用了。让开发人员针对活动的代码区域(active code area),运行一组冒烟测试(smoke test),就可以覆盖大多数情况。
冒烟测试
冒烟测试是一个简短的测试集合,其目标是被频繁修改的代码区。冒烟测试不必设法运行整个产品。一套好的冒烟测试应该是可以轮换运行的……它们不 是永久不变的。当你开始致力于不同的产品领域时,退出原来的冒烟测试,把新的测试添加进来。你可以从完整的测试套件中选择恰当的测试,使其在冒烟测试套件 运行。我通常都是把它们添加到一个 Ant target(我把它命名为 smoke-test)中,然后运行选定的测试。我们仍然需要一个干净的构建和周期性运行的完整测试。这就是我们如何验证是否有人忘记提交一个被修改的文件或者用一种非预期的方式来破坏产品。冒烟测试经常遗漏这几类破坏,为了保持产品处于最佳状态,我们需要相当频繁地运行整个测试套件。
不 要要求每个开发者从头构建整个系统,也不要每天运行五遍每个可用的测试。相反,那些需要花费一些时间来执行的任务,我们都该让计算机来替我们完成。因为我 们可以给计算机分配任务来执行自动构建和自动运行测试,所以我们没有理由每周不运行超过一遍。事实上,每个代码改动后也没有理由不运行一遍。
建立这类自动化的最佳方式是什么?最快捷、最容易的方法就是使用一个持续集成(CI)产品。CI 产品可以监视你的代码,在每次修改后构建,运行测试,然后通知所有相关人员。
持续集成图
CI 系统会创建一个快速反馈循环。当你修改代码时,如果代码遭到破坏,你就会在忘记为什么修改它之前发现它。(关于 CI 系统的更多信息,请访问我的 CI 页面 http://www.jaredrichardson.net/ci.html )。 所有这些讨论都是关于开发速度和持续不断的进度的。对于保持团队的进度,回顾上周或者上个月所编辑的代码是一条末路,而在数小时内抓住并且解决问题才是真正的解决之道。
这里描述了系统是如何进行工作的:你在电脑上编写代码,直到你确信已经完成了某个功能或者修补了某个缺陷,然后你将所作的修改提交到源代码管理系统中 (SCM)。持续集成系统一直监视着 SCM,注意到该段代码发生了改变。接着,它就会从 SCM 中检出代码,构建它,并且运行整个测试。你也将会及时地收到 一封邮件,通知修改和测试是否已经通过或者失败。
这个概念相当简单,但是它在实践中力量非凡。
测试
BAT 最后一部分就是指自动测试。现在 CI 系统正在构建产品,运行你的自动测试,我们需要确信我们的测试充分地覆盖了整个产品。毕竟,从客户的观点来看,如果一个测试不能检验客户所使用的功能,那它就是没用的。首先,设法理解产品是如何使用的……对于一个遗留产品,这常常是出于自身的要求。或许,你甚至要为客户创建一套使用场景。例如,恐怕你要有一组场景,生成日常的报表,完成日常的数据导入,或者增加新的客户。
或许,你也想区分用户类别,它被称为“用户角色”。你恐怕要有“Joe——超级用户”、“Mary——系统管理员”或者“Fred——新用户”等用户角色。每个角色使用了该产品中不同的功能,正如一个超级用户与一个新手使用该产品是大相径庭的。
下一步就是使用模拟客户测试来重现大多数的普通客户使用场景。
模拟客户测试
模拟客户测试(Mock Client Test)并不是一套特定的测试框架。它的目的是用来确保预期的功能不会被破坏。你要改变系统中的一部分,却不知道这个修改影响了产品的其他区域,这在遗 留产品中经常出现。曾经我编写一个产品时,修改了通讯协议,却影响了 GUI 组件布局。CI 系统中的模拟客户测试,是你预防意外修改的保障策略。对产品是否 如你所预望进行测试,就会有一个坚实的产品基线。而且测试也会包括更多有趣的情形,正如你所遇到的那样。一个优秀的测试策略是缺陷驱动测 试(Defect Driven Testing)。每次你发现一个系统缺陷,就增加一个相应的测试。当你增加该个测试时,你可以尝试增加一些与该测试相近而不同的测试。随着时间的推移, 这个策略为产品最需要涵盖的范围,提供了相当可观的测试覆盖率。
不管你如何选择增加测试,都要设置测试优先级。如果你计划对产品进行任何修改,有一个基本的测试套件是最关键的。
最后一步就是将测试集成到持续集成系统。
当自动测试运行在持续集成环境下时,你会及时地得到关于所有问题的反馈。每次你修改一段代码,你就会“从头开始”构建和运行完整的测试。大多数开发者很快就会沉浸于此,并且很快依赖这个 “编外成员”。
- 编写场景
- 创建模拟客户测试
- 使用持续集成
小结
构建、自动化和测试(BAT)对于程序员都是好的建议,但是它对于继承遗留代码者尤其好。拥有自信的重构能力非常关键。我发现如果我要不断地停 下来去查看我正在破坏什么,这种境况下变得高效则很困难。一个好的测试套件可以替我进行监视,而且能够让我集中精力设法完成性能改进。记住,如果你不能测试遗留代码的话,那么就不要修改它。而且不要单纯地进行手动测试,除非没有其他选择。
不仅不要害怕遗留代码,还要正确地对待它。用 BAT 这种方法来处理它,你就会每次都能成功。
关于作者
Jared Richardson 是《 Ship It! A Practical Guide to Successful Software Projects 》 一书的作者之一。他是一名演说家和独立顾问,擅长使用非定制技术来解决疑难问题。Jared 有着十年丰富的工作经验,曾经做过顾问,开发者,测试者和经 理,包括很多公司开发部主管。直到最近,他领导 SAS 软件研究所(SAS Institute, Inc.)的一个开发团队和测试团队,为三百个项目,五百万行代码和超过 1800 个开发者部署了一个持续集成系统。他也带领整个公司提高了测试自动化的使 用。可以通过网址 http://www.JaredRichardson.net 找到 Jared。
点击这里阅读Ship It! A Practical Guide to Successful Software Projects 一书的样章,只有在InfoQ 网站上才能获得!
查看英文原文: Dealing with legacy code - - - - - -
译者简介: 包亮,一名普通的程序员,喜欢敏捷实践,喜欢"懒惰",减少重复,尽可能让工作变得简单。几年来,一直通过网络汲取知识,也希望通过网络将知识与人分享。参与 InfoQ 中文站内容建设,请邮件至 china-editorial@infoq.com ,加入 InfoQ 中文站用户讨论组,请点击 ICUG,InfoQ China User Group 。
评论