Gitflow有害论

We firmly believe that long-lived version-control branches harm valuable engineering practices such as continuous integration, and this belief underlies our dislike for Gitflow. —— ThoughtWorks Technology Radar 2015 Nov

什么是Gitflow

Gitflow是基于Git的强大分支能力所构建的一套软件开发工作流,最早由Vincent Driessen在2010年提出。最有名的大概是下面这张图。

01

在Gitflow的模型里,软件开发活动基于不同的分支:

  • The main branches
    *   master 该分支上的代码随时可以部署到生产环境
    
    • develop 作为每日构建的集成分支,到达稳定状态时可以发布并merge回master
  • Supporting branche
    *   Feature branches 每个新特性都在独立的feature branch上进行开发,并在开发结束后merge回develop
    
    • Release branches 为每次发布准备的release candidate,在这个分支上只进行bug fix,并在完成后merge回master和develop
    • Hotfix branches 用于快速修复,在修复完成后merge回master和develop

Gitflow通过不同分支间的交互规划了一套软件开发、集成、部署的工作流。听起来很棒,迫不及待想试试了?等等,让我们先看看Gitflow不是什么。

  • Gitflow不是Git社区的官方推荐工作流。是的,不要被名字骗到,这不是Linux内核开发的工作流也不是Git开发的工作流。这是最早由Web developer Vincent Driessen和他所在的组织采用并总结出的一套工作流程。
  • Gitflow也不是Github所推荐的工作流。Github对Gitflow里的某些部分有不同看法,他们利用简化的分支模型和Pull Request构建了适合自己的工作流Github Flow
  • 现在我要告诉你,Gitflow在企业软件开发中甚至不是一个最佳实践。ThoughtWorks Technology Radar在2011年7月刊2015年1月刊里多次表明了Gitflow背后的feature branch模型在生产实践中的危害,又在2015年11月刊里专门将Gitflow列为不被推荐的技术。

为什么Gitflow有问题

Gitflow对待分支的态度就像: Let’s create branches just because… we can!

很多人吐槽,为什么开发一个新feature非得新开一个branch,而不是直接在develop上进行,难道就是为了……废弃掉未完成的feature时删除一个branch比较方便?

很多人诟病Gitflow太复杂。将这么一套复杂的流程应用到团队中,不仅需要每个人都能正确地理解和选择正确的分支进行工作,还对整个团队的纪律性提出了很高的要求。毕竟规则越复杂,应用起来就越难。很多团队可能不得不借助额外的帮助脚本去应用这一套复杂的规则。

然而最根本问题在于Gitflow背后的这一套feature branch模型。

VCS里的branch本质上是一种代码隔离的技术。使用feature branch通常的做法是:当developer开始一个新feature,基于develop branch的最新代码建立一个独立branch,然后在该branch上完成feature的开发。开发不同feature上的developers因为工作在彼此隔离的branch上,相互之间的工作不会有影响,直到feature开发完成,将feature branch上的代码merge回develop branch。

我们能看到feature branch最明显的两个好处是:

  1. 各个feature之间的代码是隔离的,可以独立地开发、构建、测试;
  2. 当feature的开发周期长于release周期时,可以避免未完成的feature进入生产环境。

后面我们会看到,第一点所带来的伤害要大于其好处,第二点也可以通过其他的技术来实现。

merge is merge

说到branch就不得不提起merge。merge代码总是痛苦和易错的。在软件开发的世界里,如果一件事很痛苦,那就频繁地去做它。比如集成很痛苦,那我们就nightly build或continuous integration,比如部署很痛苦,那我们就频繁发布或continuous deployment。 merge也是一样。所有的git教程和git工作流都会建议你频繁地从master pull代码,早做merge。

然而feature branch这个实践本身阻碍了频繁的merge: 因为不同feature branch只能从master或develop分支pull代码,而在较长周期的开发完成后才被merge回master。也就是说相对不同的feature branch,develop上的代码永远是过时的。如果feature开发的平均时间是一个月,feature A所基于的代码可能在一个月前已经被feature B所修改掉了,这一个月来一直是基于错误的代码进行开发,而直到feature branch B被merge回develop才能获得反馈,到最后merge的成本是非常高的。

现代的分布式版本控制系统在处理merge的能力上有很大的提升。大多数基于文本的冲突都能被git检测出来并自动处理,然而面对哪怕最基本的语义冲突上,git仍是束手无策。在同一个codebase里使用IDE进行rename是一件非常简单安全的事情。如果branch A对某函数进行了rename,于此同时另一个独立的branch仍然使用旧的函数名称进行大量调用,在两个branch进行合并时就会产生无法自动处理的冲突。

