「08」- 利于集成的分支策略

  CREATED BY JENKINSBOT

(第六章)我们已经讨论过如何将需求拆分成多个可交付、可验收的用户故事,以及如何将它们安排到我们交付迭代的过程中。

接下来,本章将介绍研发团队通过源代码仓库,高效组织团队多人开发协作的方法,即「代码分支策略」。

分支策略的选择对持续交付的成本与效果有很大的影响。

8.1 版本控制系统的使用目的

版本控制系统(Version Control System)主要用于存储及追踪目录(文件夹)和文件的修订历史(这里的修订操作包括3类:新增修改和删除),从而让你能够回溯那些被纳入其管理范围之内的任意对象的任意一次修订。其最本质的作用是回答“4个W”:

	即在什么时间(When)
	修改了什么内容(What)
	是谁修改的(Who)
	为什么要修改(Why)

其中最后一个“W”是通过用户提交代码变更时书写提交注释(Comments)的方式提供的。

现在,版本控制系统已经成为团队合作共同交付软件过程中所用到的重要协作管理机制,是软件公司的基础设施。其目标是支持软件配置管理活动,追踪和记录多个版本的开发和维护活动。

根据版本控制系统的运作方式,目前市面上的主流版本管理系统被划分为「集中式版本控制系统」和「分布式版本控制系统」两种类型。

8.1.1 集中式版本控制系统

「集中式版本控制系统」的出现,解决了多人如何进行协同修改代码的问题。这类版本控制系统,都有一个单一的集中管理的版本控制管理服务器,保存所有文件的历史修订版本记录。团队成员之间的代码交换必须通过客户端连接到这台服务器,获取自己需要的文件。每个人如果想获得其他人最新提交的修订记录,就必须从集中式版本控制系统中获得。此时,客户端并没有整个集中式仓库保存的所有内容,而是根据用户的指定命令,一次仅能获取仓库中的某一次代码文件快照。集中式版本控制系统示意图如图所示:

当工程师修改了部分代码,但尚未完成全部工作时,如果希望将这个中间成果保存成临时版本,做个备份时,则他通常只有两种选择:一是复制一份到另一个本地目录中;二是直接提交到中央仓库。而直接提交未经过质量检验的半成品到中央仓库,可能会影响原有的功能,妨碍团队其他人工作。这种类型版本控制系统的典型代表是Subversion(SVN)

集中式版本控制系统有两点劣势:

	首先,在网络环境不佳的情况下,同步大量文件时会经常失败。2007年底,GoCD团队使用SVN作为版本管理工具。软件产品研发团队主要在北京工作,销售人员在美国,售后支持人员在印度班加罗尔,源代码仓库所用的服务器部署在美国芝加哥。有一次,开发人员从北京到班加罗尔出差,找了一台新计算机,想在当地公司办公室修改并提交代码,但手上没有源代码。于是,打算将源代码库从版本控制库检出到这台新计算机上。然而源代码库稍大,印度办公室的网络不稳定,前后花了数个小时,也没能将代码从中央服务器拉取到这台计算机上。
	其次,集中式版本服务器具有单点故障风险。假如 SubversionL服务器宕机一小时,那么在这一小时内,谁都无法提交更新,也无法从服务器获取文件。最坏的情况是,如果服务器的硬盘发生故障,并且没有做过备份或者备份不及时,则还会有丢失大量数据的风险。

那次事件以后,GoCD团队将源代码仓库从 Subversion迁移到了Mercurial,它是一款分布式版本控制系统,简称为Hg, Facebook也在使用它。

8.1.2 分布式版本控制系统

「分布式版本控制系统」与「集中式版本控制系统」的区别在于多个服务器共存,每个人的节点都是一个代码仓库,所有的节点都是平等的。在团队协作过程中,通常会指定某个节点作为团队的中央服务器,如图所示:

分布式控制系统的特点是:提交(commit)操作都是在本地进行而无须经过服务器,因此提交速度也更快。只有当需要向其他人或远程服务器做文件提交或同步时,才通过网络将其推送到远程仓库或从远程仓库拉取。因此,即使在没有网络环境的情况下,你也可以非常愉快地频繁提交更新。当有了网络环境的时候,再推送到远程的团队代码仓库。目前主流的分布式版本控制系统是「Git」。

