主干开发TBD——特性开关篇

一、概念
Feature Toggles,又叫Feature Flags,中文通常译作特性开关,是一种非常常用的上线方式,并不局限在主干开发中使用。
话不多说,直接看看特性开关是什么。
想象一下,你是微信开发团队中的一员,负责朋友圈广告推送。有一天产品经理告诉你,原来的广告推荐算法不够精准,要用一种新算法。你找到一段代码
Ad calcAdForUser(User user) {
Ad ad = someAlgorithm(user);
return ad;
}
然后将它改成了
Ad calcAdForUser(User user) {
Ad ad = someNewAlgorithm(user);
return ad;
}
Ad someNewAlgorithm(User user) {
xxx
}
然后你估计这个someNewAltorithm需要几天甚至一两周才能完成,如果现在直接合并的话,线上的代码就跑不通了;而如果现在不合并,根据你的经验,两周之后代码合并可能会有非常多的冲突,非常痛苦。其实这就是主干开发中会碰到的问题,这个时候特性开关就登场了,你声明了一个newAlgorithSwitchOn,并接入动态配置中心(比如TCC),然后把代码改成了
Ad calcAdForUser(User user) {
Ad ad;
if (newAlgorithmSwitchOn) {
ad = someNewAlgorithm(user);
} else {
ad = someAlgorithm(user);
}
return ad;
}
Ad someNewAlgorithm(User user) {
xxx
}
然后,你把newAlgorithSwitchOn放进配置中心,并设置为FALSE。这样你可以放心的把这段代码合进master,并且不会影响线上用户。当你把代码实现完毕,测试通过,并跟相关人士确认可以上线之后,再把newAlgorithSwitchOn改成TRUE。这样,配置中心会把配置变动推送到客户端,所有用户看到的广告也变成了新算法计算出来的。这个需求就完美上线了。
这是一个非常简单的场景,实际需求中的特性开关一般都是稍微复杂一些的,往往会在多个地方有开关检查语句。

二、Feature Toggles最常用的场景:
1、Canary Release
主干开发中的开关就属于这种。需要注意的有两点:

  • 即使不在TBD中,这种特性开关也是很常见的,通常用在比较大型、影响广泛的feature中。
  • 上线流程中也有一个金丝雀发布,此金丝雀发布非彼金丝雀发布。这个是以feature为单位的,而上线过程中的金丝雀发布是以版本为单位的。
    我们谈一下在主干开发中的Feature Toggles和特性分支开发中的Feature Branches的本质和异同:

    1. 本质:都是一种代码隔离的手段。这非常非常重要,如果理解了这条本质,就可以更容易地在实际场景中作出选择。branch使用分支物理隔离代码,toggles使用开关逻辑隔离代码。同样,如果理解这只是代码隔离的手段,就能够理解这种方式只会对rd本身产生影响,对QA和PM以及团队其它成员产生的影响是有限的。
  • 不同点:
    • Toggles具有更强的代码侵入,且需要额外的工作量,出错的概率也更高。这种牺牲是有回报的,Toggles因此具有更好的灵活性,且保证了持续集成和代码统一,大大降低了代码的管理成本,这就是我们追求主干开发的原因。
    • Branches具有更弱的代码侵入,解耦更加彻底。但这是有代价的,首先是老生常谈的三点:1、长期存在的branch很难合并进主干,且合并非常容易出错。2、与持续集成的思想从根本上相悖。3、同时维护大量分支大大增加的了管理成本。
      2、A/B Testing
      用Feature Toggles来做A/B测试是一种比较老式的做法,不是特别靠谱,建议采用灰度方案 中的方案。

另外还有一些使用场景,有兴趣可以看Martin Fowler的文章(下文参考中已给出)。

三、使用Feature Toggles对开发人员提出了非常高的要求。
1、首先,并不是所有的特性都能很方便的使用Feature Toggles隔离。
有时候,改一个功能的时候,需要这里改一行,那里改一行,改动地方又多又杂,这时候用feature Toggles就很麻烦。此时,有两个点需要注意:

  • 如果实现开关的复杂度很高,而功能的规模又很小,还不如放弃使用开关,代之以充足的单元测试和集成测试。
  • 如果实现一个功能需要改动的点很多,通常说明代码的设计有问题,这一点非常重要。正如你在写单元测试的时候,发现一个函数很难测试;一个功能很难用Toggles来实现隔离,也说明设计有问题。如果所有的代码都是高内聚低耦合的,又怎么会做一个功能改动点四处分散。此时,应该慎重考虑是否重构相关代码。
    2、其次,某些复杂的特性的Feature Toggles也会很复杂,对Feature Toggles本身的测试就会占用一部分工作量。
    引入Feature Toggles有两部分成本,对它的测试是其中的大头。
  • 使用branch隔离的代码,在上线时,如果没有合并branch,则相应的代码肯定不会存在于线上。
  • 不同于branch,开关的正确性并不是天然的。尤其是大型的功能,在开始测试功能之前,首先要测试开关的逻辑是否正确,这本身就是不小的工作量,而且完全是额外引入的,在branch模式下并不存在的工作量。也正因为如此,对于主干开发,完善的单元测试和集成测试非常非常重要,同样,Code Review也成倍重要。
    3、同样,这些复杂的Feature Toggles对RD的设计能力和开发能力都有一定的要求,且增加了一部分工作量。
    引入Feature Toggles的另一部分成本,自然就是开发为它付出的成本。这部分成本比起测试的成本,通常要低一些。最差的情况下,大不了把分支的每一行代码都用开关包起来,再合并就好了。这是一种非常土,但是肯定正确的方法。
    当然,并不推荐这么做。最好还是经过设计地使用简单易懂的feature Toggles,这就对开发的设计能力产生了要求。
    四、参考
    https://martinfowler.com/articles/feature-toggles.html (martin fowler还是厉害,讲个feature toggle都能搞出这种长篇大论)

发表评论

电子邮件地址不会被公开。 必填项已用*标注