3 个案例,详解如何选择合适的研发模式 | 研发效能提升 36 计


策划 &编辑|雅纯
上一讲,我们详细介绍了4种常见的分支模式及其优劣对比。本文我们将根据不同的团队场景,分析如何选择适合团队的研发模式。
研发模式选择看什么

研发模式的选择与产品形态、发布方式、团队规模、协作成熟度密切相关。比如团队规模很小,协作成熟度很高,就直接用主干开发。类似于 Web 服务端的开发,可以做到持续部署,可以选择 GitHub—Flow,或者是 TBD。
如果你的团队规模比较大,需要开发的时候做相应的隔离,再看协作的成熟度。如果一般就用 GitHub—Flow,成熟度很高就用 TBD。
另外,有一些研发场景有固定的发布窗口,它是按版本的方式去发布,我们建议有相应的 release 分支去做隔离。实际过程中我们应该尽可能地根据自己实际的产品和团队情况,来去制定合适的分支规则。

举一个例子,这张图的上半部分是需求价值流。可以看到他们开发的时候是一个需求一个需求地做,做完一个需求再做另外一个。因此可以看出这是一种持续交付需求的方式,针对一个需求会做相应的代码变更。

与之相应的分支模式为 GitHub—Flow。
在做特性开发的时候,只要做特性之间的隔离,但是可以做到持续发布,所以直接在 Master 上发布就好,因为是在 Master 上去发布,所以不会同时有两个发布存在。
可以看到分支模式和发布流水线之间是强相关的。发布流水线会基于不同的分支来去做相应地事情。比如特性分支发生变化后,针对特性分支做相应的集成和验证,通过后再合到 Master 上去做集成,完成集成后做相应的 SIT(系统级别的验证),然后再做部署。因此分支模式和发布流水线之间是强相关的。
这是持续发布的方式。

版本制发布模式常见于客户端的发布。例如 iOS 或安卓,因为有一定的发布节奏。和上面的持续发布模式相比多了一个 release 分支,用来做版本发布用。从整体来看分支模式比较简单。不建议用 Git—Flow,可以对其适当进行裁剪。
研发模式的目的是减少代码协作当中的冲突,减少等待。代码之间的协作冲突有两种,一是开发过程中的冲突和隔离,另一个是发布过程中的隔离,所以组合方式无非是:分支开发和主干发布、分支开发和分支发布、或是主干开发和分支发布等几种。

这个例子初看和 Git—Flow 一样,但是相对于 Git—Flow,它有两个变化。首先,它没有 release 分支,它的发布表现为在主干上打 Tag。第二,它的 hotfix 不合回主干,而是直接在 hotfix 上打 Tag 进行发布。这样它就少了 release 分支,少了 hotfix 和 master 之间的同步。
整个分支模式有这样一个特点,它有四种分支:feature 分支、develop 分支、master 分支和 hotfix 分支,其中 develop 和 master 是长期分支,feature 和 hotfix 是短期分支。开始开发的时候会拉一个 feature 分支,合并完成后消亡掉,如果是热修复,会拉一个 hotfix 分支,hotfix 分支永远是从 tag 上创建的,之后创建 tag,分支消亡。
所以长期分支就两个,大部分的情况下 hotfix 就是 feature 分支,整个流程比 Git—flow 简化很多。
分支模式实践案例分析
分支模式是和产品的形态和团队是强相关的,以下是几个实践案例。
1、P2P 直播 CDN 产品

