在大型遗留系统基础上运作重构项目
作者:熊节(ThoughtWorks中国公司资深咨询师)
(本文发表于《程序员》杂志2008年第4期)
本文以ThoughtWorks中国公司与客户合作的咨询项目为背景,为读者介绍如何在一个大型遗留系统的基础上组织和运作重构项目,从而切实有效地改善系统质量。
现状
eMAN是客户的一个核心业务平台。该产品采用了典型的C/S结构,负责处理大量请求和计算的后台部分采用C++开发,负责响应用户操作和处理业务逻辑的前台部分采用Java开发;此外该产品还计划在新版本中提供基于Web的前台,这部分也采用Java开发。
在ThoughtWorks为该产品的开发团队提供咨询时,eMAN产品已经发布了十多个版本,最新版本代码量超过40万行,其中15万行是Java代码。一次又一次的赶工给它留下了大量的“技术债”:系统缺乏测试,代码质量低劣,“copy & paste”的痕迹比比皆是,维护和新功能开发举步维艰。我们这个咨询项目的主要目标之一就是为这个产品找出重构的办法。
原则
可以用两种不同的角度来看待一个软件:程序员的角度,商业的角度。
从程序员的角度看来,“成功的软件”意味着所有测试都通过、代码结构良好、并且容易理解和维护。从商业的角度看来,“成功的软件”意味着它所创造的价值超出在它身上付出的代价。
-
和别的任何story一样,重构的story(以及其他“技术债”类型的story)也应该符合INVEST的标准。尤其是,它们的工作量应该得到估算,它们应该按照业务价值排列优先级。因为归根到底,重构(以及其他任何开发任务)都归结为“花在代码上的成本”与“对业务创造的价值”之间的权衡。
-
按照定义,重构意味着“在不改变功能性行为的前提下改进代码的组织结构”。如果代码基础本身脆弱而没有测试覆盖,重构的成本就会很高,因为你需要花很大力气来确认自己的修改没有改变功能性行为。
-
如果偿还“技术债”的成本非常高,那么与之对应的业务价值就必须更高,否则偿还这些债务就将得不偿失。其结果是:一段代码,从程序员的角度看来越糟糕,从商业的角度来说就越不应该去动它。
-
综上所述,如果有人说“这一大堆代码都需要重构”,这样的说法很有可能是值得商榷的。你需要把重构划分成细粒度的、可控的story,为这些重构story制定验收标准,评估它们的优先级,估算它们的工作量,然后逐一实现它们,并且放弃一些得不偿失的重构。
在eMAN项目中,我们按照软件功能模块划分了story,而不区分是新功能开发还是重构。比如说一个典型的story可能是:
作为系统管理员我要从服务器列表中选择一个服务器从而让我可以登录到选中的服务器
不管是新开发还是重构,这个story的验收条件是一致的:功能通过验收测试,代码符合质量要求。在实际工作中我们发现,对于同样的story,新开发和重构的工作量差别不大。这也使得迭代计划可以在story的基础上照常进行,不必特意区分重构和新功能开发。
持续集成
前面已经提到,重构story也同样应该是可验收的。除了确保其功能性行为仍然保持原样不变之外,这类story还有更多的验收条件:单元测试覆盖率、代码复杂度、编码规范等指标都应该符合项目要求。这些指标也同样适用于新功能开发的story。这些指标的报告应该自动化地生成,及时地展现给所有项目成员,为此我们需要一个持续集成环境。
eMAN项目采用CruiseControl作为持续集成工具。每次开发者往代码库中签入(check in)代码时,就会触发CruiseControl对项目进行构建,构建的内容包括编译、连接(对于后台部分)、单元测试、测试覆盖率统计、代码复杂度统计、编码规范检查、部署到测试环境、功能测试等。由于前台运行在Windows上而后台运行在Linux上,我们用两台持续集成服务器分别构建前台与后台,然后再把两者集成起来进行功能测试。
在建立持续集成环境的过程中,我们发现eMAN项目以前缺乏有效的项目自动化(automation)机制。虽然前后台分别有一些脚本用于执行编译、部署、启动应用等常规任务,但项目自动化机制还有较大的欠缺:
-
-
缺乏版本控制。原来的版本控制库中只有项目源代码和运行时配置文件,开发阶段的配置和自动化脚本都不在版本控制中。
-
环境依赖。原来的自动化脚本对操作系统、安装的软件环境甚至项目路径等因素都有依赖,每个开发者从版本控制库获取代码之后还需要复杂的配置才能让系统在本地运行。
-
自动化不彻底。原来的自动化脚本没有覆盖到构建的所有环节,
-