特性分支与特性开关哪家强?

译者:乐视SCM高翻院 张鹏飞

分支管理策略对一个研发团队发布高质量的软件至关重要。在本文中,我们将探讨同一代码库中多任务并行开发时的解决方案,以及它们之间的优缺点。一般意义上来说冲突合并成本和独立任务发布能力之间的矛盾往往不可调和,但是特性开关提供了一条通往成功之路。

合并冲突

新产品研发初期代码量较少,团队规模也不大,这种时候并不需要太多正式流程。 然而,即使一个团队只有两名开发人员,为了有效避免冲突,仍然建议不要在同时对相同文件进行改动。

不幸的是,即便我们小心翼翼的保持独立开发,还是会不可避免的修改同一个文件。有时候这种意料之外的代码冲突往往会带来很大的麻烦,比如IOS工程师在使用XIB开发UI界面时,大多经历过痛苦的三方代码合并

在本文中我们将探讨一些不同的方法来处理多个开发人员使用相同的代码库时出现的合并冲突问题。

有些人可能会说,如果使用 Git 这样的现代工具,大部分的冲突都会自动解决,所以这并不是什么大问题。然而事实并非如此。诚然Git在创建新分支方面非常便捷,但那些看似神奇修改自动合并却远没有那么智能。尤其当你在处理XML文件冲突时,Git往往无法摆脱对人的依赖。

更重要的是,Git对语义冲突的自动解决无能为力。假设在一条分支中,Alice 重命名一个方法,而与此同时Bob在另一条分支中对该方法做了一些改动。当这两条分支合并时,Git无法得知Bob 的代码正在调用一个不存在的方法,因为那个方法已经被改名了。自然也不会给出任何冲突的提示,因为我们目前的工具集是不支持检测这类语义冲突的。

当我们尝试编译合并的代码线时,才会发现语义冲突,而这些发现可能只是其中的一个子集。 如果代码库是动态类型的语言(如javascript),可能直到用户吐槽应用程序崩溃时,我们才会发现这些语义冲突。

冲突不可避免,手动解决冲突会带来额外的工作量,但是潜在的成本也不可忽视,因为这种方式会对代码质量带来长期负面的影响。如果研发工程师意识到一个让代码质量变得更好的重构会引发大量的代码合并冲突,那么或多或少这种重构的意愿会受到抑制。

由于代码变更所带来的冲突成本的上升,衡量代码优化投入产出比的天平就会偏转,这样一来随着时间的积累,代码内在质量就无法得到提升。尤其对于一些小巧但是影响范围较大的改动,这些类型的变更本来会逐步提升软件代码质量,但也正是这类变更才最容易诱发代码冲突。

在我看来,这才是分支开发模式所带来的真正的隐藏成本,因为它抑制了那些“让世界变得更好一点”的冲动,结果代码质量逐步下降,并最终走向一条不归路。所以,合并冲突被认为是一个很糟糕的事情,我们来看看如何避免这个问题。

主干开发

当一个小型研发团队启动一个新应用项目时,它们可以通过将所有开发人员的改动频繁推送到一个共享集成主分支的方式,来降低潜在的代码冲突风险,这也是持续集成思想的核心原则之一。

当每个人都频繁向主干同步代码改动的时候,潜在的代码合并冲突会在第一时间暴露出来,并快速得以解决。

但这里面有一个很大问题,假如任何一个任务没有达到可发布状态,那么会影响整条分支的发布工作,我们可以举个例子来说明。

Alice和Bob两个团队共同研发一个移动应用。Bob在对用户配置界面进行大幅重构的同时,Alice则专注于一个关键功能的开发,老板们对这个功能非常看重并且要求本周末需要提交到应用商店。

两个团队为了避免大型冲突而频繁的提交代码,到了周四Alice完成了所有改动并提交测试,发现用户配置界面崩溃。她找到Bob,Bob解释,它正在对这部分代码进行大幅重构,而且需要的时间比他预期的更长。

Alice强调这个功能必须本周完成发布上线,Bob深吸一口凉气,于是在接下来的两天Bob和Alice一直在回退Bob的改动,而其中一些修改已经与Alice的新功能交错在一起。不过好在它们想方设法赶在周末前完成了上线工作。

由此我们发现,由于Alice和Bob共用分支,它们原本独立的工作流反而变的错综复杂,无法独立完成发布工作,这是共用分支的方式所带来的问题,接下来我们看看能否做的更好。

