构建功能分支是一种版本控制策略,开发人员在共享主干之前将更改提交到源代码存储库的各个远程分支。构建功能分支可以使用集中式版本控制系统 (VCS),例如 Subversion 和 TFS ,但它通常与分布式版本控制系统 (DVCS) 相关联,例如 Git 和 Mercurial—— 尤其是 GitHub 和 GitHub Flow 。
在 Build Feature Branching Trunk 中,Trunk 被认为是所有先前发布的工作的完美代表,并且新功能是在从 Trunk 剪切的短期功能分支上开发的。开发人员将对他们的功能分支提交更改,完成后这些更改要么直接合并到 Trunk 中,要么由另一名开发人员使用 GitHub 合并请求 等流程进行审查和合并。然后在 Trunk 上执行自动化测试,测试人员手动验证更改,并将新功能发布到生产环境中。当生产缺陷发生时,它被修复在从 Trunk 切出的发布分支上,并在生产发布时合并回来。
考虑一个提供在线公司账户服务的组织,其代码库由实践构建功能分支的团队维护。最初请求两个特性——F1 Computations 和 F2 Write Offs——因此 F1 和 F2 特性分支从 Trunk 中删除,开发人员将他们的更改提交给 F1 和 F2。
然后开始开发另外两个功能——F3 银行详细信息和 F4 会计期间——F3 和 F4 功能分支从 Trunk 中删除,开发人员致力于 F3 和 F4。 F2 由非 F2 开发人员在代码审查后完成并合并到 Trunk 中,一旦在 Trunk + F2 上签署测试,它就会发布到生产环境中。 F1 分支增长到包含 Computations 重构,它短暂地打破了 F1 分支。
在 F2 中发现生产缺陷,因此在从 Trunk + F2 剪切的发布分支上进行了 Write Offs 的 F2.1 修复,并在修复投入生产时合并回来。 F3 被认为是完整的,并由非 F3 开发人员合并到 Trunk + F2 + F2.1 中,并在测试后发布到生产环境中。随着计算重构范围的扩大,F1 分支进一步增长,而 F4 分支因会计期间提交系统的架构更改而暂时中断。
当 F1 完成时,修改的代码量意味着非 F1 开发人员需要进行冗长的代码审查,并且在 F1 可以合并到 Trunk + F2 + F2.1 + F3 之前需要进行一些返工,之后它被成功测试并发布到生产。 F4 中所做的架构更改也意味着耗时的代码审查,并由非 F4 开发人员合并到 Trunk + F2 + F2.1 + F3 + F1,并在测试后 F4 投入生产。但是,随后在 F4 中发现了生产缺陷,并在发布分支上对会计期间进行了 F4.1 修复,并在缺陷解决后合并到 Trunk + F2 + F2.1 + F3 + F1 + F4 中。
在此示例中,F1、F2、F3 和 F4 都在自己的功能分支上享受不间断的开发。对短期特性分支的强调降低了合并到 Trunk 中的复杂性,并且代码审查的使用降低了 Trunk 构建失败的可能性。然而,F1 和 F4 功能分支不受控制地增长,直到它们都需要一个复杂的、有风险的合并到 Trunk 中。
公司账户服务团队可能已经使用 混杂集成 来降低将每个功能分支合并到主干中的复杂性,但这并不能防止相同的代码在不同的分支上出现偏差。例如,将F2和F3集成到F1和F4中,可以简化后面将F1和F4合并到Trunk中的过程,但如果F1和F4修改了相同的代码,则不会限制F1和F4产生 语义冲突 。
此示例显示构建功能分支通常如何将代价高昂的集成阶段插入到软件交付中。具有混杂集成的短暂特性分支应该确保最小的集成成本,但现实是特性分支的持续时间仅受开发人员纪律的限制——即使有最好的意图,纪律也很容易丢失。一个功能分支可能只打算持续一天,但它经常会增长以包括错误修复、可用性调整和/或重构,直到它持续的时间比预期的要长,并且需要复杂地合并到 Trunk 中。这就是为什么 Build Feature Branching 通常与 Continuous Integration 不兼容,后者要求每个团队成员至少每天在 Trunk 上集成和测试他们的更改。 Build Feature Branching 团队的每个成员不太可能每天都合并到 Trunk,因为它很容易误入歧途,虽然使用构建服务器持续验证分支完整性是一个很好的步骤,但它并不等同于共享反馈整个系统。
Build Feature Branching 提倡功能分支的开发人员应该让其他开发人员审查他们的更改并将其合并到 Trunk 中,并且这个过程由 GitHub Pull Requests 等工具很好地管理。然而,每次代码审查都代表一个充满延迟机会的交接期——开发人员可能等待审查人员可用,审查人员可能等待开发人员上下文,开发人员可能等待审查人员反馈,和/或审查人员可能等待开发人员返工。正如 Allan Kelly 所说,“ 代码审查如果不及时进行,就会失去效力 ”,当代码审查缓慢时,功能分支就会变得陈旧,主干合并的复杂性也会增加。一种更好的技术是结对 编程 ,这是一种持续代码审查的形式,返工最少。
要求从事正交任务的开发人员分担将功能集成到 Trunk 中的责任会削弱责任。当一个开发人员拥有功能分支的权限而另一个开发人员负责其主干合并时,两个人自然会觉得对整体结果不那么负责任,并且没有动力获得对该功能的快速反馈。正是出于这个原因,构建功能分支通常会导致 Jim Shore 所说的 异步集成 ,其中功能分支的开发人员在要求审查后立即开始处理下一个功能,而不是等待成功的审查和 Trunk建造。在短期内,异步集成会导致成本更高的构建失败,因为原始开发人员必须中断他们的新功能并将上下文切换回旧功能以解决 Trunk 构建失败。从长远来看,它会导致 Trunk 构建速度变慢,因为在异步监控时构建速度较慢更容易被接受。开发人员将拒绝在本地运行完整的构建,然后开发人员将减少签入的频率,并且构建将逐渐放缓,直到整个团队陷入停顿。更好的解决方案是让开发人员在构建功能分支的情况下采用 同步集成 ,并且通过等待主干构建,他们将被迫使用验收测试并行化等技术对其进行优化。
构建功能分支非常适用于开源项目,在这些项目中,由经验丰富的开发人员组成的小团队必须集成来自不同贡献者组的更改,并且缓解不同时区和不同专业水平的需求超过了对持续集成的需求。然而,对于商业软件开发,Build Feature Branching 符合维基百科对反模式的定义——“ 对反复出现的问题的常见反应,通常是无效的,而且有可能适得其反 ”。假设一个结构良好的架构和可预测的功能流,一个实践构建功能分支的经验丰富的小型团队理论上可以完成持续集成,但这并不常见。对于绝大多数从事商业软件工作的同地团队而言,构建 功能分支 是一种代价高昂的做法,它会阻碍协作、抑制重构,并且通过隐式牺牲持续集成成为持续交付的重大障碍。正如 Paul Hammant 所说,“ 无论需要多长时间,你都不应该为功能创建分支 ”。