前面提到GoCD团队工程师在印度遇到的情况,如果使用分布式版本管理仓库,即使网络不稳定,也可以比较方便地完成代码拉取操作,如图所示:

(1)Bob通过班加罗尔办公网络克隆(clone)一份Sa的代码仓库(Sara也在班加罗尔办公室)。
(2)Bob从芝加哥的中央仓库中拉取(pull)与本地仓库有差异的代码。
(3)Bob修改代码文件,并提交(commit)到本地仓库,产生一个新的文件版本。
(4)Bob将这个新的版本推送(push)至中央仓库。
(5)Sara即可从中央仓库拉取(pull)所有的差异代码。

8.1.3 版本控制系统中的基本概念

版本控制系统要解决的核心问题是多人协作过程中的文件版本管理问题。目前所有的版本控制系统中都有几个相似的概念,用于协调多人协作。在具体讨论多人协作模式之前,因为有多种版本控制系统,所以有必要对这些概念进行统一定义,以方便后续的讨论:

	代码仓库(codebase)是指一个包含一组文件所有历史修改信息的逻辑单位,通常用于保存有关一个软件产品或某一组件的所有文件信息记录。
	分支(branch)是指对选定的代码基线创建一个副本。人们可以对这个副本中的文件进行操作,而这些操作与原有代码基线的文件操作是互不影响的。
	主干(trunk/master)是一个具有特殊意义的分支(branch),通常在创建代码仓库时即由版本控制系统默认创建,每个代码仓库有且仅有一个这样的分支。其特殊意义在于其与软件的开发活动和发布方式紧密关联,例如,在SVN中以“trunk”命名的分支和Git中以“master”命名的分支都是主干分支,我们将在分支模型进一步讨论它们的特殊意义。
	版本号(revision)对应在某个分支(branch)上的一次提交操作,是系统产生的个编号。通过这个编号,你可以获取该次提交操作时点的所有文件镜像。在SVN中,它叫作revision,是一个连续变化的正整数。而在Git中,它是一个40位的散列值,类于“734713bc047d87b-65ae793478c50d3”这样一段字母与数字的组合。为了方便使用,Gi可以使用该散列值的前几个字符来识别某次提交,只要你提供的那部分SHA-1不短于4个字符,并且没有歧义即可。
	标签(tag)是某个分支上某个具体「版本号」的一个别名,以方便记忆与査找。你可以通过版本控制工具自身提供的命令来创建这个别名。
	头(head)是指某个分支上的最新一次提交对应的版本号。
	合入(merge)是指将一个分支上的所有内容与某个目标分支上的所有内容进行合并,并在该目标分支上创建一个新版本号。
	冲突(conflict)是指在「合入」操作时,两个分支上的同一个文件在相同位置上出现不一致的内容。通常需要人工介入,确认如何修改后,方可合入目标分支。

依据上面的定义,通过下面的字串记录方式可以唯一确定某个代码镜像:{代码仓库名}:{分支名}:{版本号},或者:{代码仓库名}:{分支名}:{标签}

8.2 常见分支开发模式

目前基于版本控制系统的开发模型,根据新功能开发以及版本发布所用的分支进行分类,主要有3种,它们分别是:

	(1)主开发,主干发布(Trunk-based Development & Release);
	(2)主干开发,分支发布(Trunk-based Development & Branch-based-Release);
	(3)分支开发,主干发布(Branch-based Development & Trunk-based-Release);

下面我们分别介绍一下它们各自的特点。

8.2.1 主干开发,主干发布

顾名思议,“主干开发,主干发布”是指:工程师向主干上提交代码(或者每个分支的生存周期很短,如数小时,或少于1天),并用主干代码进行软件交付(如图所示)。也就是说,所有新特性的开发,代码均提交到主干(trunk)上;当需要发布新功能时,直接将主干上的代码部署到生产环境上:

根据交付频率不同,可以分为「低频交付」和「高频交付」两类:

	「低频交付」类型常见于一些周期比较长的大型软件开发项目,也是一种最古老的软件开发模式,当时的IT行业是以数年或数月为一个交付周期。在低频工作模式下,其主干代码总是长时间处于不可用状态,只有在项目内所有功能的代码开发完成后,才开始进行软件联调和集成测试工作。在开发期间,版本控制系统的作用仅仅是确保代码不丢失,是纯粹的代码备份仓库。
	「高频交付」子类型是指代码库中的代码发布频率较高,通常每天都会发布一次,甚至多次。「高频交付」子类型常见于具有比较完备的交付基础设施(自动化配置构建、自动化测试、自动化运维、自动化监控与报警等)的互联网产品团队,通常也有快速缺陷修复能力,尤其适用于后台服务端产品形态(如Web网站或SaaS软件的后台服务)。

