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是基于Git的强大分支能力所构建的一套软件开发工作流,最早由 Vincent Driessen 在2010年提出。最有名的大概是下面这张图。
在Gitflow的模型里,软件开发活动基于不同的分支:
Gitflow通过不同分支间的交互规划了一套软件开发、集成、部署的工作流。听起来很棒,迫不及待想试试了?等等,让我们先看看Gitflow不是什么。
Gitflow对待分支的态度就像: Let’s create branches just because… we can!
很多人吐槽吐槽,为什么开发一个新feature非得新开一个branch,而不是直接在develop上进行,难道就是为了……废弃掉未完成的feature时删除一个branch比较方便?
很多人诟病Gitflow太复杂。将这么一套复杂的流程应用到团队中,不仅需要每个人都能正确地理解和选择正确的分支进行工作,还对整个团队的纪律性提出了很高的要求。毕竟规则越复杂,应用起来就越难。很多团队可能不得不借助额外的 帮助脚本 去应用这一套复杂的规则。
然而最根本问题在于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最明显的两个好处是:
后面我们会看到,第一点所带来的伤害要大于其好处,第二点也可以通过其他的技术来实现。
说到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,我们应该使用什么样的开发工作流?如果你还没听过 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了。
虽然long lived branch是一种不好的实践,但branch作为一种轻量级的代码隔离技术还是非常有价值的。比如在分布式版本控制系统里,我们不用再依赖某个中心服务器,可以进行独立的开发和commit。比如在一些探索性任务上,我们可以开启branch进行大胆的尝试。
技术用的对不对,还是要看上下文。