转载

GITFLOW有害论

GITFLOW有害论

  文/TW 洞见

  什么是 Gitflow

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

GITFLOW有害论

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

  • The main branches
    • master 该分支上的代码随时可以部署到生产环境
    • develop 作为每日构建的集成分支,到达稳定状态时可以发布并 merge 回 master
  • Supporting branches
    • 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。
  • 现在我要告诉你,Github 在企业软件开发中甚至不是一个最佳实践。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 比较方便?

  然而最根本问题在于 Github 背后的这一套 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,那你应该用起来了。

GITFLOW有害论

  是的,所有的开发工作都在同一个 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
正文到此结束
Loading...