这种模式的优点在于:

	分支模型简单,因此分支管理工作量较少(如代码合并成本)。

这种模式的弱点在于:

	针对「低频交付」模式,其项目后期的缺陷修复阶段,并不是团队所有人都需要做缺陷修复,会有一定的资源浪费。针对这种情况,很多团队会采用后续介绍的“主干开发,分支发布”模式,下面会详细介绍。
	针对「高频交付」模式,由于多人向主干上频繁提交代码,其代码变动非常快。假如某个开发人员拉出一个私有开发分支,并在该开发分支上进行开发,开发完成后再合并回主干。此时,他只有两种工作方式:一是每天从主干上更新代码到他自己的分支上。此时该开发人员很可能每天需要一两个小时将主干上的代码与自己分支上的代码进行合并;二是不做每日更新,而是一段时间后(例如在分支上开发完成特性)之后,再向主干合并。此时,很可能由于主干上的代码变化太大,导致自己这个分支上的代码已经无法再合并回去了。

# 无法完成的“合并任务” #
2011年,百姓网(一个生活分类服务网站)的研发团队只有12名工程师。他们使用高频交付模式,每天早上7点做一次生产环境发布。为了对某个重要模块进行重大重构,其技术负责人曾经创建了一个专有分支。然而,一周以后,他不得不宣布放弃该分支的所有代码,因为其他工程师在主干上已经做了太多的改动,专有分支已经无法合并回主干。

“未开发完成的功能代码不能带入将要发布的版本里”曾被认为是一种最佳软件质量管理实践。然而,在这种高频交付模式下,很难再遵守这一实践。相反,应该允许提交未完成功能的代码,前提是不影响用户的正常使用和发布。为了使未开发完成的功能不影响发布质量,可以使用一些特殊技术管理手段(如开关技术或抽象分支方法等)来处理这类问题,当然,这些手段也会产生一定的管理开销(详细方法参见第12章)。与此同时,高频交付模式也要求质量保证活动能够做到既快速又全面。

8.2.2 主干开发,分支发布

“主干开发,分支发布”这种开发模式如图所示:

这种开发模式是指:

	开发人员将写好的代码提交到主干;
	当新版本的功能全部开发完成或者已经接近版本发布时间点的时候,从主干上拉出一个新的分支;
	在这个新的分支上进行集成测试,并修复缺陷,进行版本质量打磨。当质量达标后,再对外发布该版本;

其特点如下:

	主干代码提交活动频繁,对保障主干代码质量有较大的挑战;
	分支只修复缺陷,不增加新功能;
	新版本发布后,如果发现严重缺陷,而且必须立即修复的话,只要在该版本所属的分支上修复后,再次发布补丁版本,然后将分支上的修改合并回主干即可。也可以在主干上修复缺陷,然后将针对该缺陷的修复代码挑出来(cherry-pick)合并到该缺陷所在的分支上。Facebook的移动端产品开发流程就使用后面这种方式。

通常,发布分支的生命周期不应该持续时间过长,一段时间后应该终止该分支上的任何操作活动,例如,图8中v1.01发布点之后,V1.0分支应该结束。

在“主干开发、分支发布”模式下,从拉出发布分支开始,到分支代码达到可交付状态的时间周期可以作为评估主干代码质量的指示器,我们称之为“质量打磨周期(Branch Stabilization Time)”。打磨周期越短,说明主干代码质量越好。当质量打磨周期极短时,就可以转换到高频的“主干开发,主干发布”模式。当然,做到这一点并不容易,需要结合本书其他部分所描述的原则、方法与实践,方能游刃有余。

该模式的优势在于:

	与将要发布的新功能无关的人员可以持续工作在开发主干上,不受版本发布的影响;
	新发布的版本出现缺陷后,可以直接在其自己的版本发布分支上进行修复,简单便捷。即使当前开发主干上的代码已经发生了较大的变化,该分支也不会受到影响。

