抽象分支与版本控制系统(VCS)分支功能的对比
抽象分支其时或多或少有那么一点儿“文不达意”,因为它是你对系统做大规模变更时,替代VCS中分支的一种方式。很多团队经常使用VCS的分支功能进行大规模变更,以便能够在主干上正常开发功能,修复缺陷。当然,头痛的问题也就随之而来,即:将分支合并回主干时相当痛苦,痛苦的程度决定于你在分支上所做修改的多少、大小,以及在这段时间里,主干上做了多少工作1。
也就是说,迫使你使用VCS分支的力量越强,当你需要合并回主干时,痛苦就越大。如果你还在分支上开发了新功能,那情况就更糟糕。一般来说,“利用分支开发特性或者做大的变更”不是一个好主意,原因有很多种,但最重要的一个原因是:它妨碍了持续交付和重构2。老马(Martin Fowler)写了为什么特性分支不好?, 以及 如何使用特性开关做为分支的替代方案?
但这并不是说,版本控制系统的所有分支都不好。如果只是想到了一个好点子,拉个分支出来做试验,到最后并不需要将代码合并回主干的话,此时用分支也无所谓。另外,当需要发布时才为该发布建立一个发布分支也是可以的。但要记住,在这个发布分支上,你只能做一些小的、严重缺陷的修复。 然而,对于那些正在做持续部署的团队来说,通常他们都不会这么做。因为在主干上修复任何问题对他们来说很容易,可以通过向前发布来代替向后回滚。 这种容易来自于两个版本之间的差异非常小。
另外,最后一种可容忍使用分支的情况是:你的代码基正处于一团乱麻模式。在这种情况下,创建抽象层可能非常困难。为了能创建这个抽象层,你必须首先找到一个“接缝”(如果你使用静态类型面向对象的编程语言的话,典型地是一个接口集合),在这个“接缝”处放上抽象层。如果找不到“接缝”的话,你就要通过一系列的重构创造出一个。然而,如果因为某种原因实在弄不出来“接缝”的话,那么你就只能依靠在分支上重构,以达到那种有“接缝”的状态了。当然,这是一个极端手段。
抽象分支与其它模式之间的关系
重构:重构被定义为“一种对软件内部的变更,使软件更容易理解,更方便地修改,但却不改变软件的外部行为”。从这个角度来讲,上面提到的两种抽象分支的例子也都属于重构。关键是,抽象分支是与重构相关的一种有效步骤,能够对软件架构进行大规模的修改。你不但能够随时发布软件,而且同时还能进行重构,这也许是在主干上开发最重要的收益了。
特性开关: 大家常常把抽象分支和 特性开关搞混。二者都能够让你在主干上做出增量式改变的模式。不同点在于,特性开关的主要目的是在开发新功能时,如果需要发布,让这些未完成的新功能对用户不可见。所以,特性开关通常是用于部署或者运行时选择是否让某个或某组特性在应用程序中可见。
抽象分支是为了增量式地对应用程序进行大面积改动,所以是一种开发技术。抽象分支当然可以与特性开关一起使用,比如,你可能通过开关决定是使用 iBatis,还是Hibernate来访问一组特定的数据调用,用于运行时的性能对比。但这种实现的选择通常是由开发人员决定,是硬编码进去的,也可能是构建时设定的,比如,通过依赖反转的配置项放进去的。
抽象分支与强制应用(strangler application)的关系。 强制应用模式包括增量式地用一个全新的系统替代整个系统(通常是遗留系统)。所以,与抽象分支相比,它是一种更高层上的抽象,即增量式地改变系统中某个组件。如果你的系统结构是面向服务的架构,那么两者之间的界线是比较模糊的。
你可能会说,这不就是一个“好的面向对象设计”嘛? 是的。在遵循SOLID 原则的代码库上,非常容易使用这种模式,尤其是遵循了依赖反转原则和接口分离原则(ISP)。ISP原则非常重要,因为它提供了良好的粒度划分,很容易做到对不同实现的切换。David Rice(敏捷项目管理产品Mingle的的Lead)指出,对于改变软件系统中某个特定组件的具体实现方式,抽象分支是唯一一种明智的选择。老马(Martin Fowler)也有相同的观点,他把组件(component)定义为系统中可以被另一种实现方式完全替代的那个部分。
1还有一种争论是:分布式版本控制系统让代码合并变得很容易,所以我们不应该害怕分支。这种误解有两个原因。首先,正如老马指出的,自动合并工具无法捕获语义冲突。其次,即使使用世界上最好的合并工具,也改变不了“分支存在的时间越长,合并的困难越大”这种事实。你到GitHub上看一看,就不难发现,每个人可能都想把他的分支合并回去,无奈的是,与主干的差异太大,合并需要大量的集成工作。
2 当然,每个规则都有一些例外。如果你的团队比较小,而且每个成员都很有经验,并且分支的生命周期很短(比如少于一天),那么,此时的分支操作未尝不可。
原文转自:http://www.continuousdelivery.info/index.php/2013/01/04/branch_by_abstraction/