特性分支 Feature Branches

为了解决工作流相互耦合的问题,开发团队会避免将开发一半的代码推送到共享分支上。一些团队选择继续在本地工作,其它团队则使用一条共享的特性分支,只有当功能模块开发完成后才会提交到共享分支。

其实对于Git这种分布式版本控制工具来说,上面这两种方式是等效的,一种是将未完成的代码修改提交到可见的特性分支上,一种是提交到本地的开发分支。因此,我们将它们都视为特性分支进行讨论。

那么如果一个开发团队使用特性分支,可以解决全部问题吗,答案是否定的。因为这种方式引入了更加令人痛苦的合并风险。特性分支的改动只有在回归主线时才会集成其它改动,当两条特性分支同时修改了同一个文件,那么无论是有心还是无意都会带来潜在的合并冲突风险。

随着分支上的改动逐渐累积,这样的风险也随之变大,所以为了解决这个问题,部分研发团队会不定期的从主线向特性分支进行代码合并。

然后,这对于两条同时存在的特性分支来说并不起作用,因为它们只有在回归主线的时候才会意识到彼此的存在,那么这些潜在的合并冲突将如影随形,一直到下一次回归主线为止。

(感谢Martin Fowler关于特性分支的阐述,以上图片受到了本文很大的启发)

另外一些团队试图通过“交叉合并”来解决这个问题,将一条特性分支上的改动同步到另一条特性分支,来减小分支间潜在的合并冲突。

但是,一旦您将两条特性分支合并在一起,基本上等同于创建了一条共享分支,那么我们在一开始提到的问题又会出现,未完成的代码开发会直接影响两条特性分支的工作,于是特性无法解耦和独立发布。

总而言之,功能分支允许团队通过分离工作流,实现独立发布。但是,不可忽视当并行分支包含大量修改时,它们又会引入巨大的合并冲突风险。

特性分支定期同步主线只能一定程度解决回归问题,单对两条独立的特性分支则毫无帮助,而同步两条特性分支则相当创建了一条共享主线,那么特性分支的独立发布又无法保证。

特性开关 Feature Flags

那么是否有一种替代方案,可以帮助团队解决特性分支合并冲突的风险呢?它们可以选择频繁合并主线,但是这样一来就会让原本独立的工作流耦合在一起,无法独立发布。为此我们引入了特性开关(也称为特性标志)来解决这个问题。

1.if( featureFlags.isOn(“my-new-feature”) ){2.showNewFeatureInUI();3.}

只有当my-new-feature标志配置打开,新特性才会体现在应用界面中。这意味着即使新特性的相关代码充斥着各类Bug,只要特性开关是关闭状态,也不会影响应用正常发布。

通过特性开关,半成品的代码也可以集成到共享分支中,并且不会影响分支发布。即便Bob的改动只完成了一半,而Alice需要发布一个正式版本,就可以将 Alice 的特性开关设置成打开,而将 Bob 正在开发中的特性开关设置成关闭即可。

这样我们既可以受益于持续集成所带来的福利,减少潜在的合并冲突,也可以保证特性开发彼此解耦,随时处于可发布状态。

这种技术由来已久,它来源于主干开发的技术实践,Flickr,Etsy,Github以及Facebook等业界知名公司都在采用这项技术,而特性开关正是Facebook每天两次从主线部署到线上环境的有力手段之一。

特性开关的缺点

与迄今为止我们讨论的其它方法一样,特性开关也有其固有缺点。由于需要对未完成的代码增加显式的逻辑控制,导致代码基线中引入大量的噪声。随着时间的推移,这种噪声可能会导致一些凌乱的代码,只有在功能模块不需要的时候才能移除这些代码。

测试人员可能需要一段时间才能适应这种利用特性标记进行开发的应用程序,部分没有完成的功能也会随着应用对外发布。

如果可以远程控制特性开关,那么在部署到最终用户之前,团队就应该对开关功能配置进行充分的测试。

最后,某些类型的更改可能无法通过特征开关进行控制,尤其是那些修改范围广,涉及大量文件的例子。令人高兴的是,特性开关方法有其灵活的一面,对于那些复杂的改动,通过开关来控制是个不错的选择。

特性开关的常用技巧