其不足在于:

	主干上的代码通常只能针对下一个新发布版本的功能开发。只要新发布版本的任何功能在主干上还没有开发完成,就不能创建版本发布分支,否则很有可能影响下一个发布的开发计划,开源项目在发布时间点以及特性功能方面的压力小一些,因此常常采用这种分支模型;

使用这种开发模式,对发布分支的数量不加约束,并且分支周期较长,很容易出现“分支地狱”倾向,这种倾向常见于“系列化产品簇+个性化定制”的项目,例如某硬件设备的软件产品研发的分支模型,如图所示:

该硬件设备最初只有一种类型,其类别定义为A,型号是x,对应软件的发布版本为Ax1.0。发布以后,客户提出了同类别不同型号的紧急需求,公司为了能够快速响应客户需求,从Ax1.0的产品分支上又拉出一个产品分支,名为Ay分支,其发布版本为Ay2.0。然后又在Ay的基础上开发了一个增强版Az,对应的分支及时间点如图所示。随后在Ax1.0上发现了一个严重缺陷,需要增发A1.01补丁版本。该缺陷在Ay分支和Az分支上也同时存在。因此就要将修复缺陷的代码移植到主干及Ay和Az两个分支。

该公司以这种管理模式支持了更多类别和型号的产品。如图中,公司开发了硬件产品B,而其软件版本是从主干分支上拉出,并先后发布了B1.0和B1.01。客户需要在B类型上也具有Ay2和Az3上的部分新功能特性,于是,公司决定从Ay2.01和Az3.0的分支上移植该新功能的代码到B分支上。

随着硬件类别和型号的不断衍生,研发团队效率越来越差。如图8-6中的虚线处所示,团队终将疲于在分支间移植代码和测试。

这与《大规模敏捷开发实践: HPLaserJet产品线敏捷转型的成功经验》一书中描述的HP激光打印机固件团队在2008年的状态相似。该团队仅有5%的资源用于新功能的开发,而各分支间移植代码会占用团队25%的时间,如图所示:

8.2.3 分支开发,主干发布

“分支开发,主干发布”模式是一种最为广泛应用的工作方式,如图所示:

这种模式是指:

	团队从主干上拉出分支,并在分支上开发软件新功能或修复缺陷;
	当某个分支(或多个分支)上的功能开发完成后要对外发布版本时,才合入主干;
	通常在主干上进行缺陷修复,质量达标后,再将主干上的代码打包发布。

这种模式的优势在于:

	在分支合并之前,每个分支之间的开发活动互相不受影响;
	团队可以自由选择发布哪个分支上的特性;
	如果新版本出现缺陷,可以直接在主干上进行修复或者使用hotfix分支修复,简单便捷,无须考虑其他分支。

它的优势也会导致不良后果,即为了分支之间尽量少受影响,开发人员通常会减少向主干合并代码的频率,从而推迟了发现各分支中代码冲突的时间,不利于及时进行代码重构,如图所示:

该主干上的代码中原有一个方法签名为handleY(int b)。Alice F和Bob各自领取了一个新功能的开发任务,并创建了对应的一个分支A和B,而且,在新功能开发完成之前,两人都没有向主干合并代码。为了完成自己的新功能, Alicex对handleY方法进行了修改,将其签名变更为handleY(int[] b, boolean c)。同时,Bob在自己的分支上也修改了handleY(int b)的内部实现。主干上发生了两次hotfix,两人都将主干的修改合入了自己的分支上。在这之后,Bob又从handleY(int b)中抽取了一个方法,签名为findX(int[] a)。此时 Alice开发完成了自己的新功能,将代码(从a1到a4)合入主干。当Bob打算提交代码到主干时,他需要将Alice的4次代码变更与自己的5次变更合并在一起。由于Alice修改较大,这个合并很可能成了非常大的包袱。Bob发现,Alice对handleY()进行了较大的重构。然而,这些还只是文本上的冲突,比较容易发现和修正。风险更大的则是语义上的冲突,即程序运行时的逻辑冲突。这类情况的发生会令团队成员进行代码重构的意愿大大下降,从而令代码的可维护性越来越糟糕。

如果分支过多,那么衍生出来的问题是:当某个分支的生命周期(即从主干拉出分支那一时刻至将其再次合入主干这段时间周期)过长,代码合并及验收成本会快速增加。成本增加的数量与其生命周期中合入主干的分支数量成正比。