第一个案例是 P2P 直播 CDN 产品,左边是它的架构图,分为一个客户端和一个服务端。客户端是有多端的,比如手机、路由器、机顶盒等,每一种端的发布形式是不一样的。终端,客户端和服务端之间有两条通信链路,一条是视频数据的链路,另外一条是控制数据的链路。
服务端包括了三部分,控制面、用户面,和数据、运营、监控等服务。每一块都包含多个具体的应用。团队成员物理上在一起,协作紧密,工程能力还可以,有单元测试和功能自动化保证,基本上可以做到比较快的测试反馈。
它有两种应用:一个是服务端应用。一般 golang、C++都是通过源码级构建依赖,运行时依赖配置中心,共 30 个左右应用,一次发布一个应用,每个应用是独立发布,所以不存在发布的依赖性和编排问题。
另外一个是客户端,一个代码多端构建,无运行依赖,有的可以热更新,有的需要通过应用市场发布,比如说 iOS,所以发布频率不太一样,会导致长期有多个版本存在。那么,怎样针对服务端和客户端去做研发模式的设计?

首先看服务端,服务端是一个看上去比 TBD 还简单的模式,因为人很少,服务拆得足够小,几乎每个服务同时只有 1—2 个人在修改。这样的情况下就没必要再用 release 分支,直接在主干上开发。基本上一个服务一个库,而且这个服务拆得粒度很小,平均一个人大概是 3、4 个应用,这个服务是很小的。
这样的情况下,它会有一些自己的纪律,比如因为要保证多端和客户端多版本,代码需要保证向前兼容,同时代码是直接 Push 在 Master 分支上的,不存在合并等问题。在 Master 上一旦代码提交会有对应的测试,如果测试失败,提交者需要在一小时内修复。在 Master 上创建 Tag 即会视为一次发布。
如果出现问题,在最新代码上修复,永远发布最新的版本。这就是服务端的流水线,所以如果有类似的团队建议可以尝试一下,基本上来说如果做好纪律,可以做到很高效地发布。

客户端基本上就是 TBD 的模式。平时还是主干开发,代码在主干上集成,但是要发布的时候会拉一个 release 分支,因为客户端的发布和升级比较困难,需要做足够多的发布前验证,这个情况下就需要 release 分支去保护。同时因为它会同时存在多个版本,所以需要在 release 分支上做 bugfix。
但是,release 分支还是要保持活跃数尽量地少,所以一般只关注最新的活跃的 release 分支。这样 TBD 是一个非常合适的模式,针对发布它会做隔离,另外,因为一个版本需要保持一定时间的维护,所以需要一个相对长期的 release 分支。
2、基础网络产品

它是在软件层面做的虚拟化网络产品,很多外部做一些底层产品的公司会遇到这样类似的产品。整个产品研发 50 人左右,分为 5 个团队,每个团队大概 10 个人。团队间协作需求很高,一般都是一起发布、一起集成,但开发的时候是很多人一起开发的。
整个团队工程能力中等,有单元测试但是没有其他测试的保护,后面的测试主要是靠具体的环境去测,开发的语言是 C+和 C++为主,部署到物理机或者虚拟机上。应用是一份代码,多端构建,需要应对多种的硬件和操作系统,底层依赖 Hypervisor 和硬件。部署时可能需要停机,因为网络问题不是总能做到热更新,一次部署一个应用,发布顺序有要求。
如果有多个应用,应用间的发布有编排顺序,它的发布周期很长,通常几个月发布一次,同时会存在两个都在发布的版本,比如一个版本发布了 80%,另外一个版本发布了 10%。

这个产品的 release 分支会更长,它的版本需要固定下来,要有明确的 Tag。所以 Master 不能直接提交,永远指向最后一个已发布的版本,但是整个开发其实是拉 release 去做,这个 release 可能会比较久。
在这边做完以后,在 release 做完整的测试和评审然后发布,完成后合进 Master。这个类似于项目制,一个 release 相当于一个项目,从 Master 上创建出来以后,所有的开发和发布的工作都在这个 release 分支上,这个 release 分支就相当于项目的版本。发布完后 release 分支进入维护阶段。Master 在这里是作为一个稳定基线来管理的。
3、金融安全产品

