1、Robert和Pat的争吵导致了大量的代码变动,他不得不重新审核代码,而Jason的代码合并引发的新问题又让他不得不分神去帮助Jason进行调整。最后的结果是,软件开发的成本是预期的2.4倍,发布时间也拖后了不少。我并不是在开玩笑,上面所讲的是一个发生在那家安全软件公司的真实故事。他们的技 术经理介绍,在施行了规范的开发制度,以及启用并行版本控制系统之后,他们认为开发达到了一个全新的水平。并行版本控制系统本身并没有产生任何代码,但由 于使用这样的系统,开发的效率被大大地提高了。所谓版本控制其实并不是什么复杂的概念。对于开发活动的绝大多数参与者来说,版本控制系统在某种意义上能够帮助他们做好开发过
2、程中的记录工作,并且,通过保存文件在不同时期的版本, 交付工程师和代码复审员能够很容易地缩小搜索问题代码的范围,而程序员则可以通过这样的系统更好地并行协作。一般来说,源代码的版本控制系统能够实现以下一些最基本的功能:保存任意一个源代码文件的不同版本记录修改者、修改原因当两个用户同时修改一个文件时,尽可能地自动合并修改;在不能合并时,给出提示比较不同版本之间,或与本地副本之间的差异获取最新版本的全部源代码供测试,并允许回退到所保存的源代码的任意版本创建代码分支,便于软件发布和后期维护(后面将会提到);新的代码可以合并到这些分支中。对不同的源代码给出标记,方便日后审查访问控制:阻止XX的修改和查阅
3、我们知道,技术不是解决一切问题的灵丹妙药,但是谁也不会否认大规模的机械化生产的效率高于人拉肩扛的手工业作坊,一旦运用得当,技术将极大地改善我们的工作和生活。我们可以看到,上面的功能有效地解决了TJRP开发组所面临的绝大部分问题,例如:由于能够同时修改代码,并获取对方的修改,Pat和Robert能够有效地、尽早地进行沟通促进开发者之间的交流,每一个修改都必须给出原因,并记录提交者测试和连调可以尽早开始,避免模块之间的不兼容在最后阶段被暴露出来而阻碍发布不同开发者的修改能够及时合并,并避免由于版本不一致导致的冲突代码复审可以针对某一代码分支进行,从而,允许一些开发者持续地开发下一个版本,而稳定的代
4、码则可以交付给用户更进一步,以管理者的角度,还有了一些额外的好处,如:每日构建和测试能够让项目经理更好地把握工程的进度谁作了多少工作,谁工作的更出色,可以在版本控制系统中清晰地体现分工明确,通过访问控制,可以避免不了解整个代码体系的开发人员偶然的错误修改导致的全盘崩溃更重要地,版本控制系统中将保持大量的开发经验,这对于一个开发团队来说是一笔无价的财富我们可以看到,上述改进集中地体现了一个重要的思想,即:及时沟通以预防问题的出现;尽早发现、尽早解决问题;明确奖惩制度,激发开发人员的积极性。下面我们将以非常常见的版本控制系统cvs1为例,介绍并行版本系统一些基本的使用原理。代码提交和同步从upda
5、te和commit说起每当我们开始一个新的修改之前,首先要做的是从代码库中提取出一份最新的副本(通过update操作完成);在本地修改、粗调之后,则应尽快将代码提交回代码库(通过commit操作完成)。基本的update和commit操作流程如下图所示:图1. cvs update和commit这两项操作也解决了日常开发大约80%的问题。绝大多数情况下,这部分的工作是相当简单的,除非出现两个开发者同时修改同一个文件的情况,例如,两个开发者同时地修改了同一个文件的同一个版本,这种情形称为冲突:图2. cvs并行开发中的冲突可能开发者B的动作比较快,或者,修改的东西比较简单,于是他首先提交。在A、
6、B从代码库中提取代码时,最新版本是1.1,于是,B提交的版本被版本控制系统命名为1.2。但不久,A想要提交代码,版本控制系统将拒绝他的提交,因为他的修改基于代码的1.1版,而目前的最新版本已经是1.2了。cvs提供了自动合并功能,允 许在两个人修改的不是同一行代码的前提下,自动合并本地修改和最新的代码,当然,如果赶上两个人同时修改同一行代码的情况,cvs也会非常“聪明”地把两 个“英雄所见略同”的地方合并。但如果两个人恰好都修改了同一行代码,而且改的不一样怎么办?cvs会告诉后一个提交的开发者发生了这样的情况,并且要求他解决问题。代码中存在的差异将以标记出来,以方便进行修改。简单说来,当发生冲
7、突时,我们通常约定由后一个提交者解决冲突当然,他可以选择忽略这些冲突,但这些操作都会被记录,更何况,统计显示,同时将一行代码修改为两种不同的样子这样的情况在实际开发中很少出现。于是,修改流程继续,如下图:图3. 开发者A解决冲突,并提交极端情况下,可能出现多个开发者同时修改同一个文件的问题。这一问题基本上可以按照上述的方法解决。当然,为了避免发生这样的情形,在设计的时候就应该让每个人工作的代码尽可能地不重叠。下面是非常基本的cvs update/commit操作规范:简单的cvs操作约定修改文件之前首先update。这意味着修改时的版本尽可能新,一旦发生冲突,解决它的工作量会比较小。及时com
8、mit。本地代码与代码库中的代码差异越小,别人合并的难度也就越小(他们有比较大的概率能够拿到新的版本)将不同的功能单元修改分开commit。一方面,这样做能够尽早地commit,减少别人合并的难度;另一方面,由于cvs提供了回退到先前版本的能力,一旦由于某项功能修改造成问题,也很容易将那次修改的内容,而不是整个修改回退到正常的代码。同一功能涉及的所有代码一次commit。不希望将涉及同一功能修改的代码分开commit,因为这会给日后的追踪带来麻烦。先调试后提交。这将减少别人不会因为同步了中间结果引发问题,甚至发生提交冲突的可能。写清commit log(提交日志)。cvs中允许保存commit
9、 log,在这里可以写为什么进行代码的修改,以及进行了什么样的修改,清楚的commit log能够帮助其他开发者在不仔细阅读代码的情况下了解修改的内容,从而极大地提高开发效率;另一方面,这些日志对于开发者自己,以及整个开发团队,都是非常宝贵的财富。同步代码(update)和提交代码(commit)占到了cvs日常操作的80%以上。从上面的介绍我们可以看出,仅仅依靠这两项非常简单的功能,cvs就能极大地改善开发流程,并提高软件工程的可控性。简单地说:全体开发者使用同一个中央代码库,从而消除了由于来回复制文件导致的不一致。发生冲突时,后提交的开发者必须解决它。这名开发者能够知道是谁引入了冲突,他可
10、以自己解决冲突,也可以与引入冲突的开发者商量如何解决冲突。测试可以贯穿编码过程的始终,任何时候引入的新问题都能够被及时追踪、快速定位,从而更有效地解决。由于存在中央代码库,代码审核人员和每日构建人员能够及时地了解代码是否存在问题,并帮助项目经理保证开发进度。有助于帮助开发人员养成严谨的工作习惯规则要求他们将尽可能地提交正确的代码,并且每个修改必须写commit log进行说明。有助于建立更公平的工作质量评估机制。 cvs能够记录每个人完成的实际工作量,包括他们因为修正问题等等所作的劳动,以及他们完成代码的质量情况。这样,管理者能够为优秀的开发人员提供更好的工作机会、报酬,等等,这对于鼓舞整个团
11、队的士气、提高开发人员的工作积极性都是非常有益的。促进开发人员之间的交流。尽管cvs本身无法代替交流,但commit log,以及cvs系统获取任意版本之间差异的能力,能够帮助开发人员了解对方的想法,并促进他们共同提高。降 低程序开发人员的门槛。由于提供了许多非常方便的协同开发手段, 使用cvs能够减少协同开发所需要的磨合期,同时,不同层次的开发者之间由于能够进行经常性的沟通,从而把编码过程从技能型工作向熟练性工作又推进了一 步。这意味着高层次的开发人员能够去进行更能发挥他们特长的工作,而新手则可以很快地融入到日常的开发活动中来,从而提高劳动生产率,降低开发成本。编码过程中的沟通纽带commi
12、t mailcvs是一项开放性很强的工具,它可以被非常容易地订制。一般来说,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
13、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:20)、涉及的代码库名字(FreeBSD src repository)、修改过的文件(sys/geom的geom_io.c)以及commit log。最后,commit log还提到了提交后文件的最新版本(1.50),修改规模
14、(+3 -6)以及代码的实际路径。实现上面的功能并不复杂,实际上,您只需要下载一套经过定制的FreeBSD cvs代码库(压缩包不超过40KB),并作少量的调整,就能够直接使用这些功能(我们将在不久以后发布这些内容)。进行这些订制甚至不需要基本的 Perl和C/C+常识就能够完成当然,我想这样的常识对于软件开发人员来说,并不算是很高的要求。commit mail可以通过邮件列表发给全体开发者。许多大的软件公司,以及开放源代码团体,都采用这样的方式来协调开发活动。日常测试坚持每日构建传统的软件工程中,测试发生在连调之后。这么做的理论依据是,测试依赖于一份一致的、至少能够正常编译并启动的代码。而这
15、个条件在连调之前是无法满足的。然而在有了版本控制系统(如, cvs)之后,连调变成了开发中的日常行为。代码几乎在每一时刻都处于高度的一致状态,甚至在许多时候,代码会处于可用状态,从而为测试创造非常有利的条件。许多大型开发团队会使用一台甚至多台被称作TinderBox的机器来完成每日构建和测试。简单的每日构建流程如下:测试工程师,或代码复审员从代码库中提取一份代码的快照相关人员在TinderBox上进行编译编译的任何错误被追踪,测试工程师或代码复审员将回退代码到上一次能够成功编译的点,并与此后进行代码提交的其他开发者进行联系,解决问题测试工程师对于代码进行测试其中,第一、二步是可以通过脚本定时、
16、自动完成的,不需要人工干预。第三步中的编译错误在软件开发中偶尔会发生(这可能来自于冲突合并时引发的问题,但由 于开发人员的本地测试,这种文体不会是经常性的),习惯上,这些错误会由代码复审员去追踪和处理,并交给相关的开发人员解决。测试工程师可以将编译好的版本交付给一个测试组,甚至用户去进行测试。测试工程师可能随时发布软件的“快照”版本。实际上在许多大公司中,每日构建是非常“家常便饭”的事情。我们看到的Internet Explorer版本,如6.0.2600或者类似6.0 Build 2600中的2600的意思就是这份代码之前已经经历了2600次每日构建操作(当然,中间肯定进行过不少修改,而且,
17、也不排除这个2600是故意凑整得到的,但总之,他们进行了相当多的日常构建工作)。在软件开发的后期,由于发布的迫在眉睫,每日构建很可能会演化为持续构建,即,每次commit触发一次构建操作。所有问题立即得到反馈。为了支持每日构建或持续构建,比较理想的方法是采用Makefile完成构建操作。对于Unix系统,make工具通常是pmake(BSD Make)或gmake(GNU Make);对于Visual C+,则是nmake。大的开发团体通常使用一组脚本来完成所有的make操作,而对于中小型项目,手工地使用类似VC+这样的IDE本身的构建功能也是可以接受的。基本的每日构建规范如下:基本的每日构建
18、和测试注意事项避免在存在开发人员大规模地进行commit时提取快照。此时提取的中间结果很可能有问题,并导致每日构建从头做起。通常,测试工程师和代码复审员在多数开发人员下班的时候开始每日构建,因此,每日构建有时也被称作每晚构建(Nightly Build)。标记(tag)每日构建中能够正确编译的版本。这将减少第二天每日构建中的麻烦。及时通知造成问题的开发人员,即,所涉及代码的提交者。某些公司甚至要求员工开着手机和寻呼机,以保证工期。作为commit mail的有效补充,许多项目开发组会建立邮件列表来传递一些相关的信息。测试日报通常会发给整个开发团队的参加人员,此外,保留一个出现过的问题记录,对于
19、测试环节也会有相当大的好处这些问题在随后被反复测试,以保证最终的RELEASE不出现这些问题。每日构建并不是可有可无的工作,作为日常测试的重要手段,每日构建能够有效地帮助管理者了解工程进度,帮助开发者尽早发现问题,同时,也会促进开发组中的交流。有效的版本控制版本标记、代码分支三个文件的“最新版本”分别是1.5, 1.3, 1.4,但最新版本不一定是我们需要的。在这种情况下,版本控制系统提供了一个非常重要的机制版本标记。例如,我们目前已经确认三个文件的1.4, 1.3, 1.4组合在一起能够正常运行,于是我们在三个文件的这些版本上标注标记TAG_1,如下图:图4. TAG_1标记被打到三个文件的
20、不同版本上需要说明的是,标记是可以被移动的。这意味着一旦发现标记打错了,可以把标记移动到别的位置。但在实践中,标记往往同另一个非常重要的版本控制机制代码分支一起使用。在详细讨论标记(tag)的重要意义之前,我们先来看看代码分支时什么:图5. 比较复杂的情形,一个正在开发的项目中的某个文件,已经完成了2.0和2.1的发布; 其中,BP是指划分分支的切分点(Branchpoint)所谓代码分支是版本控制中的一个非常关键的概念。当开发到某个阶段的时候,可以交付一个版本,而主要的开发者则把精力投入到最新版本的开发中。第一个交付 分支(2.0)中的一些问题,以及引入的新功能随后在RELENG_2分支中被
21、修正,公司决定发布2.1版本;此后,2.x中的问题继续在 RELENG_2中被修正,而一些安全更新,则被合并到2.1-RELEASE中(RELENG_2_1)。图5展示的是一个文件上的版本分支。实际的软件工程项目的源代码会由大量文件组成,尽管在本质上分支是针对每一个文件说的,但在被标注了同一分支名称的文件,就像版本标记一样,能够表达一组特定版本文件的集合。cvs的版本分支功能有一个很大的缺陷,即,大量文件的切分点(Branchpoint, 即某一个分支最初的版本号)在cvs中很难被指定(cvs支持按某一分支、某一特定时间、某一特定版本来提取文件,但通常不同的文件的版本号并不统一,特 别是在大型
22、项目中,肯定有某些文件因为被提交的次数很多,而版本号很“高”的情况)。为了消除这个缺陷,在实践上,我们采用版本标记与分支结合的方法, 即,在划分新的分支之后,在这一分支的这些文件的版本上增加一个版本标记。例如:对于软件的2.0版,在划分时,将切分出RELENG_2(2.x),RELENG_2_0(2.0)两个分支,而这时的文件的版本,同时被打上一 个RELENG_2_0_0_BP的标记。这样一来,在以后比较版本时,我们可以使用RELENG_2_0_0_BP来指定这个版本。当不同的分支又增加 了许多修改之后,这个标记将极大地减轻代码复审员的工作量。注意,代码分支并不仅限于版本上的用法。事实上,基
23、于同一代码基础的多个不同的软件也可以采用代码分支的方法进行开发。而最终,这些代码还可以合并为一个。您可能已经注意到最左边的一组版本序列:1.1, 1.2, 1.3, 1.4, 1.5. 1.6。在cvs中,这一序列被称为“主分支(MAIN Branch)”。尽管并非必须,但习惯上,主分支通常是活跃的开发分支。在这个分支中,人们不断地引入最新的特性,当然,不可避免地,这也可能引发一些 问题,而这些引入主分支的问题在随后将被追踪、修订。经过一段时间之后,被“沉淀”下来的代码可以进入另一个叫做“稳定分支”的代码系。这样的开发模式通常被称作“多头并进”模式,这样的模式在许多开放源代码的软件开发中非常常
24、见,例如,Linux的单、双号版本、FreeBSD的 -STABLE和-CURRENT2,等等。在一般的商业软件开发中,这种模式也相当常见,特别是在大公司的开发中。拥有多头并进这一能力对于大型软 件的开发尤为重要 ,因为大型软件很可能包含相当多的模块,通过版本控制,问题能够很容易地被整个开发团队追踪。多头并进的开发环境中,开发人员可以在粗略熟悉了某个分支的代码体系的情况下参与开发或维护,这意味着,即使某个代码分支的维护人员突然离去,其他人也不 用担心通盘阅读不同分支的代码可能造成的理解困难,换言之,对于新的维护人员的要求被降低,从而,软件的开发和维护过程能够更为有序地进行。据我所知,FreeB
25、SD的软件开发过程极大地得益于多头并进的开发模式。下面简单地介绍一下FreeBSD所采用的软件开发模式:案例:FreeBSD开发模式中对于多头并进的应用FreeBSD包括了两个主要的开发分支:4-STABLE和5-CURRENT,以及若干安全分支。其中,4-STABLE(RELENG_4分支)代表的是FreeBSD 4.x系列的开发,其关注的焦点是系统的稳定性和性能;5-CURRENT(HEAD分支)代表的是FreeBSD 5.x系列的开发,它关注的焦点是尽可能多地引入最新的操作系统特性,全新的设计思想,等等。除此之外,还有一些被称作安全分支的分支,它们分别代表FreeBSD 2-STABLE
26、, 3-STABLE, 4.6-RELEASE, 4.7-RELEASE, 4.8-RELEASE以及即将推出的4.9-RELEASE等等,但这些分支完全不引入任何新的特性,只有安全更新能够被加入到这些分支中。FreeBSD的“安全分支”是一个非常重要的概念,在FreeBSD的开发中,这些分支基本上只由一个包括了少量开发者(目前只有两人)的,被称为 “FreeBSD安全官”的团队维护。对于很多用户来说,他们并不在乎操作系统是否拥有新的特性他们不愿意尝试新版本的软件,因为现有的系统工作的非 常好。这些用户使用“安全分支”的FreeBSD操作系统,因为他能够提供必要的安全更新,而操作系统特性并不会
27、因此发生变化(无论这种变化是否能够改进 性能,或提供一些眩目的功能,甚至支持新的硬件,因为用户的系统已经放在那里了)。CURRENT分支走的是另外一个极端。所有的新特性,一旦被特定的工作人员(committer)测试通过(大的变化需要核心团队,即core team的批准,但这种情况并不是很多),就允许被引入CURRENT分支。尽管CURRENT分支在绝大多数时间都能够被正确地编译,但引入新特性有时会不可避免地带来一些问题,例如硬件适应性问题。在这两个极端之间,有一条中间路线,即STABLE分支。在CURRENT分支中提交的代码通常会被指定一个MFC(Merge from -CURRENT)时间
28、,在这个时间之后,如果没有人提交关于代码的问题,则这些代码会被引入STABLE分支。这样,STABLE分支的代码几乎都是经过相当长时间测试的代码,对于大多数用户来说,STABLE分支是一个很好的选择。一般来说,FreeBSD中的代码会经历下面的历程:代码被引入CURRENT分支相关开发者获得来自用户的反馈; 在确认基本没有问题的情况下,代码被引入STABLE分支大多数最终用户使用STABLE分支的代码来支持他们的计算机我们可以看到,上面的开发模式同时照顾到了开发者和用户群体的利益。一方面,活跃的开发不会因为影响到了大量的普通用户而遭到指责;另一方面,在开发分支(CURRENT)的代码经过一段时间被引入STABLE,最终用户能够得到那些新的操作系统功能。事实上,上述开发模式已经被证明是相当成功的。由于开发过程中每一天都有相当多的人对新的CURRENT和STABLE分支的代码进行测试,因此,在最近的几年中,FreeBSD的开发一直呈现着良好的态势。交付
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1