若想成功使用这种模式,其关键点在于:

	让主干尽可能一直保持在可发布状态;
	每个分支的生命周期应该尽可能短;
	主干代码尽早与分支同步;
	一切以主干代码为准,尽可能不要在各特性分支之间合并代码。

另外,根据分支的存在周期和目的,“分支开发,主干发布”模式还可以进一步分为两种子类型,它们分别是「特性分支模型」和「团队分支模型」。

# 1. 特性分支模型 #

在开发过程中,允许多个开发分支同时存在,且每个分支对应一个功能特性的开发工作。当该特性开发完成后,立即合入主干,其他尚未合入主干的特性分支需要从主干拉取主干代码,与自己分支上的代码进行合并后,才能再合回主干。这种模式为特性分支模型如图所示:

	该模式的目的是:让团队更容易在“特性”这个层次上并行工作,同时保持主干的稳定可发布状态。其优势在于每次发布的内容调整起来比较容易。假如某个新功能或者缺陷在版本发布时间点之前无法完成,则不必合入主干中,也不会影响其他功能的发布时间点。
	但这种模式也有不足:如果特性分支过多,会带来比较多的合并成本。例如,每当某个特性分支开发完成打算合入主干时,都需要与主干的代码合并,并进行质量验证。一旦主干代码的质量验证通过,其他分支此时都应该从主干上拉取最近的通过质量验证的新代码。否则,如果在特性开发完成后再与主干合并,那么这种一次性合并会带来较大的工作量和质量验证工作。如图所示,特性2支需要合并特性1、3和4的代码。
	假如有多个特性同时开发完成,怎么办?下面是两种极端的做法:
		(1)所有已完成的特性分支一同向主干合并,然后再共同设法让主干代码达到可交付状态。这种方式通常会被特性团队排斥。因为共同合并后,多方代码交织在一起,出现的缺陷可能很难快速定位和快速修复。
		(2)所有已完成的特性分支排成队列,以顺序方式合入主干。每个特性分支向主干合入代码后,必须使主干上的代码达到可交付状态后,下一个特性分支才可以合入。这种方式通常是特性分支的常见做法,也是特性分支的优势所在。但所带来的问题是,多个特性分支按排队顺序进行合并,会导致排在队尾的特性分支等待较长的时间。
	如果想让特性分支模型更好地工作,需要做好下面的管理:
		(1)每个特性分支的生命周期都应该很短,分支上的开发和测试工作尽量在3天内完成。这要求尽可能将“特性”拆分成小需求(参见第6章)。
		(2)开发人员每天从主干上拉取最新的可交付代码,与自己的分支合并。
		(3)不要从其他特性分支上拉取代码。

# 某互联网公司的特性分支与排队上线 #

某互联网公司在2011年主要采用“分支开发,主干发布”的代码分支策略,如图8-11所示。主线为发布主干,而尚在开发或测试的特性代码都在各自的项目分支上。当项目在配置管理平台上立项,并获得一个3位版本号(如1.2.1)后,即可在当前主干的最新版本处拉出特性项目分支,进行功能开发工作。当开发完成后,进入联调自测阶段。该阶段由开发人员自行负责。当开发人员完成联调后,即可在配置管理平台上申请提测。


申请提测时,平台会自动生成一个4位版本号(如1.2.1.1),并自动通知测试负责人测试负责人马上安排测试人员进行测试。发现缺陷后,测试人员通知开发人员进行修改。开发人员修改完成后,再次申请提测,生成一个新的4位版本(如1.2.1.2)

			重复这个过程,直到质量达标,就进入等待合入的队列。如果等待队列中没有其他项目,那么开发人员即可将代码合入主线再次进行测试,质量达标后即可整理上线部署文档,编写上线操作步骤,上传到运维平台,交由业务运维人员进行操作。
			根据运维部门的规定,每周只有两个工作日可以进行上线操作。并且每次只允许一个项目上线。因此,曾出现过14个项目分支排队等待上线的情况。这14个已经开发完成的特性分支最快也需要7周的时间才能合并上线完成。还要祈祷每个分支的代码质量都非常好,每个分支上线时都不出现任何严重问题,不需要回滚和修改。

# 2. 团队分支模型 #

	团队分支可以看作是特性分支的一种特殊情况。也就是说,一组人一起在同一个分支上进行开发工作,而且该分支上通常包括一组相近或相关的特性集合的开发。由于是一组特性集合的开发,因此其分支存续时间比特性分支的存续时间长。

