写在开头:本文摘抄自《持续交付2.0:业务引领的DevOps精要》第8章:利于集成的分支策略

本章的核心在于通过代码分支策略来高效组织团队多人开发协作,从而达到持续交付的目的。

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

版本控制系统(Version Control System)主要用于存储及追踪目录(文件夹)和文件的修改历史(新增、修改和删除)。其最本质的作用是回答4个W,即什么时间(When)、修改了什么内容(What)、是谁修改的(Who)以及为什么要修改(Why)

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

8.1.1 集中式版本控制系统

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

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

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

  1. 首先,在网络环境不佳的情况下,同步大量文件时会经常失败。

  2. 其次,集中式版本服务器具有单点故障风险。

8.1.2 分布式版本控制系统

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

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

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

版本控制系统要解决的核心问题是多人协作过程中的文件版本管理问题。目前所有的版本控制系统中都有几个相似的概念,用于协调多人协作:

  • 代码仓库(codebase):是指一个包含一组文件所有历史修改信息的逻辑单位,通常用于保存有关一个软件产品或某一组件的所有文件信息记录。

  • 分支(branch):是指对选定的代码基线创建一个副本。人们可以对这个副本中的文件进行操作,而这些操作与原有代码基线的文件操作是互不影响的。

  • 主干(trunk/master):是一个具有特殊意义的分支(branch),通常在创建代码仓库时即由版本控制系统默认创建,每个代码仓库有且仅有一个这样的分支。其特殊意义在于其与软件的开发活动和发布方式紧密关联,例如,在SVN中以“trunk”命名的分支和Git中以“master”命名的分支都是主干分支。

  • 版本号(revision):对应在某个分支(branch)上的一次提交操作,是系统产生的一个编号。通过这个编号,你可以获取该次提交操作时点的所有文件镜像。在SVN中,它叫做revision,是一个连续变化的正整数。而在Git中,它是一个40位的散列值。为了方便使用,Git可以使用该散列值的前几个字符来识别某次提交,只要你提供的那部分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行业是以数年或数月为一个交付周期。在低频工作模式下,其主干代码总是长时间处于不可用状态,只有在项目内所有功能的代码开发完成后,才开始进行软件联调和集成测试工作。在开发期间,版本控制系统的作用仅仅是确保代码不丢失,是纯粹的代码备份仓库。

高频交付子类型是指代码库中的代码发布频率较高,通常每天都会发布一次,甚至多次。高频交付子类型常见于具有比较完备的交付基础设施(自动化配置构建、自动化测试、自动化运维、自动化监控与报警等)的互联网产品团队,通常也有快速缺陷修复能力,尤其适用于后台服务端产品形态。

这种模式的有点在于分支方式简单,因此分支管理工作量较少(如代码合并成本),但也存在弱点。例如,针对低频交付模式,其项目后期的缺陷修复阶段,并不是团队所有人都需要做缺陷修复,会有一定的资源浪费。

针对高频交付模式,由于多人向主干上频繁提交代码,其代码变动非常快。假如某个开发人员拉出一个私有开发分支,并在该开发分支上进行开发,开发完成后再合并回主干。此时,他只有两种工作方式。一是每天从主干上更新代码到他自己的分支上。此时该开发人员很可能每天需要一两个小时将竹竿上的代码与自己分支上的代码进行合并。二是不做每日更新,而是一段时间后(例如在分支上开发完成特性)之后,再向主干合并。此时,很可能由于主干上的代码变化太大,导致自己这个分支上的代码已经无法再合并回去了。

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

8.2.2 主干开发,分支发布

这种开发模式是指:

  • 开发人员将写好的代码提交到主干

  • 当新版本的功能全部开发完成或者已经接近版本发布时间点的时候,从主干上拉出一个新的分支

  • 在这个新的分支上进行集成测试,并修复缺陷,进行版本质量打磨。当质量达标后,再对外发布该版本

