【VC++开源代码栏目提醒】:网学会员鉴于大家对VC++开源代码十分关注,论文会员在此为大家搜集整理了“协作开发中的质量保证技术——并行版本控制、每日构建和交付工程 - 计算机教材”一文,供大家参考学习
?? 摘要 ?? 问题的提出 ?? 并行版本控制——多人协作开发的有效保障 ??
代码提交和同步 ?? 编码过程中的沟通纽带——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能够有效地、尽早地进行沟通 ?? 促进开发者之间的交流,每一个修改都必须给出原因,并记录提交者 ?? 测试和连调可以尽早开始,避免模块之间的不兼容在最后阶段被暴露出来而阻碍发布 ?? 不同开发者的修改能够及时合并,并避免由于版本不一致导致的冲突 ??
代码复审可以针对某一
代码分支进行,从而,允许一些开发者持续地开发下一个版本,而稳定的
代码则可以交付给用户 更进一步,以管理者的角度,还有了一些额外的好处,如: ?? 每日构建和测试能够让项目经理更好地把握工程的进度 ?? 谁作了多少工作,谁工作的更出色,可以在版本控制系统中清晰地体现 ?? 分工明确,通过访问控制,可以避免不了解整个
代码体系的开发人员偶然的错误修改导致的全盘崩溃 ?? 更重要地,版本控制系统中将保持大量的开发经验,这对于一个开发团队来说是一笔无价的财富 我们可以看到,上述改进集中地体现了一个重要的思想,即: 下面我们将以非常常见的版本控制系统——cvs1为例,介绍并行版本系统一些基本的使用原理。
——updatecommit 每当我们开始一个新的修改之前,首先要做的是从
代码库中提取出一份最新的副本通过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会告诉后一个提交的开发者发生了这样的情况,并且要求他解决问题。
代码中存在的差异将以ltltlt和gtgtgt标记出来,以方便进行修改。
简单说来,当发生冲突时,我们通常约定由后一个提交者解决冲突——当然,他可以选择忽略这些冲突,但这些操作都会被记录,更何况,统计显示,同时将一行
代码修改为两种不同的样子这样的情况在实际开发中很少出现。
于是,修改流程继续,如下图: 图3. 开发者A解决冲突,并提交 极端情况下,可能出现多个开发者同时修改同一个文件的问题。
这一问题基本上可以按照上述的方法解决。
当然,为了避免发生这样的情形,在设计的时候就应该让每个人工作的
代码尽可能地不重叠。
下面是非常基本的cvs update/commit操作规范: cvs ?? update ?? commit ?? commitcommitcvs ?? commitcommit ?? ?? commit logcvscommit logcommit 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工具通常是pmakeBSD Make或gmakeGNU Make;对于Visual C,则是nmake。
大的开发团体通常使用一组脚本来完成所有的make操作,而对于中小型项目,手工地使用类似
VC这样的IDE本身的构建功能也是可以接受的。
基本的每日构建规范如下: ?? commitNightly 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_22.x,RELENG_2_02.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和-CURRENT2,等等。
在一般的商业软件开发中,这种模式也相当常见,特别是在大公司的开发中。
拥有多头并进这一能力对于大型软件的开发尤为重要,因为大型软件很可能包含相当多的模块,通过版本控制,问题能够很容易地被整个开发团队追踪。
多头并进的开发环境中,开发人员可以在粗略熟悉了某个分支的
代码体系的情况下参与开发或维护,这意味着,即使某个
代码分支的维护人员突然离去,其他人也不用担心通盘阅读不同分支的
代码可能造成的理解困难,换言之,对于新的维护人员的要求被降低,从而,软件的开发和维护过程能够更为有序地进行。
据我所知,FreeBSD的软件开发过程极大地得益于多头并进的开发模式。
下面简单地介绍一下FreeBSD所采用的软件开发模式: FreeBSD FreeBSD包括了两个主要的开发分支:4-STABLE和5-CURRENT,以及若干安全分支。
其中,4-STABLERELENG_4分支代表的是FreeBSD 4.x系列的开发,其关注的焦点是系统的稳定性和性能;5-CURRENTHEAD分支代表的是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分支中提交的
代码通常会被指定一个MFCMerge from -CURRENT时间,在这个时间之后,如果没有人提交关于
代码的问题,则这些
代码会被引入STABLE分支。
这样,STABLE分支的
代码几乎都是经过相当长时间测试的
代码,对于大多数用户来说,STABLE分支是一个很好的选择。
一般来说,FreeBSD中的
代码会经历下面的历程: ??
代码被引入CURRENT分支 ?? 相关开发者获得来自用户的反馈 在确认基本没有问题的情况下,
代码被引入STABLE分支 ?? 大多数最终用户使用STABLE分支的
代码来支持他们的
计算机 我们可以看到,上面的开发模式同时照顾到了开发者和用户群体的利益。
一方面,活跃的开发不会因为影响到了大量的普通用户而遭到指责;另一方面,在开发分支CURRENT的
代码经过一段时间被引入STABLE,最终用户能够得到那些新的操作系统功能。
事实上,上述开发模式已经被证明是相当成功的。
由于开发过程中每一天都有相当多的人对新的CURRENT和STABLE分支的
代码进行测试,因此,在最近的几年中,FreeBSD的开发一直呈现着良好的态势。
Release Engineering—— 许多参与过大型项目开发的读者可能都经历,至少是听说过特性冻结和
代码冻结这样一个概念。
所谓“特性冻结”实际上是一个开发者之间的约定,在这个阶段中,不再允许添加新的功能。
特性冻结Feature Freeze通常在一个开发分支Development Branch跃变为交付分支Release Branch的时候开始。
之所以需要特性冻结,是因为增加新的特性很有可能引入新的问题,而这将给
代码复审带来沉重的负担,甚至导致一次不成功的最后交付。
当然,对于那些已经明确地定义了特性表的小型软件工程项目例如,传统的瀑布开发模型来说,特性冻结没有什么意义,因为在这些工程中,详细设计完全是在编写
代码之前进行的,这意味着,
代码将要写成什么样子已经在详细设计中明确地定义。
但在实际.