这种分支模型通常出现于规模较大的团队(在40人以上)共同开发同一款产品,团队被分成多个组,每组开发不同的系统组件。只有当一系列功能特性开发完成后,才对外发布新的软件版本,很容易成为典型的瀑布开发流程,如图所示:

团队分支模型在通信公司的产品研发或大型客户端软件产品研发中比较常见,例如第14章的案例中,团队研发管理模式改进之前,就使用这种开发模式。成功应用这种模式的关键在于:

	(1)每个团队尽早向主干合入高质量的代码,即使不马上发布;
	(2)向主干合入代码后,尽快使其达到可交付状态;
	(3)其他团队尽早从主干拉取可交付状态的代码,与自己分支上的代码合并。

8.3 分支模型的演化

基于前面三种基本分支模型,在实际工作中,根据不同的软件项目特点,以及不同团队各自工作习惯及软件演进历史,还衍生了很多其他形式的分支模型,例如常用于客户端套装软件的「三驾马车分支模型」,以及常见的「Gitflow分支模型」,「GitHubFlow分支模型」等。

8.3.1 三驾马车分支模型

三驾马车分支模型是指软件开发团队仅维护3个分支,分别是开发分支、预发布分支、发布分支,如图所示:

在2010年时, Chrome浏览器就使用这种分支开发模式。

「开发分支」就是所有开发人员提交代码的目标分支。当开发分支上有足够多的新功能(或者即将接近既定的发布日)时,将该分支中准备发布的那些功能分捡到(Cherry Pick)「预发布分支」上。

在这个「预发布分支」上只做缺陷修复、文档生成及、发布相关的工作,不做新功能开发。当团队认为该分支代码达到Alpha版本发布质量时,会发布一个Alphal版本。Alpha版本只给极少用户进行体验。然后再进一步发布Beta版本,它主要是为了让尝鲜用户进行版本体验,以便尽早发现存在的质量问题,及时修正。

当「预发布分支」上发布的Beta版本代码基本稳定后,即将这部分代码合入「发布分支」,并发布一个RC版本(Release Candidate)给一部分用户。如果RC版本质量稳定,即可作为「正式版本发布」。

8.3.2 Gitflow分支模型

Gitflows分支模型是目前很多企业所应用的分支模型,如图所示:

(1)Master – 正式版本的发布分支。
(2)Release – 用于质量打磨的予发布分支。如果Release分支质量达标,就可以将其合入Master分支,同似也需要将代码合入Developments分支。
(3)Development – 对新功能进行集成的分支。
(4)Feature – 为了开发某一功能特性,开发人员从Developments分支上拉出的分支。当特性开发完成后,合入 Developments分支。
(5)Hotfix – 如果已经发布的版本(如V0.1)出现了严重的缺陷,从Masters分支上V0.1版本标签处拉出Hotfix分支,在这个分支上进行缺陷修复,验证后再次合入Masters分支,并发布新的补丁版本V0.2。与此同时,由于Development分支上也有同样的缺陷存在,因此开发人员还要将Hotfix分支的代码合并到Developments分支上,以修复 Developments分支上的缺陷。

「Gitflow分支模型」是「特性分支模型」和「三驾马车分支模型」的组合。它的优点是每个分支的定义都明确且清晰。

而带来的问题是分支较多,具有「特性分支模型」的不足。

8.3.3 GitHubFlow分支模型

GitHubFlow分支模型名称来自于GitHub团队的工作实践。

它对开发者的开发纪律要求比较严格,对质量保障手段的要求也比较高。一个开发人员在开发新特性或修改缺陷时,其工作步骤如下:

	(1)从Master上创建一个新的分支,以这个特性或缺陷的编号命名该分支。
	(2)在这个新创建的分支上提交代码。
	(3)功能开发完成,并自测通过,创建Pull Request(PR)
	(4)其他开发人员对这个PR进行审查,确认质量合格后,合入 Master

如果特性分支的存在时间很短,则该模式可被认为是高频的“主干开发,主干发布”模式。

8.4 分支策略的选择

企业需要根据开发或维护的软件产品类型,结合发布频率,并考虑自身团队成员能力和基础设施水平如自动化测试程度、程序运行环境的管理水平、团队纪律性等,来确定适合自己的分支模型。