其特点如下:

  • 主干代码提交活动频繁,对保障主干代码质量有较大挑战

  • 分支只修复缺陷,不增加新功能

  • 新版本发布后,如果发现严重缺陷,而且必须立即修复的话,只要在该版本所属的分支上修复后,再次发布补丁版本,然后将分支上的修改合并回主干极客。也可以在主干上修复缺陷,然后将针对该缺陷的修复代码挑出来(cherry-pick)合并到该缺陷所在的分支上。

在“主干开发、分支发布”模式下,从拉出发布分支开始,到分支代码达到可交付状态的时间周期可以作为评估主干代码质量的指示器,我们称之为“质量打磨周期(Branch Stabilization Time)”。打磨周期越短,说明主干代码质量越好。当质量打磨周期极短时,就可以转换到高频的“主干开发,主干发布”模式。

该模式的优势在于:

  • 与将要发布的新功能无关的人员可以持续工作在开发主干上,不受版本发布的影响

  • 新发布的版本出现缺陷后,可以直接在其自己的版本发布分支上进行修复,简单便捷。即使当前开发主干上的代码已经发生了较大的变化,该分支也不会受到影响。

其不足在于:

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

  • 使用这种开发模式,对发布分支的数量不加约束,并且分支周期较长,很容易出现“分支地狱”倾向

8.2.3 分支开发,主干发布

这种模式是指:

  • 团队从主干上拉出分支,并在分支上开发软件新功能或修复缺陷

  • 当某个分支(或多个分支)上的功能开发完成后要对外发布版本时,才合入主干

  • 通常在主干上进行缺陷修复,质量达标后,再将主干上的代码打包发布

这种模式的优势在于:

  • 在分支合并之前,每个分支之间的开发活动互相不受影响

  • 团队可以自由选择发布哪个分支上的特性

  • 如果新版本出现缺陷,可以直接在主干上进行修复或者使用hotfix分支修复,简单便捷,无须考虑其他分支

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

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

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

  • 让主干尽可能一直保持在可发布状态

  • 每个分支的生命周期应该尽可能短

  • 主干代码尽早与分支同步

  • 一切以主干代码为准,尽可能不要在各特性分支之间合并代码

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

  1. 特性分支模式
    在开发过程中,允许多个开发分支同时存在,且每个分支对应一个功能特性的开发工作。当该特性开发完成后,立即合入主干,其他尚未合入主干的特性分支需要从主干拉取主干代码,与自己分支上的代码进行合并后,才能再合回主干。这种模式为特性分支模式。
    该模式的目的是:让团队更容易在“特性”这个层次上并行工作,同时保持主干的稳定可发布状态。其优势在于每次发布的内容调整起来比较容易。假如某个新功能或者缺陷在版本发布时间点之前无法完成,则不必合入主干中,也不会影响其他功能的发布时间点。
    但这种模式也有不足:如果特性分支过多,会带来比较多的合并成本。假如有多个特性同时开发完成怎么办?下面是两种极端的做法:

    1. 所有已完成的特性分支一同向主干合并,然后再共通设法让主干代码达到可交付状态
    2. 所有已完成的特性分支排成队列,以顺序方式合入主干

    如果想让特性分支更好地工作,需要做好下面的管理:

    • 每个特性分支的生命周期都应该很短

    • 开发人员每天从主干上拉取最新的可交付代码,与自己的分支合并

    • 不要从其他特性分支上拉取代码

  2. 团队分支模式
    团队分支可以看作是特性分支的一种特殊情况。也就是说,一组人一起在同一个分支上进行开发工作,而且该分支上通常包括一组相近或相关的特性集合的开发。由于是一组特性集合的开发,因此其分支存续事件比特性分支的存续时间长。
    团队分支模式在通信公司的产品研发或大型客户端软件产品研发中比较常见。成功应用这种模式的关键在于:

    • 每个团队尽早向主干合入高质量的代码,即使不马上发布
    • 向主干合入代码后,尽快使其达到可交付状态

    其他团队尽早从主干拉取可交付状态的代码,与自己分支上的代码合并

8.3 分支模式的演化

基于前面3种基本的分支模式,还衍生了很多其他形式的分支模式,例如常用于客户端套装软件的三驾马车分支模式,以及常见的Gitflow分支模式、GitHubFlow分支模式等。

