文/TW 洞见
什么是 Gitflow
Gitflow 是基于 Git 的强大分支能力所构建的一套软件开发工作流,最早由 Vincent Driessen 在 2010 年提出。最有名的大概是下面这张图。
在 Gitflow 的模型里,软件开发活动基于不同的分支:
Gitflow 通过不同分支间的交互规划了一套软件开发、集成、部署的工作流。听起来很棒,迫不及待想试试了?等等,让我们先看看 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 最明显的两个好处是:
后面我们会看到,第一点所带来的伤害要大于其好处,第二点也可以通过其他的技术来实现。
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 的代码集成在一起,确保能构建成功通过所有的测试。按照持续集成的纪律,本地代码必须每日进行集成,我想大概有这几种方案:
所以你会发现,在坚持持续集成实践的情况下,feature branch 是一件非常矛盾的事情。持续集成在鼓励更加频繁的代码集成和交互,让冲突越早解决越好。feature branch 的代码隔离策略却在尽可能推迟代码的集成。延迟集成所带来的恶果在软件开发的历史上已经出现过很多次了,每个团队自己写自己的代码是挺 high,到最后不同团队进行联调集成的时候就傻眼了,经常出现写两个月代码,花一个月时间集成的情况,质量还无法保证。
如果不用 Gitflow…
如果不用 Gitflow,我们应该使用什么样的开发工作流?如果你还没听过 Trunk Based Development,那你应该用起来了。
是的,所有的开发工作都在同一个 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 进行大胆的尝试。
技术用的对不对,还是要看上下文。
[参考文献]