当前位置:首页>>软件教程>>新闻内容  
协作开发中的质量保证技术
作者:司徒彦南 发布时间:2003-12-30 15:06:31 | 【字体:

——并行版本控制、每日构建和交付工程

目录

  • 摘要
  • 问题的提出
  • 并行版本控制——多人协作开发的有效保障
  • 代码提交和同步
  • 编码过程中的沟通纽带——commit mail
  • 日常测试——每日构建
  • 有效的版本控制——代码分支、版本标记
  • 特性冻结与代码冻结
  • 交付工程
  • 总结
  • 参考文献
  • 作者简介
  • 摘要

    本文以cvs为例,介绍了软件工程中,编码过程中对于版本控制的运用的一些技巧。在最后部分,还介绍了软件工程最后的“交付工程”。

    问题的提出

    编码过程是软件工程的重要一环。这一部分工作的好坏直接关系到软件产品的质量。高效率的多人协作开发,依赖于团队精神、设计师对于软件架构的整体把握、好的并行版本控制技术,以及制度化的每日构建和最后阶段的交付工程。

    今年六月,我有幸在一家开发安全软件的公司观摩了他们的每日构建和交付工程中的活动。他们对于并行版本控制、每日构建技术熟练而深入的应用给我留下了非常深刻的印象。在此,我愿与读者一同分享我自己的学习体会,这其中的某些部分得益于在那家公司的实地观摩,另一些则来自于我自己参加的实际软件工程项目的体会。

    毫无疑问地,一个软件工程项目最有价值的部分还是在它的设计阶段。良好的设计能够让实现环节变得更有效率,从而极大地提高劳动生产率;而好的编码规范,则是协同开发的重要基石。限于篇幅,对于前述两项内容本文将不会过多设计,我将着重介绍软件工程中编码与测试环节的一些经验,这些经验对于已经拥有优秀的软件设计师和编程、测试人员,而苦于由于连调、最终测试导致发布频频延期的开发团队来说是非常有益的。

    并行版本控制——多人协作开发的有效保障

    设想一个有4名编程人员的小型开发团队(以下简称“TJRP开发组”),Tom、Jason、Robert和Pat分别负责4个模块,按照传统的软件开发模式,开发将经历编程-连调-测试-发布4个阶段。

    如果最初的设计正确,并且,四个开发人员都是Guru级的程序员而且配合默契,那么这个模式将会运转良好。然而遗憾的是,Pat刚刚参加工作不久,对于设计师撰写的设计文档的理解不够透彻,而Robert则自作主张地对设计进行了一些修正,更糟糕的是,项目组会议的时候,这些问题没有及时地被暴露出来,导致Pat和Robert代码在设计上的“分歧”越来越大。结果,进入连调的阶段,Pat和Robert发生了激烈的争执,在吵得不可开交之后,项目经理终于让4个人坐到了一起来解决问题,最后,连调阶段整整多花了一倍的时间。

    但倒霉的事情还没有结束。Jason在测试中发现,原本正常的代码的行为被改变了,并且,他惊讶地发现代码被某个“别人”改过,在翻箱倒柜地找出某份正常版本的副本之后,他又发现,“别人”修改中的某些地方是必要的。代码合并和重新测试使得测试阶段足足花掉了原先预想3倍的时间。

    可怜的Tom运气更差,作为主要的代码复审人员,他不得不阅读所有的代码。Robert和Pat的争吵导致了大量的代码变动,他不得不重新审核代码,而Jason的代码合并引发的新问题又让他不得不分神去帮助Jason进行调整。

    最后的结果是,软件开发的成本是预期的2.4倍,发布时间也拖后了不少。我并不是在开玩笑,上面所讲的是一个发生在那家安全软件公司的真实故事。他们的技术经理介绍,在施行了规范的开发制度,以及启用并行版本控制系统之后,他们认为开发达到了一个全新的水平。并行版本控制系统本身并没有产生任何代码,但由于使用这样的系统,开发的效率被大大地提高了。

    所谓版本控制其实并不是什么复杂的概念。对于开发活动的绝大多数参与者来说,版本控制系统在某种意义上能够帮助他们做好开发过程中的记录工作,并且,通过保存文件在不同时期的版本,交付工程师和代码复审员能够很容易地缩小搜索问题代码的范围,而程序员则可以通过这样的系统更好地并行协作。一般来说,源代码的版本控制系统能够实现以下一些最基本的功能:

    • 保存任意一个源代码文件的不同版本
    • 记录修改者、修改原因
    • 当两个用户同时修改一个文件时,尽可能地自动合并修改;在不能合并时,给出提示
    • 比较不同版本之间,或与本地副本之间的差异
    • 获取最新版本的全部源代码供测试,并允许回退到所保存的源代码的任意版本
    • 创建代码分支,便于软件发布和后期维护(后面将会提到);新的代码可以合并到这些分支中。
    • 对不同的源代码给出标记,方便日后审查
    • 访问控制:阻止未经授权的修改和查阅

    我们知道,技术不是解决一切问题的灵丹妙药,但是谁也不会否认大规模的机械化生产的效率高于人拉肩扛的手工业作坊,一旦运用得当,技术将极大地改善我们的工作和生活。我们可以看到,上面的功能有效地解决了TJRP开发组所面临的绝大部分问题,例如:

    • 由于能够同时修改代码,并获取对方的修改,Pat和Robert能够有效地、尽早地进行沟通
    • 促进开发者之间的交流,每一个修改都必须给出原因,并记录提交者
    • 测试和连调可以尽早开始,避免模块之间的不兼容在最后阶段被暴露出来而阻碍发布
    • 不同开发者的修改能够及时合并,并避免由于版本不一致导致的冲突
    • 代码复审可以针对某一代码分支进行,从而,允许一些开发者持续地开发下一个版本,而稳定的代码则可以交付给用户

    更进一步,以管理者的角度,还有了一些额外的好处,如:

    • 每日构建和测试能够让项目经理更好地把握工程的进度
    • 谁作了多少工作,谁工作的更出色,可以在版本控制系统中清晰地体现
    • 分工明确,通过访问控制,可以避免不了解整个代码体系的开发人员偶然的错误修改导致的全盘崩溃
    • 更重要地,版本控制系统中将保持大量的开发经验,这对于一个开发团队来说是一笔无价的财富

    我们可以看到,上述改进集中地体现了一个重要的思想,即:

    及时沟通以预防问题的出现;尽早发现、尽早解决问题;明确奖惩制度,激发开发人员的积极性。

    下面我们将以非常常见的版本控制系统——cvs[1]为例,介绍并行版本系统一些基本的使用原理。

    代码提交和同步——从update和commit说起

    每当我们开始一个新的修改之前,首先要做的是从代码库中提取出一份最新的副本(通过update操作完成);在本地修改、粗调之后,则应尽快将代码提交回代码库(通过commit操作完成)。

    基本的update和commit操作流程如下图所示:


    图1. cvs update和commit

    这两项操作也解决了日常开发大约80%的问题。绝大多数情况下,这部分的工作是相当简单的,除非出现两个开发者同时修改同一个文件的情况,例如,两个开发者同时地修改了同一个文件的同一个版本,这种情形称为冲突:

    图2. cvs并行开发中的冲突

    可能开发者B的动作比较快,或者,修改的东西比较简单,于是他首先提交。在A、B从代码库中提取代码时,最新版本是1.1,于是,B提交的版本被版本控制系统命名为1.2。

    但不久,A想要提交代码,版本控制系统将拒绝他的提交,因为他的修改基于代码的1.1版,而目前的最新版本已经是1.2了。cvs提供了自动合并功能,允许在两个人修改的不是同一行代码的前提下,自动合并本地修改和最新的代码,当然,如果赶上两个人同时修改同一行代码的情况,cvs也会非常“聪明”地把两个“英雄所见略同”的地方合并。

    但如果两个人恰好都修改了同一行代码,而且改的不一样怎么办?cvs会告诉后一个提交的开发者发生了这样的情况,并且要求他解决问题。代码中存在的差异将以<<<和>>>标记出来,以方便进行修改。

    简单说来,当发生冲突时,我们通常约定由后一个提交者解决冲突——当然,他可以选择忽略这些冲突,但这些操作都会被记录,更何况,统计显示,同时将一行代码修改为两种不同的样子这样的情况在实际开发中很少出现。于是,修改流程继续,如下图:

    图3. 开发者A解决冲突,并提交

    极端情况下,可能出现多个开发者同时修改同一个文件的问题。这一问题基本上可以按照上述的方法解决。当然,为了避免发生这样的情形,在设计的时候就应该让每个人工作的代码尽可能地不重叠。

    下面是非常基本的cvs update/commit操作规范:


    简单的cvs操作约定

    • 修改文件之前首先update。这意味着修改时的版本尽可能新,一旦发生冲突,解决它的工作量会比较小。
    • 及时commit。本地代码与代码库中的代码差异越小,别人合并的难度也就越小(他们有比较大的概率能够拿到新的版本)
    • 将不同的功能单元修改分开commit。一方面,这样做能够尽早地commit,减少别人合并的难度;另一方面,由于cvs提供了回退到先前版本的能力,一旦由于某项功能修改造成问题,也很容易将那次修改的内容,而不是整个修改回退到正常的代码。
    • 同一功能涉及的所有代码一次commit。不希望将涉及同一功能修改的代码分开commit,因为这会给日后的追踪带来麻烦。
    • 先调试后提交。这将减少别人不会因为同步了中间结果引发问题,甚至发生提交冲突的可能。
    • 写清commit log(提交日志)。cvs中允许保存commit log,在这里可以写为什么进行代码的修改,以及进行了什么样的修改,清楚的commit log能够帮助其他开发者在不仔细阅读代码的情况下了解修改的内容,从而极大地提高开发效率;另一方面,这些日志对于开发者自己,以及整个开发团队,都是非常宝贵的财富。

    同步代码(update)和提交代码(commit)占到了cvs日常操作的80%以上。从上面的介绍我们可以看出,仅仅依靠这两项非常简单的功能,cvs就能极大地改善开发流程,并提高软件工程的可控性。简单地说:

    • 全体开发者使用同一个中央代码库,从而消除了由于来回复制文件导致的不一致。
    • 发生冲突时,后提交的开发者必须解决它。这名开发者能够知道是谁引入了冲突,他可以自己解决冲突,也可以与引入冲突的开发者商量如何解决冲突。
    • 测试可以贯穿编码过程的始终,任何时候引入的新问题都能够被及时追踪、快速定位,从而更有效地解决。
    • 由于存在中央代码库,代码审核人员和每日构建人员能够及时地了解代码是否存在问题,并帮助项目经理保证开发进度。
    • 有助于帮助开发人员养成严谨的工作习惯——规则要求他们将尽可能地提交正确的代码,并且每个修改必须写commit log进行说明。
    • 有助于建立更公平的工作质量评估机制。 cvs能够记录每个人完成的实际工作量,包括他们因为修正问题等等所作的劳动,以及他们完成代码的质量情况。这样,管理者能够为优秀的开发人员提供更好的工作机会、报酬,等等,这对于鼓舞整个团队的士气、提高开发人员的工作积极性都是非常有益的。
    • 促进开发人员之间的交流。尽管cvs本身无法代替交流,但commit log,以及cvs系统获取任意版本之间差异的能力,能够帮助开发人员了解对方的想法,并促进他们共同提高。
    • 降低程序开发人员的门槛。由于提供了许多非常方便的协同开发手段, 使用cvs能够减少协同开发所需要的磨合期,同时,不同层次的开发者之间由于能够进行经常性的沟通,从而把编码过程从技能型工作向熟练性工作又推进了一步。这意味着高层次的开发人员能够去进行更能发挥他们特长的工作,而新手则可以很快地融入到日常的开发活动中来,从而提高劳动生产率,降低开发成本。

    编码过程中的沟通纽带——commit mail

    cvs是一项开放性很强的工具,它可以被非常容易地订制。一般来说,cvs服务器会架设在一台Unix主机上(我们推荐使用FreeBSD),通过使用脚本语言(例如,Perl),cvs能够完成一些额外的功能。

    commit mail是commit log在邮件系统上的延伸。下面是一封典型的commit mail,它来自FreeBSD开发团队:

    phk 2003/10/21 23:32:20 PDT

      FreeBSD src repository

      Modified files:
        sys/geom geom_io.c
      Log:
        Forgotten commit: If a provider has zero sectorsize, it is an
        indication of lack of media.

        Tripped up: peter

      Revision Changes Path
      1.50     +3 -6   src/sys/geom/geom_io.c

    我们看到,上面的这封commit mail中提到了开发者(phk)、提交时间(太平洋时间2003年10月21日23:32:20)、涉及的代码库名字(FreeBSD src repository)、修改过的文件(sys/geom的geom_io.c)以及commit log。最后,commit log还提到了提交后文件的最新版本(1.50),修改规模(+3 -6)以及代码的实际路径。

    实现上面的功能并不复杂,实际上,您只需要下载一套经过定制的FreeBSD cvs代码库(压缩包不超过40KB),并作少量的调整,就能够直接使用这些功能(我们将在不久以后发布这些内容)。进行这些订制甚至不需要基本的Perl和C/C++常识就能够完成——当然,我想这样的常识对于软件开发人员来说,并不算是很高的要求。

    commit mail可以通过邮件列表发给全体开发者。许多大的软件公司,以及开放源代码团体,都采用这样的方式来协调开发活动。

    日常测试——坚持每日构建

    传统的软件工程中,测试发生在连调之后。这么做的理论依据是,测试依赖于一份一致的、至少能够正常编译并启动的代码。而这个条件在连调之前是无法满足的。

    然而在有了版本控制系统(如, cvs)之后,连调变成了开发中的日常行为。代码几乎在每一时刻都处于高度的一致状态,甚至在许多时候,代码会处于可用状态,从而为测试创造非常有利的条件。

    许多大型开发团队会使用一台甚至多台被称作TinderBox的机器来完成每日构建和测试。简单的每日构建流程如下:

    • 测试工程师,或代码复审员从代码库中提取一份代码的快照
    • 相关人员在TinderBox上进行编译
    • 编译的任何错误被追踪,测试工程师或代码复审员将回退代码到上一次能够成功编译的点,并与此后进行代码提交的其他开发者进行联系,解决问题
    • 测试工程师对于代码进行测试

    其中,第一、二步是可以通过脚本定时、自动完成的,不需要人工干预。第三步中的编译错误在软件开发中偶尔会发生(这可能来自于冲突合并时引发的问题,但由于开发人员的本地测试,这种文体不会是经常性的),习惯上,这些错误会由代码复审员去追踪和处理,并交给相关的开发人员解决。

    测试工程师可以将编译好的版本交付给一个测试组,甚至用户去进行测试。测试工程师可能随时发布软件的“快照”版本。

    实际上在许多大公司中,每日构建是非常“家常便饭”的事情。我们看到的Internet Explorer版本,如6.0.2600或者类似6.0 Build 2600中的2600的意思就是这份代码之前已经经历了2600次每日构建操作(当然,中间肯定进行过不少修改,而且,也不排除这个2600是故意凑整得到的,但总之,他们进行了相当多的日常构建工作)。

    在软件开发的后期,由于发布的迫在眉睫,每日构建很可能会演化为持续构建,即,每次commit触发一次构建操作。所有问题立即得到反馈。

    为了支持每日构建或持续构建,比较理想的方法是采用Makefile完成构建操作。对于Unix系统,make工具通常是pmake(BSD Make)或gmake(GNU Make);对于Visual C++,则是nmake。大的开发团体通常使用一组脚本来完成所有的make操作,而对于中小型项目,手工地使用类似VC++这样的IDE本身的构建功能也是可以接受的。

    基本的每日构建规范如下:


    基本的每日构建和测试注意事项

    • 避免在存在开发人员大规模地进行commit时提取快照。此时提取的中间结果很可能有问题,并导致每日构建从头做起。通常,测试工程师和代码复审员在多数开发人员下班的时候开始每日构建,因此,每日构建有时也被称作每晚构建(Nightly Build)。
    • 标记(tag)每日构建中能够正确编译的版本。这将减少第二天每日构建中的麻烦。
    • 及时通知造成问题的开发人员,即,所涉及代码的提交者。某些公司甚至要求员工开着手机和寻呼机,以保证工期。

    作为commit mail的有效补充,许多项目开发组会建立邮件列表来传递一些相关的信息。测试日报通常会发给整个开发团队的参加人员,此外,保留一个出现过的问题记录,对于测试环节也会有相当大的好处——这些问题在随后被反复测试,以保证最终的RELEASE不出现这些问题。

    每日构建并不是可有可无的工作,作为日常测试的重要手段,每日构建能够有效地帮助管理者了解工程进度,帮助开发者尽早发现问题,同时,也会促进开发组中的交流。

    有效的版本控制——版本标记、代码分支

    三个文件的“最新版本”分别是1.5, 1.3, 1.4,但最新版本不一定是我们需要的。在这种情况下,版本控制系统提供了一个非常重要的机制——版本标记。例如,我们目前已经确认三个文件的1.4, 1.3, 1.4组合在一起能够正常运行,于是我们在三个文件的这些版本上标注标记TAG_1,如下图:

    图4. TAG_1标记被打到三个文件的不同版本上

    需要说明的是,标记是可以被移动的。这意味着一旦发现标记打错了,可以把标记移动到别的位置。但在实践中,标记往往同另一个非常重要的版本控制机制——代码分支一起使用。在详细讨论标记(tag)的重要意义之前,我们先来看看代码分支时什么:

    图5. 比较复杂的情形,一个正在开
    发的项目中的某个文件,已经完成了
    2.0和2.1的发布; 其中,BP是指划分
    分支的切分点(Branchpoint)

    所谓代码分支是版本控制中的一个非常关键的概念。当开发到某个阶段的时候,可以交付一个版本,而主要的开发者则把精力投入到最新版本的开发中。第一个交付分支(2.0)中的一些问题,以及引入的新功能随后在RELENG_2分支中被修正,公司决定发布2.1版本;此后,2.x中的问题继续在RELENG_2中被修正,而一些安全更新,则被合并到2.1-RELEASE中(RELENG_2_1)。

    图5展示的是一个文件上的版本分支。实际的软件工程项目的源代码会由大量文件组成,尽管在本质上分支是针对每一个文件说的,但在被标注了同一分支名称的文件,就像版本标记一样,能够表达一组特定版本文件的集合。

    cvs的版本分支功能有一个很大的缺陷,即,大量文件的切分点(Branchpoint, 即某一个分支最初的版本号)在cvs中很难被指定(cvs支持按某一分支、某一特定时间、某一特定版本来提取文件,但通常不同的文件的版本号并不统一,特别是在大型项目中,肯定有某些文件因为被提交的次数很多,而版本号很“高”的情况)。为了消除这个缺陷,在实践上,我们采用版本标记与分支结合的方法,即,在划分新的分支之后,在这一分支的这些文件的版本上增加一个版本标记。

    例如:对于软件的2.0版,在划分时,将切分出RELENG_2(2.x),RELENG_2_0(2.0)两个分支,而这时的文件的版本,同时被打上一个RELENG_2_0_0_BP的标记。这样一来,在以后比较版本时,我们可以使用RELENG_2_0_0_BP来指定这个版本。当不同的分支又增加了许多修改之后,这个标记将极大地减轻代码复审员的工作量。

    注意,代码分支并不仅限于版本上的用法。事实上,基于同一代码基础的多个不同的软件也可以采用代码分支的方法进行开发。而最终,这些代码还可以合并为一个。

    您可能已经注意到最左边的一组版本序列:1.1, 1.2, 1.3, 1.4, 1.5. 1.6。在cvs中,这一序列被称为“主分支(MAIN Branch)”。尽管并非必须,但习惯上,主分支通常是活跃的开发分支。在这个分支中,人们不断地引入最新的特性,当然,不可避免地,这也可能引发一些问题,而这些引入主分支的问题在随后将被追踪、修订。经过一段时间之后,被“沉淀”下来的代码可以进入另一个叫做“稳定分支”的代码系。

    这样的开发模式通常被称作“多头并进”模式,这样的模式在许多开放源代码的软件开发中非常常见,例如,Linux的单、双号版本、FreeBSD的-STABLE和-CURRENT[2],等等。在一般的商业软件开发中,这种模式也相当常见,特别是在大公司的开发中。拥有多头并进这一能力对于大型软件的开发尤为重要,因为大型软件很可能包含相当多的模块,通过版本控制,问题能够很容易地被整个开发团队追踪。

    多头并进的开发环境中,开发人员可以在粗略熟悉了某个分支的代码体系的情况下参与开发或维护,这意味着,即使某个代码分支的维护人员突然离去,其他人也不用担心通盘阅读不同分支的代码可能造成的理解困难,换言之,对于新的维护人员的要求被降低,从而,软件的开发和维护过程能够更为有序地进行。

    据我所知,FreeBSD的软件开发过程极大地得益于多头并进的开发模式。下面简单地介绍一下FreeBSD所采用的软件开发模式:


    案例:FreeBSD开发模式中对于多头并进的应用

    FreeBSD包括了两个主要的开发分支:4-STABLE和5-CURRENT,以及若干安全分支。其中,4-STABLE(RELENG_4分支)代表的是FreeBSD 4.x系列的开发,其关注的焦点是系统的稳定性和性能;5-CURRENT(HEAD分支)代表的是FreeBSD 5.x系列的开发,它关注的焦点是尽可能多地引入最新的操作系统特性,全新的设计思想,等等。除此之外,还有一些被称作安全分支的分支,它们分别代表FreeBSD 2-STABLE, 3-STABLE, 4.6-RELEASE, 4.7-RELEASE, 4.8-RELEASE以及即将推出的4.9-RELEASE等等,但这些分支完全不引入任何新的特性,只有安全更新能够被加入到这些分支中。

    FreeBSD的“安全分支”是一个非常重要的概念,在FreeBSD的开发中,这些分支基本上只由一个包括了少量开发者(目前只有两人)的,被称为“FreeBSD安全官”的团队维护。对于很多用户来说,他们并不在乎操作系统是否拥有新的特性——他们不愿意尝试新版本的软件,因为现有的系统工作的非常好。这些用户使用“安全分支”的FreeBSD操作系统,因为他能够提供必要的安全更新,而操作系统特性并不会因此发生变化(无论这种变化是否能够改进性能,或提供一些眩目的功能,甚至支持新的硬件,因为用户的系统已经放在那里了)。

    CURRENT分支走的是另外一个极端。所有的新特性,一旦被特定的工作人员(committer)测试通过(大的变化需要核心团队,即core team的批准,但这种情况并不是很多),就允许被引入CURRENT分支。尽管CURRENT分支在绝大多数时间都能够被正确地编译,但引入新特性有时会不可避免地带来一些问题,例如硬件适应性问题。

    在这两个极端之间,有一条中间路线,即STABLE分支。在CURRENT分支中提交的代码通常会被指定一个MFC(Merge from -CURRENT)时间,在这个时间之后,如果没有人提交关于代码的问题,则这些代码会被引入STABLE分支。

    这样,STABLE分支的代码几乎都是经过相当长时间测试的代码,对于大多数用户来说,STABLE分支是一个很好的选择。

    一般来说,FreeBSD中的代码会经历下面的历程:

    • 代码被引入CURRENT分支
    • 相关开发者获得来自用户的反馈; 在确认基本没有问题的情况下,代码被引入STABLE分支
    • 大多数最终用户使用STABLE分支的代码来支持他们的计算机

    我们可以看到,上面的开发模式同时照顾到了开发者和用户群体的利益。一方面,活跃的开发不会因为影响到了大量的普通用户而遭到指责;另一方面,在开发分支(CURRENT)的代码经过一段时间被引入STABLE,最终用户能够得到那些新的操作系统功能。

    事实上,上述开发模式已经被证明是相当成功的。由于开发过程中每一天都有相当多的人对新的CURRENT和STABLE分支的代码进行测试,因此,在最近的几年中,FreeBSD的开发一直呈现着良好的态势。

    交付工程(Release Engineering)基础——特性冻结和代码冻结

    许多参与过大型项目开发的读者可能都经历,至少是听说过特性冻结和代码冻结这样一个概念。所谓“特性冻结”实际上是一个开发者之间的约定,在这个阶段中,不再允许添加新的功能。

    特性冻结(Feature Freeze)通常在一个开发分支(Development Branch)跃变为交付分支(Release Branch)的时候开始。之所以需要特性冻结,是因为增加新的特性很有可能引入新的问题,而这将给代码复审带来沉重的负担,甚至导致一次不成功的最后交付。

    当然,对于那些已经明确地定义了特性表的小型软件工程项目(例如,传统的瀑布开发模型)来说,特性冻结没有什么意义,因为在这些工程中,详细设计完全是在编写代码之前进行的,这意味着,代码将要写成什么样子已经在详细设计中明确地定义。但在实际的项目中,详细设计往往会包括两类不同的类型要求——一部分是“必须实现的特性(Must have feature)”,另一部份则是“希望实现的特性(Desired Feature)”。在交付之前,所有“希望实现的特性”都会在特性冻结时被明确成“实现特性”和“不实现特性”。

    我们注意到,这种情况下,某些特性被延迟到接近交付的时候才被明确成“必须实现”,而另一些“希望实现的功能”则被作为“不实现”,从而转化为我们先前熟悉的样子,即详细设计文档中明确地定义了软件中的所有特性。

    这样做的结果是软件工程项目具有更大的灵活性。由客户需求产生的功能设计,很显然地,应该列为“必须实现的特性”,而那些开发团队提出的能够提高软件整体可扩展性、可伸缩性或其他性能的特性,则应列为“希望实现的特性”。在特性冻结之后,整个开发团队将专注于那些“实现特性”(尽管这些特性可能还没有被正式的实现)更加稳定,从而将生产出更高质量的软件。

    根据我个人的经验,特性冻结应该发生在预期编码时间已经用去大约2/3的时候。这时,项目经理应该组织开发人员举行一次会议讨论特性冻结,而在特性冻结之后,在软件正式交付之前,任何开发人员都不应该再去考虑那些被列为“不实现特性”的功能。

    代码冻结是一个与特性冻结类似的概念,在这个阶段,只允许对被冻结代码分支中的错误进行修正,而不允许任何其他的、涉及功能的修改。实践上,这个过程中,只有交付工程师(通常是一个或多个对于整个系统架构非常了解的、有丰富开发经验的代码复审员)被授予审查和批准代码提交的权力,任何代码修改,只要没有经过交付工程师的批准,就不能被提交到代码库中。

    代码冻结的时间一般不需要太长。对于中等规模的项目,这一过程通常会持续一至两周,对于大型项目,这一过程则有可能持续一个月甚至更长的时间。在这个阶段,交付工程师主要负责代码复审,而测试工程师则有责任及时反馈集中的测试中暴露出的问题,并与相关的开发人员联系、解决这些问题。

    技术上,代码冻结可以通过修改cvs中的配置来实现。不过,更好的办法是通过制度来保证代码冻结。

    交付工程——编码阶段的总结

    交付工程的好坏在某种意义上,是直接关系到用户利益的部分。前面已经说了相当多的关于软件开发过程中通过版本控制技术来提高开发效率的技巧和方法,这里我将继续说一说交付工程。

    前面提到了每日构建中在代码上适当地打上版本标记,以及在划分版本分支时在切分点上增加版本标记,这些对于交付工程都具有非常重要的意义。交付工程,在软件工程项目中是一个融合了代码复审和集中测试的重要阶段。


    交付工程的一般程序

    • 在项目进行到某个特定的阶段,交付工程师同其他主要的项目管理者商议决定宣布代码冻结和交付工程开始。
    • 交付工程师对代码进行集中的复审,而测试工程师则组织进行大规模的集中测试。
    • 交付工程过程中,交付工程师每隔一段时间发布一个“交付候选版本(Release Candidate)”,测试工程师跟随安装并测试这些交付候选版本。
    • 此间,测试工程师随时向交付工程师反馈问题,交付工程师将问题分类、整理,并约见相应的开发人员,予以解决。
    • 最终,某个“交付候选版本”被最终指定为交付版本(Release)。代码冻结结束,软件被交付给客户。

    一些读者可能已经注意到,交付工程的绝大多数任务事实上已经被融入了我前面所描述的开发过程——在编码阶段的整个过程中,代码复审、问题反馈和测试一直是持续地进行的,只是,在“交付工程”阶段,代码复审和测试被提升到了一个更为核心的地位,在这一阶段,开发的重要任务是查错和排错,而不再是将软件的功能推向一个崭新的水平。

    总结——基于版本控制、每日构建的编码过程

    前面我们已经介绍了通过引入版本控制系统改善软件工程中编码和测试阶段过程的一些方法。这些方法来自于我本人所参与的,以及通过一些其他途径了解到的实际项目的开发经验。文中介绍的内容以cvs (Concurrent Version System)为主,这是因为cvs比较容易得到(它本身是开放源代码软件),并且,比较成熟。另外,由于关于cvs本身的细节并没有涉及很深,因此,读者也很容易将这些经验套用在其他版本控制系统,如Bit Keeper, Perforce, Clear Case等等之上。

    引入版本控制、每日构建之后,项目管理人员和开发者可以明显地感受到以下改善:

    • 问题能够尽早暴露,并被解决。测试过程被渗透到开发过程中,有利于及早发现问题。日常的每日构建和测试意味着更多的测试者可以尽早地参加到测试工作中,并与开发团队并肩作战,及时地为他们提供第一手的资料;而最终的交付工程中进行的测试,只是验证软件的质量确实达到了设计要求,而不再肩负发现问题的重大责任,这意味着很难出现最后阶段发现严重问题而导致工期大大加长的尴尬局面。
    • 每个人做了多少工作在版本控制系统中能够非常有效地体现,这对于更客观地评价一位开发者的能力,明确奖惩和提高员工积极性,都有非常积极的意义。
    • 管理者能够随时观察工程的进度情况,并且,每日构建的到的版本也可以交付给客户,从而改善软件开发团队同客户之间的交流。
    • 版本控制系统记录的软件工程的进展,对于今后的开发将是一笔宝贵的财富。

    最后,让我们用图片的形式,重新描述一下上面的那些过程:

    图6. 日常开发过程

    图7. 交付工程

    这样,我们已经粗浅地讨论了关于并行版本控制技术如何改变软件开发过程的一些大致的方法,以及其中的一些先进思想。在文中我尽可能地回避了类似cvs系统的操作具体细节,并且,避开了一些cvs特有的功能。基本上,本文中提供的方法,特别是其中的一些思想,能够适应任何一种版本控制系统,因此,这些方法具有相当大的普遍意义。

    参考文献

    [1] CVS - 并行版本系统 http://www.cvshome.org
    [2] FreeBSD 4.4交付工程, Murray Stokely
    [3] CVS使用手册- CVS WinCVS CVSWeb CVSTrac,车东
    [4] 4.3BSD交付工程, Marshall Kirk McKusick, Michael J. Karels, and Keith Bostic

    作者简介

    李鑫,北京工业大学计算机学院2000级学生,目前担任计算机学院学生科协主席、放飞技术网技术总监。您可以通过电子邮件 ( delphij@frontfree.net 与他联系 )。

    版权声明

    本文版权归原作者和放飞技术网共同所有,转载本文必须同我们联系并获得同意(书面或PGP签署的许可)。


    文章来源:放飞技术网
     放生
     愚爱
     够爱
     触电
     白狐
     葬爱
     光荣
     画心
     火花
     稻香
     小酒窝
     下雨天
     右手边
     安静了
     魔杰座
     你不像她
     边做边爱
     擦肩而过
     我的答铃
     怀念过去
     等一分钟
     放手去爱
     冰河时代
     你的承诺
     自由飞翔
     原谅我一次
     吻的太逼真
     左眼皮跳跳
     做你的爱人
     一定要爱你
     飞向别人的床
     爱上别人的人
     感动天感动地
     心在跳情在烧
     玫瑰花的葬礼
     有没有人告诉你
     即使知道要见面
     爱上你是一个错
     最后一次的温柔
     爱上你是我的错
     怎么会狠心伤害我
     不是因为寂寞才想
     亲爱的那不是爱情
     难道爱一个人有错
     寂寞的时候说爱我