8.4.1 版本发布模式

版本发布的基本模式有3种:

	「项目制发布模式」(Project Release Mode)
	「发布火车模式」(Release Train Mode)
	「城际快线模式」(Intercity Express Mode)

无论哪种发布模式,都有相同的3个约束变量,即:

	交付时间点(schedule)
	特性数量(features)
	交付质量(quality)

在团队资源相对固定的情况下,只能对其中的两个因素提出固定的要求。例如,对发布的「交付时间」和「交付质量」提出固定要求后,那么该版本的「特性数量」也就相对固定了,如图所示:

# 1. 项目制发布模式 #

「项目制发布模式」是指在软件研发规划中,预先确定某一版本所需包含的功能特性数量,只有当该集合内的所有特性全部开发完成,并且达到相应的发布质量标准后,才能发布该版本。前后两次发布之间的时间间隔并没有明确的规定,而是在根据新版本要求的特性集合开发完成并达到发布标准后,对所需时间进行评估确定的。

这种模式是最古老的发布方式,其目标是:针对一个特定版本,在确定了版本中的「特性数量」和「交付质量」以后,再估计版本交付周期,这相当于固定了特性数量和质量要求,那么团队可能交付的时间点也就相对固定了。

「项目制发布」的优点:

	可以确切地知道每个版本包括哪些具体功能,有利于商业套装软件的售卖模式(卖版本副本和授权,收取软件维护费用,当包含有新功能的版本发行后,再向客户收取新版本的升级费用)。
	这也符合人们的安全生产习惯,即绝对不能把未完成的功能带到即将发布的版本中。

「项目制发布」的缺点:

	通常项目整个交付周期较长,参与人员众多。
	在版本研发周期中由于某些原因导致需求变更(如增加需求、修改原有需求实现方式或者进行需求置换)时,需要重新确定项目的交付时间,这会影响那些原本能够按期交付的需求。因为,这种项目制发布模式需要等所有需求全部实现完成后才能一起发布。

# 2. 发布火车模式 #

「发布火车模式」常见于大型套装分发类软件。大型传统软件企业通常有多条产品线,各产品线之间存在非常复杂的相互依赖关系。为了能够使各产品线协同发布,这些企业通常会为每条产品线都制订好每个版本的发布周期,即每个版本都像一列火车,事先计划好什么时间点发车。为了能够准时发布,要求所有参与到该版本开发的团队必须对齐该版本的各个开发阶段。这种严格的时间一致性要求是因为该产品线的时间变更会引起其他产品线的变更,而这些更改很可能影响共享的系统集成测试环境的分配。在大多数情况下,由于计划和集成依赖关系,因此发布火车设置为季度交付窗口,但通常不会超过10个月。

当公布这种火车时间表时,发布管理团队通常与负责各产品开发的团队进行提前沟通,讨论要发布哪些内容,有时甚至需要提前几个月的时间,并将其结论发布在企业版本表中,类似于图中LiberOffice中的发布火车的时刻表。提前几个月制订发布火车的时间表,目的是让各种业务和技术部门有足够多的时间进行预计划,以便做出依赖和影响的相关评估工作。

制订发布计划的活动是一个非常正式和结构化的过程,需要一些格式化数据,以确保参加发布火车的团队能够对正式发布的可行性做出判断。这些数据包括发布详细信息(相对标识、名称、部署日期、风险级别、发布类型企业、计划或投资组合)、整个生命周期中各个阶段及预定日期(如图所示)、每个阶段要完成的活动和任务、里程碑时间和质量要求以及负责管理发布火车的主要负责人。

该模式的好处在于:对企业来说,可以通过并行多列火车的方式,将突发需求排入某一列发布火车。用户可以提前体验最新产品版本所提供的新特性,而不必影响原有生产线上正在使用的旧版本。体验之后再决定是否将其应用于自己的生产环境中。即便已经决定将这个新版本用于自己的生产环境中,也可以等到这个新版本成熟稳定之后再这么做。在这种模式下,如果参与团队的人数较多,沟通协调成本就会较高。

# 3. 城际快线模式 #

「城际快线模式」是指在发布模式三要素中,固定其中的「时间」和「质量」两个维度,且时间周期相对较短(如一周,一天,或更少),针对那些在发布时间点已达到固定质量标准的特性进行一次发布。它与「发布火车模式」的区别在于两点:一是发布周期间隔较短,通常在两周以内;二是负责特性开发的团队可以自己选择搭乘哪列城际快线,而不必提前很长时间确定下来。

