软件质量的重要性是不言而喻的,但是当所有人都意识到它的重要性的时候,却很少有人能够清晰的描述出如何才能够提高软件质量。软件质量框架的目的就在于提出一个评价的原型,帮助我们分析一种方法和技术是否能够提高软件质量。本系列文章分日构建、测试驱动开发、建立核心框架、面向组件的大规模软件架构来进行深入的分析。
日构建是一项非常基础的软件开发实践,遗憾的是,并没有多少组织真正意识到它的好处。通过本章的讨论,你可以知道日构建对软件开发的意义,了解日构建的基本情况以及如何着手进行日构建。
什么是软件开发的有效管理
在一个全国性的银行中,是什么保证复杂的资金清算的正确性的呢?每天,各个地方的网点在结束营业之前,需要保证账目、资金、票据的平衡;这些网点的数据不断的汇集,在每一个汇集点上都要保证账目余额的平衡,最终完成一个银行的一天的结算。天天如此,就像是一部设计精巧的机器一样运作不息。不仅银行是这样,任何一个企业都是如此。一个小杂货铺的老板,也知道每天算算账,看看今天是赚是赔。这些行为已经成为了工作的一部分,甚至成为了一种习惯。
软件开发也是一样的,必须找到一种方法,来衡量每天的工作,保证每天的工作能够有效的持续下去,最终把软件开发的过程变成一种内在的过程。这种方法就称为日构建或是持续集成。
为什么需要日构建
日构建和持续集成是类似的,对开放源码熟悉的人应该都知道Nightly Build。而持续集成这个词来自XP方法,它的频率可以比日构建更高,可以做到几分钟就进行一次集成,故而由此得名。在本文中,我们只讨论日构建,而要将日构建转换为持续集成是非常容易的。事实上,并没有规定持续集成必须是以分钟为单位进行的,如果你愿意,以一周为单位进行持续集成也是可行的。这样区分的目的是为了更好的使用日构建或是持续集成技术:至少以天为单位,频率越高,效果则越好。
那么,什么是日构建呢?我们传统开发软件的流程一般是这样,理解领域问题,然后分配任务,由不同的人负责不同的软件部件,在开发完成之后,再把各人的部件整合起来,形成完整的软件。这个思路看起来并没有什么问题,但是在实践中却问题多多。
首先,这种方式适合开发人员之间工作彼此没有交集的情况,以前这种现象很常见,但是现在,随着软件规模的扩大、分工合作的加深,开发人员间的相互依赖程度越来越高,这种清晰的职责划分已经变得越来越难了。
其次,在软件集成时,往往会出现各种各样的问题,可是却很难发现到底问题在哪里?公说公有理,婆说婆有理。每个人的代码都没有问题,结合到一起就出现大量的问题。
所以日构建就将平时难得一见的集成工作转换成频繁进行的一件工作,从而使得原先如同噩梦般的集成变成了一件简单的工作。这也是很容易理解的,如果集成工作几个月才进行一次,谁能够记起几个月前的细节呢?但是如果集成以天,甚至以分钟为单位进行,排除bug就变成一件很容易的事情了。
日构建范例
现在的时间是下午的17:00,马上就到日构建的时间了。团队里四名程序员中的三位已经将自己的源代码和测试代码提交到了一台名为宙斯的机器上,这台机器将负责对代码进行日构建。在他们提交代码之前,已经通过了本机上的构建,并完成了测试。剩下的一位程序员似乎碰到了麻烦,他的代码出现了一些问题,他现在需要编写更多的测试来完善测试网。看来时间来不及了,他只能够在明天再做提交了。由于他的代码没有和其他人产生依赖,所以迟些提交也没有关系,不过他在下个工作日的时候必须仔细的将最新的源代码检出到本地,在版本控制工具的帮助下,这项工作是很简单的。
17:10,宙斯终于开始了构建的过程。他从代码源中检出最新代码,然后开始编译,构建,并执行了所有的测试,从控制台界面上,日构建的负责人(其中的一位程序员)似乎看到了有部分的测试没有通过,他对剩下的人说,似乎有麻烦了。测试完成之后,将会从代码中生成所有的API文档,并进行代码分析和测试覆盖率分析,最新测试报告和各种其它的报告都将会发布到Web上。
最后。完成构建的软件和相关的资料已经成功的发布了,时钟指向17:18。"现在只是项目的早期,到了中后期,至少还需要20分钟的时间",老鸟程序员告诉没有经验的程序员,并让大家去看看测试结果。一个程序员边嘟囔,边看log日志,"我在本机都已经测试过了呀,怎么会有错呢。"检查结果发现是环境问题,配置文件被人改动了。看来,集成过程中仍然少不了冲突的问题,只不过,这些问题可以被很快的发现,并很快的得以解决。
以上是一个典型的日构建过程,日构建的过程是完全自动化的,通过预先定义好的指令,机器将按照指令顺序执行完所有的构建步骤。日构建中涉及的步骤是可选的。
日构建的价值
可能有些人认为日构建过于浪费时间,但是实际上,比起最后除错的成本来说,日构建成本是微不足道的。当然,在企业中建立日构建制度确实需要花费不少的时间,但从长远来看,这绝对是值得的。
从表面上看,日构建能够减少最终的排错成本,但这仅仅是日构建最基本的价值。实际上,日构建更为关键的作用是能够引入日构建的制度。这是什么意思呢?日构建的思路将会为软件企业的开发流程带来变化:开发人员将会在日构建的制度下更加频繁的协作,开发进度一目了然,软件的质量也会更加的稳定。
软件开发本身就是一项强调沟通和协作的活动。但是在日常的活动中,常常出现阻碍沟通的情况,例如需要沟通的双方不在同一个地理位置、噪声、沟通双方的意愿等等。因此在软件管理中需要提供一种方法,来鼓励人们进行沟通。日构建就是其中的一种方法(但它并不是唯一的方法)。每一次的构建将会涉及到团队中的所有成员,因此沟通是少不了的,在日构建实践的驱动下,每位开发人员都朝着最终的目的-"成功的构建"努力。
在Alistair Cockburn的敏捷软件开发的第三章中,详细的阐述了团队沟通和协作中的相关问题。例如沟通的实质,影响沟通的各种因素,以及如何克服他们。最后,他还论述了如何促进团队的沟通,来打造一支成功的团队。
在日构建的驱动下,项目的进度将会变得非常的明显。每一天的构建结果将会通过某个渠道发布出来,团队和团队的老板可以看到软件现在的样子,项目的完成情况,出现的问题等等。这些信息构成了软件开发的基本信息。不但可以清晰地描述出项目进度,也为管理人员安排计划提供了基础数据的支持。有了基本的量化数据,软件开发才不是靠拍脑袋出成果的。
日构建的最后一个价值是提供了整合性。目前软件开发中并没有一种统一的管理软件,未来似乎也很难做到,因为不同的软件组织差异很大。在开发过程中,一些有价值的实践被加入、集成到日构建的过程中,在日构建的推动下,这些优秀实践很容易成为开发过程的一部分。在文章倡导的另一个优秀实践-测试驱动开发实践,就很容易集成到日构建中来。事实上,软件测试是非常重要的,它已经成为日构建成功的判别因素。
选择日构建还是持续集成
虽然日构建和持续集成的本质是相同的,但是他们在集成的频率方面的差异也导致了一些管理上的差异:
首先,日构建树立了一个明确的以工作日为单位的小目标,团队的目的就是为了使代码能够通过每天的构建。开发人员在明确的目标的指导下,往往能够发挥出很高的效率。持续集成则将这个频率变得更小,只要你的代码提交到代码库中,立刻就会检验你的成果,如果有问题,你必须立刻排除,否则,要么集成的过程无法继续,要么出错的开发人员的信箱被集成过程中的警告信息所填满。这种做法对团队的要求要更高一些。对于刚刚开始接触日构建的团队来说,可能会手忙脚乱。
其次,日构建有一条非常明确的分界线,开发工作要么是完成,要么是没有完成,不存在第三种状态。
日构建的基础实践
日构建的基础包括三个实践:自动构建、统一代码源和集成测试。
自动构建
自动构建的思路是通过脚本语言,而不是通过在IDE环境中点击构建按钮来完成项目的构建过程。日构建需要不断的进行集成的工作,如果手工来完成这项工作,既费时,效果又不好。所以更聪明的做法是把这件工作给自动化。在早期的Unix环境中,都是采用Make编写相应的脚本来完成构建的过程。随着先进的IDE环境的发展,慢慢的人们就不愿意再编写Make脚本了。可是现在,为了自动构建的目标,我们需要回归到手工编写Make脚本的历史上去。应该说,IDE环境中的构建方式侧重于个人开发,而自动构建则侧重于团队开发
自动构建目标就是通过一个简单的命令就能够全部的构建过程。
统一代码源
其次,保证一个开发团队共享统一的代码源。这时候我们需要使用版本控制工具。共享的代码库同样也是XP的一个基本实践。虽然XP还要求开发人员可以修改他人的代码,但我们并不提倡这种做法,这要求团队成员之间有非常高的默契程度。统一的代码源能够保证所有人的代码都归总到一起,这是日构建的基础。如果没有这一点的保证,每一次的构建我们都不得不把所有人的代码集中起来,这无疑会使构建过程变成灾难。
统一代码源能够保证任何一位团队成员获得所有的代码,并以此为基础进行开发。
集成测试
只是把代码编译通过并不能够证明软件可以正常工作,评价软件的标准应该是测试。在日构建中必须要执行集成测试,来保证软件确实是能够工作的。
集成测试也是一个同义词相当多的名词,有人把它称为验证测试(BVT-Build Verification Tests),因为他们认为这种测试主要的目的是为了验证构建的正确性。有些人把它称为冒烟测试(Smoke Test),因为他们觉得这个测试的目的是运行软件,看它是否会"冒烟"。
测试应该全部执行完毕,而不是遇到未被满足的错误就放弃测试过程。测试将形成结果,成功的测试,失败的测试,失败测试的细节。最后的结果将通过某种方式通知给相应的人员,要求他们修改设计或测试(如果是测试本身的问题的话)。
集成测试是证明构建成功的关键因素。和构建一样,集成测试也应该是自动化的。
日构建的基本工具
日构建的工具有很多,但是最基础、最广泛的工具是Ant(http://ant.apache.org)。Ant类似于Make,但是加入了跨平台的特性。在这个目标的驱动下,Ant摒弃了Make工具的给予Shell的缺点,提供了一种使用XML配置文件的构建方式,并定义了一个统一的微核心和强大的扩展机制。这些特点使得Ant很快被人所接受、推广。目前,Ant的最新版本是1.6.0。
<project name="MyProject" default="dist" basedir=".">
<description>
simple example build file
</description>
<!-- set global properties for this build -->
<property name="src" location="src"/>
<property name="build" location="build"/>
<property name="dist" location="dist"/>
<target name="init">
<!-- Create the time stamp -->
<tstamp/>
<!-- Create the build directory structure used by compile -->
<mkdir dir="${build}"/>
</target>
<target name="compile" depends="init"
description="compile the source " >
<!-- Compile the java code from ${src} into ${build} -->
<javac srcdir="${src}" destdir="${build}"/>
</target>
<target name="dist" depends="compile"
description="generate the distribution" >
<!-- Create the distribution directory -->
<mkdir dir="${dist}/lib"/>
<!-- Put everything in ${build} into the MyProject-${DSTAMP}.jar file -->
<jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}"/>
</target>
<target name="clean" description="clean up" >
<!-- Delete the ${build} and ${dist} directory trees -->
<delete dir="${build}"/>
<delete dir="${dist}"/>
</target>
</project>
以上是一个简单,但已经可以完全说明Ant工作流程的例子,来源于Ant的手册。在这个例子中,先定义了项目的基本信息和构建过程中所需要使用到的属性(1),然后初始化环境(2)(创建时戳和目标目录),在3和4中,对项目进行编译和打包,在5处,提供了清除项目输出的途径。
在Ant中,最关键的四个概念就是项目(Project)、目标(Target)、任务(Task)和属性(Property)。这四个概念的定义和调度、配置文件的处理构成了Ant的核心。而具体的任务则作为扩展机制。这种微核心的处理思路在很多成功的软件项目中采用过。
本文并没有打算对Ant进行全面的介绍,因此,如果你打算在组织中引入日构建,那么,学会使用Ant是必须的。目前很多的IDE环境都提供了对Ant的支持(例如Eclipse),所以使用Ant是很方便的。
原则上,光有Ant就已经可以完成一个日构建过程了,但是还有一些软件提供了更好的封装,使得持续集成变得更加的简单。典型的两个工具是AntHill(http://www.urbancode.com/projects/anthill/default.jsp)和CruiseControl(http://cruisecontrol.sourceforge.net/)。前者是一个商业软件,提供了很多优秀的日构建实践。使用起来也很简单。后者是鼎鼎大名的Martin Folwer所在的ThoughtWorks公司开发的,可以免费使用。
另一个值得关注的软件是Apcache组织下的Maven项目(http://maven.apache.org/)。这个项目还很年轻,目前才到1.0的发布版。Maven给自己的定位是项目管理软件,使用项目对象模型(POM)来描述一个项目,进一步的简化构建过程,并统一构建过程所出产的工件。Maven的另一个目标是通过一种实际的工具,来推广优秀的实践。例如开发目录树的组织。
日构建的代价
虽然日构建有诸多的好处,但是要使用日构建并不是一帆风顺的。最大的问题是如何引入日构建的三项基本实践。前两项相对简单,最难的是建立自动化测试。关于这部分的说明请参考测试驱动开发的相关部分。
日构建扩展任务
日构建的核心任务是编译、构建、执行测试和发布。除了这些任务之外,还可以微日构建添加扩展任务。
生成文档。生成文档有很多的方法,其中最关键的是生成API文档。JavaDoc的概念减弱了传统软件开发中文档的重要性,而把大量的文档嵌入到了代码层面中。除了标准的JavaDoc文档之外,还可以利用第三方的工具生成自定义的文档,例如to-do列表文档。XDoclet就是其中的一个工具。
预编译。不少的应用引入了预编译。典型的如AspectJ,作为一个AOP工具,AspectJ的作用是使用特定的代码生成器生成AOP的Java代码,然后再进行编译。将预编译的工作纳入到构建过程,可以简化开发的工作量。典型的还包括一些ORM工具。
代码分析。代码分析是软件度量的重要工作。代码分析可以为管理人员提供一个判断代码质量依据(不要把它作为唯一的标准)。代码分析是形式化的,因此可以制作成软件,集成到构建过程中来。例如,判断代码是否符合编码规范,文档和代码的比率,包和类涉及的合理性。
测试覆盖分析。测试覆盖分析作为辅助测试的手段是非常重要的。测试代码的复审,最关键的评价测试是否足够(相对),单靠人工完成这项工作太勉强了。所以应该令其自动化,并成为构建过程的一部分。
问题跟踪。测试过程中出现的问题应该被纳入到一个问题跟踪系统中,可以通过和问题跟踪系统接口来设计自动化的任务。
归档和备份。这是很基本,但也是很重要的功能。每天产生的工件应当进行妥当的归档、备份。
进一步了解
试图在短短的一篇文章中完整介绍日构建技术是很难的,从DW的网站上,你可以找到更多管理日构建工具的相关内容。