这个产品一份代码提供两种交付形态,包括 SaaS 和私有化交付形态。整个应用架构比较简单,包含一些后台服务和 API 入口,以及一个管理和配置用的控制台。后台服务里面 API 会调很多其他的服务,比如设备指纹、指标计算、数据服务等。这是典型的大数据场景,包括很多人工智能的产品都是类似的架构。整个团队在 150 人左右,它的特点是前端、算法、后端、测试都有专门的职能团队,但是没有运维。
团队之间通常需要协作才能完成一个要求,一般来说不会有一个需求落在某一个团队,工程能力一般,没有单元测试和自动化功能测试的守护,基本上是靠后续的人工测试来去保证质量。
整个技术栈是以 Java 为主,K8s 部署方式。另一个特点是二方包依赖较多,snapshot 和 release 版本都有。运行时应用间有较强依赖,比如在 API 依赖了设备指纹,API 依赖了指标计算,类似这样的依赖其实很多。
整个应用数大概是 20 个,一个应用很多人协作,一次发布往往是一组应用或者是一个应用,SaaS 版本落后私有化版本较多。

它和 Git—Flow 有点类似,区别是没有 Develop 分支,release 分支用来做了临时的集成分支,Master 是发布分支,永远指向最新可发布版本。
作为私有化产品,有固定的版本节奏,一般一个月发布一个版本,于是每个月会拉一个 release 分支来做这个月的 Feature 分支的集成。集成完以后会合回 Master 去做发布,发布完打一个 Tag。
所以在这里的 release 分支相当于一个迭代分支。整个测试是比较长的周期,同时也要维护多个版本,因此会有多个并行的 release 分支存在。
通过这几个例子可以发现,我们需要根据团队和产品的特征来确定它的分支模式。在这些分支模式里面,我们都尽可能地减少分支,让分支的维护成本低一点,因为每多一个分支意味着多一份维护成本。
除此以外,还有一些其他的场景,比如集成过程中,集成进去以后发现集成分支出现问题,需要把相应地代码摘出来。很多的 Feature 分支合在一起,合并进去以后想再摘出来就很难。这种情况其实也可以用分支,比如临时的集成分支解决。阿里内部的研发工具 Aone,有一个分支模式叫 Aoneflow,就可以解决相应的问题。
很多时候分支是可以很灵活地去使用的,但是灵活使用也会给程序员带来特别多理解和维护成本。我们的建议是分支越简单越好,另外尽可能地减少程序员的关注度,只关注在自己开发的分支上就好。这里给出几点建议:
单主干:一个代码仓库应该保证有且仅有一个主干分支。像 Gitflow 里面 Develop 和 Master 就比较迷惑。
最少长期分支:避免冲突的前提下,尽量减少长期分支的数量
Promotion(晋级):代码的提交应该是逐级合并,如 Feature–Develop-Master,是逐步地 Promotion 的过程。
发布不可变:发布的版本是不可变且可回溯的,可以根据 Commit 来追溯到你最早的源头。
自动化事件触发:分支的持续集成过程应该是自动化的,且通过代码提交事件或制品变更事件自动触发。
总结
团队研发本质上是一个异步的、延迟协作的过程,随着产品复杂度和团队复杂度的增加,协作成本快速上升。
研发模式的本质是为高效交付需求,研发团队围绕代码库的一系列行为约束。
通过分支进行隔离,避免冲突;通过小批量频繁提交,减少等待。
控制分支需要考虑最大化生产力及最小化风险。
分支的选择需要综合团队规模、协作成熟度、产品交付形态几个要素。
下一讲,我们将进入可信发布篇,敬请期待。

点击下方链接,免费体验云效流水线 Flow。
https://www.aliyun.com/product/yunxiao/flow?channel=yy_rccb_36

版权声明: 本文为 InfoQ 作者【阿里云云效】的原创文章。
原文链接:【http://xie.infoq.cn/article/d51fa3193e4d9d8b065af27bd】。文章转载请联系作者。
评论