8.3.1 三驾马车分支模式

三驾马车分支模式是指软件开发团队仅维护3个分支,分别是开发分支、预发布分支和发布分支。

开发分支就是所有开发人员提交代码的目标分支。当开发分支上有足够多的新功能(或者即将接近既定的发布日)时,将该分支中准备发布的那些功能分拣到(Cherry Pick)预发布分支上。在这个预发布分支上只做缺陷修复、文档生成及与发布相关的工作,不做新功能开发。当团队认为该分支代码达到Alpha版本发布质量时,会发布一个Alpha版本。Alpha版本只给极少用户进行体验。然后再进一步发布Beta版本,它主要是为了让尝鲜用户进行版本体验,以便尽早发现存在的质量问题,及时修正。当预发布分支上发布的Beta版本代码稳定后,即将这部分代码合入发布分支,并发布一个RC版本(Release Candidate)给一部分用户。如果RC版本质量稳定,即可作为正式版本发布。

8.3.2 Gitflow分支模式

Master分支是正式版本的发布分支

Release分支是用于质量打磨的预发布分支。如果Release分支的质量达标,就可以将其合入Master分支,同时也需要将代码合入Development分支

Development分支是对新功能进行集成的分支

Feature分支是为了开发某一功能特性,开发人员从Development分支上拉出的分支。当特性开发完成后,合入Development分支

如果已经发布的版本出现了严重的缺陷,从Master分支上V0.1版本标签处拉出Hotfix分支,在这个分支上修复缺陷,验证后再次合入Master分支,并发布新的补丁版本V0.2。与此同时,由于Development分支上也有同样的缺陷存在,因此开发人员还要将Hotfix分支的代码一直到Development分支上,以修复Development分支上的缺陷

Gitflow分支模式是特性分支模式和三驾马车分支模式的组合。它的优点是每个分支的定义都明确且清晰,而带来的问题是分支较多,具有特性分支的不足。

8.3.3 GitHubFlow分支模式

这种开发分支模式的名称来源于GitHub团队的工作实践。它对开发者的开发纪律要求比较严格,对质量保障手段的要求也比较高。一个开发人员在开发新特性或修改缺陷时,其工作步骤如下:

从Master上创建一个新的分支,以这个特性或缺陷的编号命名该分支

在这个新创建的分支上提交代码

功能开发完成,并自测通过,创建Pull Request(简称为PR)

其他开发人员对这个PR进行审查,确认质量合格后,合入Master

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

8.4 分支策略的选择

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

8.4.1 版本发布模式

