组织反模式:构建功能分支


构建功能分支是一种版本控制策略,其中开发人员在共享主干之前将他们的更改提交给源代码存储库的各个远程分支。通过集中式版本控制系统(VCSs),构建功能分支是可能的,例如SubversionTFS,但它通常与分布式版本控制系统相关联,例如GitMercurial–尤其是GitHubGitHub Flow

在构建功能分支主干被认为是所有以前发布的工作的完美代表,新的功能是在从主干切割的短期功能分支上开发的。开发人员将提交对其功能分支的更改,完成后,这些更改或者直接合并到主干中,或者由另一个开发人员使用诸如GitHub Pull Request。自动化测试随后在Trunk上执行,测试人员手动验证变更,新特性被发布到产品中。当生产缺陷发生时,它被固定在从主干上切下的释放分支上,并在生产释放时合并回来。

考虑一个提供在线公司账户服务的组织,其代码库由一个实践构建功能分支的团队维护。最初需要两个特性——F1计算和F2注销——因此F1和F2特性分支从主干中被切断,开发人员将其更改提交给F1和F2。

Organisation Antipattern - Build Feature Branching - 1

另外两个功能——F3银行详细信息和F4会计期——然后开始开发,F3和F4功能分支机构从主干网上删除,开发人员承诺在F3和F4。F2由非F2开发人员在代码审查后完成并合并到主干中,一旦测试在主干+ F2上签署,它就被发布到生产中。F1分支发展到包含计算重构,这短暂地打破了F1分支。

Organisation Antipattern - Build Feature Branching - 2

在F2中发现了生产缺陷,因此在从主干+ F2中截取的发布分支上进行了F2.1注销修复,并在修复处于生产状态时将其合并回来。F3被认为是完整的,并被非F3开发人员合并到主干+ F2 + F2.1中,并在测试之后被发布到产品中。随着计算重构范围的扩大,F1分支进一步发展,F4分支因会计期间提交系统的架构更改而暂时中断。

Organisation Antipattern - Build Feature Branching - 3

当F1完成时,修改代码的数量意味着由非F1开发人员进行长时间的代码审查,并且在F1可以被合并到主干+ F2 + F2.1 + F3之前需要一些返工,之后它被成功地测试并发布到生产中。在F4进行的架构更改也意味着一个耗时的代码审查,并由一个非F4的开发人员合并到主干+ F2 + F2.1 + F3 + F1中,在测试之后,F4投入生产。然而,在F4发现了一个生产缺陷,在一个发布分支上对会计期间进行了一个F4.1修正,一旦缺陷被解决,就合并到干线+ F2 + F2.1 + F3 + F1 + F4。

Organisation Antipattern - Build Feature Branching - 4

在本例中,F1、F2、F3和F4都在自己的功能分支上享受不间断的开发。对短期功能分支的强调降低了合并到主干中的复杂性,代码审查的使用降低了主干构建失败的概率。然而,F1和F4的特征分支不受限制地增长,直到它们都需要一个复杂的,有风险的合并到主干。

公司客户服务团队可能会使用Promiscuous Integration以降低将每个功能分支合并到主干中的复杂性,但这并不能防止相同的代码在不同的分支上偏离。例如,将F2和F3整合到F1和F4将简化F1和F4后来合并到Trunk,但它不会抑制F1和F4的生成Semantic Conflicts如果他们修改了相同的代码。

Organisation Antipattern - Build Feature Branching - 4 Promiscuous Merge

这个例子展示了构建特性分支如何在软件交付中插入一个昂贵的集成阶段。具有混杂集成的短期功能分支应该确保最小的集成成本,但事实是功能分支的持续时间只受开发人员规程的限制——即使是出于最好的意图,规程也很容易丢失。一个特性分支可能只持续一天,但是通常它会增长到包括错误修复、可用性调整和/或重构,直到它持续的时间比预期的更长,并且需要复杂地合并到主干中。这就是为什么构建特征分支通常与Continuous Integration,这要求每个团队成员至少每天在Trunk上集成和测试他们的更改。构建功能分支团队的每个成员不可能每天都与主干合并,因为这太容易出错,虽然使用构建服务器来持续验证分支完整性是一个好的步骤,但这并不等同于整个系统的共享反馈。

构建功能分支提倡一个功能分支的开发人员应该让另一个开发人员检查他们的变更并将其合并到主干中,并且这个过程由像GitHub拉请求这样的工具很好地管理。但是,每次代码评审都代表了一个充满延迟机会的交接期——开发人员可能会等待评审人员的可用性,评审人员可能会等待开发人员的上下文,开发人员可能会等待评审人员的反馈,和/或评审人员可能会等待开发人员的返工。正如艾伦·凯利所说code reviews lose their efficacy when they are not conducted promptly“,当代码审查缓慢时,功能分支会变得陈旧,中继合并的复杂性也会增加。一种更好的技术是Pair Programming,这是一种持续的代码审查形式,只需最少的返工。

要求从事正交任务的开发人员分担将一个特性集成到主干中的责任会稀释责任。当一个开发人员对一个特性分支拥有权限,而另一个开发人员负责它的主干合并时,两个人自然会觉得对整体结果不太负责,也不太想获得关于该特性的快速反馈。正是因为这个原因,构建特征分支经常导致吉姆·肖尔所说的Asynchronous Integration,其中功能分支的开发人员在请求评审后立即开始下一个功能的工作,而不是等待成功的评审和主干构建。从短期来看,异步集成会导致代价更高的构建失败,因为原始开发人员必须中断他们的新特性,并切换回旧特性来解决主干构建失败。从长远来看,这会导致较慢的主干构建,因为当异步监控时,较慢的构建更容易容忍。开发人员会抵制在本地运行完整的构建,然后开发人员会减少签入的次数,构建会逐渐变慢,直到整个团队陷入停顿。一个更好的解决方案是让开发人员采用Synchronous Integration尽管有构建特性分支,通过等待主干构建,他们将被迫使用诸如验收测试并行化这样的技术来优化它。

构建功能分支对于开源项目很有效,在开源项目中,一个有经验的开发人员小组必须集成来自不同贡献者组的变更,并且减少不同时区和不同专业水平的需求超过了持续集成的需求。然而,对于商业软件开发来说,构建功能分支符合维基百科对反模式的定义a common response to a recurring problem that is usually ineffective and risks being highly counterproductive“。一个经验丰富的小团队练习构建特性分支理论上可以实现连续集成,给定一个结构良好的架构和可预测的特性流,但是这是不寻常的。对于大多数协同工作的商业软件团队来说,构建特性分支是一种昂贵的实践,它阻碍了协作,抑制了重构,并且通过隐含地牺牲连续集成,成为了连续交付的一个重要障碍。正如保罗·哈曼特所说,”you should not make branches for features regardless of how long they are going to take“。