当一个团队开始采用特性开关时,它们通常会碰到上面讨论的一些挑战,但最终会找出解决方法。 以下是工作中成功利用特性开关的一些技巧。

让开关过期 当刚开始接触这种方法时,很多团队往往会兴奋的引入很多开关,与此同时并不会花时间来移除那些不再生效的开关。我们需要妥善的管理这些过期的开关,因为它们会引入很多无用代码,让开关管理变得混乱不堪。 一些开发团队采取相当积极的做法确保旧开关过期,例如在创建开关时设置"定时炸弹",或者在一段时间后抛出异常,或者在引入开关的时候同时在研发日志中创建一个移除开关的任务作为提醒和跟踪,都是一些不错的方法。 特性开关不是万能的 在新的工作流中引入特性开关需要谨慎评估。对于可以在代码中使用单点控制的功能模块比较适合采用开关方式,这类改动体现在UI界面上往往是一个按钮的显示隐藏,或者用户交互行为的改变。 对于类似内部重构性质的工作,则很难利用开关控制。对于这种工作可以采用特性分支的方式,通过将工作任务细分到足够小的颗粒度,然后使用一系列短分支渐进性的进行开发,可以有效降低长分支所带来的大规模合并冲突的风险。

总结

特性开关和特性分支都是解决代码并行开发的方法,帮助团队进行独立的变更发布。特性分支很容易入手,但是会引入痛苦的合并冲突。这种风险往往不利于对代码进行渐进式的优化,导致技术债务的累积并走向不归路。

特性开关允许团队实施真正的持续集成,并将代码更改与功能发布解耦,代价是增加了代码中开关的复杂性。特征开关不是万能的,也并非总是最佳选择,好在尝试使用这种方法对于开发团队来说成本很低,只需硬编码 if/else 语句即可,接下来的事情,就是看看它通向何方了。

原文:https://devops.com/feature-branching-vs-feature-flags-whatsright-tool-job/

文章来自:DevOps时代社区

原文链接:

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏腾讯移动品质中心TMQ的专栏

探秘APP性能三角区

APP要做性能测试,什么样的数据能反应应用的性能情况,如何评估应用的性能状态? 不知道该如何入手?一起来分析下如何给APP做性能测试。 性能测试三角:性能指标、...

2227
来自专栏desperate633

计算机网络基本要素和结构什么是计算机网络计算机网络的要素计算机网络的服务计算机网络的协议计算机网络的结构

那么,计算机网络中主机数量极其大,而且有的主机很远,如果要每个主机互相直接连接的话,不现实。 所以 通过交换网络互连主机,不同的主机分别跟交换网络相连

651
来自专栏IT大咖说

阿里巴巴:金融级别大数据平台的多租户隔离实践

内容来源:https://yq.aliyun.com/articles/466662

2580
来自专栏程序你好

数据库设计的最佳实践

1392
来自专栏SEO

SEO常见疑问整理总结(一)

3377
来自专栏大前端开发

从编程小白到全栈开发:数据 (1)

有些事情时刻都在发生,但是我们通常很少意识到它们的存在。比如,当我们使用网页或移动应用的时候,其实在不断的产生着数据:注册一个网站或app的账号、发一条微博、写...

1053
来自专栏王亚昌的专栏

UNIX编程艺术之“模块性”

     本章主要讨论模块划分、接口设计,提出了几个很重要的概念,包括紧凑性、正交性、自顶向下和自底向上的设计、SPOT原则、分层、插件化。下面就这几个概念,谈...

1062
来自专栏DevOps时代的专栏

特性分支与特性开关哪家强?

合并冲突 新产品研发初期代码量较少,团队规模也不大,这种时候并不需要太多正式流程。 然而,即使一个团队只有两名开发人员,为了有效避免冲突,仍然建议不要在同时对...

2217
来自专栏FreeBuf

如何阻止下一次心脏出血漏洞

原文:How to Prevent the next Heartbleed.docx 翻译:赵阳 一、引言 基于OpenSSL的心脏出血漏洞被认为是CVE-20...

34510
来自专栏编程派的专栏

小白学 Scrapy 爬虫系列之一:准备实验机器

本系列文章中,笔者将带领大家从零开始学习爬虫编写。在跟随笔者一起实操之前,要求大家有一定的 Python 基础。之前没接触过的同学也不用担心,Python 号称...

2600

扫码关注云+社区

领取腾讯云代金券