版本发布的基本模式有3种。分别是项目制发布模式(Project Release Mode)、发布火车模式(Release Train Mode)和城际快线模式(Intercity Express Mode)。无论哪种发布模式,都有相同的3个约束变量,即交付时间点(schedule)特性数量(features)交付质量(quality)。在团队资源相对固定的情况下,只能对其中的两个因素提出固定的要求。

  1. 项目制发布模式
    项目制发布模式是指在软件研发规划中,预先确定某一版本所需包含的功能特性数量,只有当该集合内的所有特性全部开发完成并且达到相应的发布质量标准后,才能发布该版本。前后两次发布之间的时间间隔并没有明确的规定,而是在根据新版本要求的特性集合开发完成并达到发布标准后,对所需时间进行评估确定的。
    这种模式是最古老的的发布方式,其目标是:针对一个特定版本,在确定了版本中的特性数量和质量标准时候,再估计版本交付周期,这相当于固定了特性数量和质量要求,那么团队可能交付的时间点也就相对固定了。
    项目制发布的好处在于:可以确切地知道每个版本包括哪些具体功能,有利于商业套装的售卖模式(卖版本副本和授权,收取软件维护费用,当包含有新功能的版本发型后,再向客户收取新版本的升级费用)。同时,这也符合人们的安全生成习惯,即绝对不能把未完成的功能带到即将发布的版本中。
    不足之处在于:通常项目整个交付周期较长,参与人员众多。在版本研发周期中由于某些原因导致需求变更(如增加需求,修改原有需求实现方式或进行需求置换)时,需要重新确定项目的交付时间,这会影响那些原本能够按期交付的需求。因为,这种项目制发布模式需要等所有需求全部实现完成后才能一起发布。

  2. 发布火车模式
    发布火车模式常见于大型套装分发类软件。大型传统软件企业通常由多条产品线,各产品线之间存在非常复杂的相互依赖关系。为了能够使各产品线协同发布,这些企业通常会为每条产品线都制定好每个版本的发布周期,即每个版本都像一列火车,事先计划好什么时间点发车。为了能够准时发布,要求所有参与到该版本开发的团队必须对齐该版本的各个开发阶段。这种严格的时间一致性要求是因为该产品线的事件变更会引起其他产品线的变更,而这些更改很可能影响共享的系统集成测试环境的分配。在大多数情况下,由于计划和集成依赖关系,因此发布火车设置为季度交付窗口,但通常不会超过10个月。
    当公布这种火车时间表时,发布管理团队通常与负责各产品开发的团队进行提前沟通,讨论要发布哪些内容,有时甚至需要提前几个月的事件,并将其结论发布在企业版本表中。提前几个月制定发布火车的时间表,目的是让各种业务和技术部分有足够多的事件进行预计划,以便做出依赖和影响的相关评估工作。
    制定发布计划的活动是一个非常正式和结构化的过程,需要一些格式化数据,以确保参加发布火车的团队能够对正式发布的可行性做出判断。这些数据包括发布详细信息(相对标识、名称、部署日期、风险级别、发布类型-企业、计划或投资组合)、整个生命周期中各个阶段及预定日期、每个阶段要完成的活动和任务、里程碑事件和质量要求以及负责管理发布火车的主要负责人。
    该模式的好处在于:对企业来说,可以通过并行多列火车的方式,将突发需求排入某一列火车。用户可以提前体验最新产品版本所提供的新特性,而不必影响原有生产线上正在使用的旧版本。体验之后再决定是否将其应用于自己的生产环境中。即便已经决定将这个新版本用于自己的生产环境中,也可以等到这个新版本成熟稳定之后在这么做。在这种模式下,如果参与团队的人数较多,沟通协调成本就会较高。

  3. 城际快线模式
    城际快线模式是指在发布模式三要素中,固定其中的时间和质量两个维度,且时间周期相对较短(如一周,甚至一天或更少),针对那些在发布时间点已达到固定质量标准的特性进行一次发布。它与发布火车模式的区别在于两点:一是发布周期间隔较短,通常在两周以内;二是负责特性开发的团队可以自己选择搭乘哪列城际快线,而不必提前很长时间确定下来。
    这种城际快线模式的优先有两个,一是每个人都非常清楚各个时间点,二是更加聚焦于生产质量。
    当然,也有其不足之处,由于发布频率较高,因此未完成功能的代码也会一同发布出去,对于代码提交质量的要求较高,需要强大的质量基础设施保证。

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

分支策略与版本发布周期之间有一定的相关性。通常,软件开发周期极长的“项目制”团队和软件发布频率极高的“城际快线式”团队会使用“主干开发,主干发布”的分支策略。而次之的团队会使用“主干开发,分支发布”的分支策略。它们之间的团队会使用“分支开发,主干发布”的分支策略。当然,这并不是绝对的。其中会有很大的重叠部分,通常会受团队成员人数、产品架构和质量保障基础设施状况的影响。

项目制发布模式不会消失。毕竟每个新产品在完成第一个可推广的1.0版本前,都需要这样一个首次启动过程。

城际快线模式是“持续交付2.0”所提倡的模式。在项目周期内加入固定时间的迭代,并要求在每个迭代结束时都能得到可交付状态的产品。这里的可交付状态是指软件可以正常运行,且已完成的软件特性达到发布质量标准,并非商业化发布。

8.5 小结

每种分支策略都有其各自的优点和挑战。并且,它对发布频率和每次发布的效率也有较大的影响。目前的发展趋势是:软件发布频率越来越高,发布周期越来越短。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