如果连rename这么简单的重构都可能面临大量冲突,团队就会倾向于少做重构甚至不做重构。最后代码的质量只能是每况愈差逐渐腐烂。

持续集成

如果feature branch要在feature开发完成才被merge回develop分支,那我们如何做持续集成呢?毕竟持续集成不是自己在本地把所有测试跑一遍,持续集成是把来自不同developer不同team的代码集成在一起,确保能构建成功通过所有的测试。按照持续集成的纪律,本地代码必须每日进行集成,我想大概有这几种方案:

  1. 每个feature在一天内完成,然后集成回develop分支。这恐怕是不太可能的。况且如何每个feature如果能在一天内完成,我们为啥还专门开一个分支?
  2. 每个分支有自己独立的持续集成环境,在分支内进行持续集成。然而为每个环境准备单独的持续集成环境是需要额外的硬件资源和虚拟化能力的,假设这点没有问题,不同分支间如果不进行集成,仍然不算真正意义上的持续集成,到最后的big bang conflict总是无法避免。
  3. 每个分支有自己独立的持续集成环境,在分支内进行持续集成,同时每日将不同分支merge回develop分支进行集成。听起来很完美,不同分支间的代码也可以持续集成了。可发生了冲突、CI挂掉谁来修呢,也就是说我们还是得关心其他developer和其他团队的开发情况。不是说好了用feature branch就可以不管他们自己玩吗,那我们要feature branch还有什么用呢?

所以你会发现,在坚持持续集成实践的情况下,feature branch是一件非常矛盾的事情。持续集成在鼓励更加频繁的代码集成和交互,让冲突越早解决越好。feature branch的代码隔离策略却在尽可能推迟代码的集成。延迟集成所带来的恶果在软件开发的历史上已经出现过很多次了,每个团队自己写自己的代码是挺high,到最后不同团队进行联调集成的时候就傻眼了,经常出现写两个月代码,花一个月时间集成的情况,质量还无法保证。

如果不用Gitflow…

如果不用Gitflow,我们应该使用什么样的开发工作流?如果你还没听过Trunk Based Development,那你应该用起来了。

02

是的,所有的开发工作都在同一个master分支上进行,同时利用Continuous Integration确保master上的代码随时都是production ready的。从master上拉出release分支进行release的追踪。

可是feature branch可以确保没完成的feature不会进入到production呀。没关系,Feature Toggle技术也可以帮你做到这一点。如果系统有一项很大的修改,比如替换掉目前的ORM,如何采用这种策略呢?你可以试试Branch by Abstraction。我们这些策略来避免feature branch是因为本质上来说,feature branch是穷人版的模块化架构。当你的系统无法在部署时或运行时切换feature时,就只能依赖版本控制系统和手工merge了。

Branch is not evil

虽然long lived branch是一种不好的实践,但branch作为一种轻量级的代码隔离技术还是非常有价值的。比如在分布式版本控制系统里,我们不用再依赖某个中心服务器,可以进行独立的开发和commit。比如在一些探索性任务上,我们可以开启branch进行大胆的尝试。

技术用的对不对,还是要看上下文。

[参考文献]

  1. Long-lived-branches-with-gitflow in radar: https://www.thoughtworks.com/radar/techniques/long-lived-branches-with-gitflow
  2. Gitflow in radar: https://www.thoughtworks.com/radar/techniques/gitflow
  3. Feature Branching in radar: https://www.thoughtworks.com/radar/techniques/feature-branching
  4. Fowler on feature branch: http://martinfowler.com/bliki/FeatureBranch.html
  5. Fowler on continuous integration: http://www.martinfowler.com/articles/continuousIntegration.html
  6. Paul Hammant on TBD: http://paulhammant.com/2015/12/13/trunk-based-development-when-to-branch-for-release/
  7. Google’s Scaled Trunk Based Development: http://paulhammant.com/2013/05/06/googles-scaled-trunk-based-development/
  8. Trunk Based Development at Facebook: http://paulhammant.com/2013/03/04/facebook-tbd/
  9. Fowler on feature toggle: http://martinfowler.com/bliki/FeatureToggle.html
  10. Jez Humble on branch by abstraction:http://continuousdelivery.com/2011/05/make-large-scale-changes-incrementally-with-branch-by-abstraction/
  11. Fowler on branch by abstraction: http://martinfowler.com/bliki/BranchByAbstraction.html
Share