这种模式常见于提供互联网服务或SaaS服务的软件公司。其好处在于减少了团队及角色之间的协调成本。因为每个人都事先知道每次发布的具体时间点,所有工作任务都可以按这个时间点提前进行协调。而且,即使某个特性没有及时赶上最近的一次版本发布,团队也确切地知道这个特性是否可以在下一次发布间点对外发布。例如,Facebook的Web网站在2013年的部署推送频率已达到每天发布两次,每周发布一次大版本。其分支发布策略如图所示

每个周日从主干上拉一个发布分支,自动化测试验证通过以后,即在公司内部人员开放(在公司内访问,直接重定向到 latest.facebook.com)。运行过程中如果出现缺陷,可以在主干上修复,然后分捡到(CherryPick)发布分支上。发布分支上的代码每天两次更新 latest.facebook.com,供公司员工开发使用。如果版本稳定,就向外部用户发布,同样是每天两次。据报道,自2017年开始, Facebook公司网站的分支发布策略已经从一天发布两次的“主干开发,分支发布”模式改变为平均每天发布9到10次的“主干开发,主干发布”模式。

这种城际快线模式的优点有两个:一是每个人都非常清楚各个时间点;二是更加聚焦于生产质量。

当然,也有其不足之处,由于发布频率较高,因此未完成功能的代码也会一同发布出去,对于代码提交质量的要求较高,需要强大的质量基础设施保证。

使用这种城际快线模式,间隔多长时间发出一趟合适呢?我的建议是:在不影响用户体验、不增加成本且合规的前提下,让发布周期尽可能缩短到令你感到有些紧张的节奏。例如,原来每个月发布一个版本,现在可以把两周作为一个目标。

8.4.2 分支策略与发布周期的关系

「分支策略」与「版本发布周期」之间有一定的相关性如图所示。

通常,软件开发周期极长的“项目制”团队和软件发布频率极高的“城际快线式”团队会使用“主干开发,主干发布”的分支策略。

而次之的团队会使用“主干开发,分支发布”的分支策略。

处于它们之间的团队会使用“分支开发、主干发布”的分支策略。

当然,这并不是绝对的,其中会有很大的重叠部分,通常会受团队成员人数、产品架构和质量保障基础设施状况的影响。

「项目制发布模式」不会消失。毕竟每个新产品在完成第一个可推广的1.0版本前,都需要这样一个首次启动过程。目前仍旧有很多传统IT企业采用「项目制发布模式」。

「城际快线模式」是“持续交付2.0”所提倡的模式。越来越多的企业开始使用这种「城际快线模式」。即使在那些目前的版本发布周期较长的企业中,也常常在「项目制发布模式」中套用「城际快线模式」,即在项目周期内加入固定时间的迭代,并要求在每个迭代结束时都能得到可交付状态的产品。这里的可交付状态是指软件可以正常运行,且已完成的软件特性达到发布质量标准,并非商业化发布。

一般来说,当发布周期缩短到一定程度后,“主干开发模式“更具有优势,因为分支开发模式的合并成本会成为短周期发布的障碍。如果发布周期短于两周,软件团队就应该毫不犹豫地转向“主干开发模式”。

8.5 小结

每种分支策略都有其各自的优点和挑战。并且,它对发布频率和每次发布的效率也有较大的影响。目前的发展趋势是:软件发布频率越来越高,发布周期越来越短。硅谷顶级互联网公司多采用“主干开发”或高频的分支模型。一个企业到底选择哪种分支策略,需要根据团队的具体情况来决定如果相关的配套条件(如软件架构、人员能力和工具平台的成熟度)不足,那么,盲目提高发布频率、缩短发布周期会造成不必要的损失。

“持续交付2.0”提倡鼓励持续集成的分支策略,因此,选择分支模型的原则有以下几条:

	(1)分支越少越好,最好只有一条主干
	(2)分支生存周期越短越好,最好在3天以内。
	(3)在业务允许的前提下,发布周期越短越好。

企业管理者应该遵循“持续交付2.0”的思想、理念与原则,制订合理的改善目标,促进公司IT交付能力不断提升,才能够跟上时代的发展。