作者|郑明曼提出的问题任何复杂的软件都是团队合作的产物,所以我们会使用版本控制工具和不同的分支策略来辅助团队的日常开发和沟通,Mainline开发模式和pullrequest开发模式(以下简称PR)都是最常用的两种模式。开发时选择哪种模式也成为人们经常讨论的话题。疫情时代,远程办公可能会阻碍团队沟通,PR开发模式越来越流行。一方面,PR开发模式可以为代码开发带来更好的隔离性,但另一方面,PR开发模式其实是一种更难掌握或者要求更高的开发模式。例如:一个PR被审查和合并的速度至少取决于三个因素:上下文、大小和原子性。还有,PR开发模式对重构不是很友好。因为重构需要高频集成,尽早发现并解决引入的冲突,而在PR开发模式下做起来难度更大。相比之下,mainlina开发模式是我更倾向于的一种做法。首先,它不需要考虑太多的附加因素。开发人员只需要有一个“健康”的提交,就可以与主线集成,并在团队中可视化他们的代码更改。另外它对重构有很好的支持,因为主线开发模式本身就支持持续集成。《Software Configuration Management Patterns》一书(以下简称SCM模式)从软件配置管理的角度出发,关注那些影响日常工作的代码编写、功能实现、代码变更等方面,并将其归纳为一系列模式.接下来,本文想从SCM模式的角度来比较主线开发模式和PR开发模式。概念澄清为了更好地理解和比较这两种开发模式,有必要对相关的基本概念和两种模式的概念进行简要说明。codeline和codelinepolicy组合的不同决定了不同的开发模式codeline和codelinepolicy的不同组合可以形成不同的开发模式。从形式上看,主线开发模式和PR开发模式的区别,其实就是codeline和codelinepolicy的区别。因此,我们有必要了解一下两者的基本概念。(1)CodelineCodeline其实就是我们常说的分支(BradAppleton在这里解释一下)。在SCM模式中,代码线定义如下:代码线是一组源文件和其他工件的进展,这些工件构成了一些软件组件,随着时间的推移而变化。每次更改版本控制系统中的文件或其他工件时,都会创建该工件的修订版。(2)CodelinePolicycodelinepolicy其实是一个针对codeline的手册,持续为每条codeline的运行提供保障机制,同时也让开发者更清楚的知道:代码应该checkin到哪个codeline,什么时候checkin,以及在签入之前要运行哪些测试。每个代码线都有相应的代码线策略。例如:开发代码线:可以签入临时代码,但相关组件需要是可构建的。Mainline:所有组件必须编译链接,并通过回归测试;可以签入已完成和测试的新功能。发布代码线:必须在签入之前构建软件并通过回归测试;签入的代码仅限于错误修复;不得签入新特性或功能;签入后,分支将被冻结,直到完整的QA周期完成。一般来说,不同的代码线在不同的项目中有不同的用途和不同的代码线策略,也代表着不同程度的稳定性。比如主动开发codeline主要是为了快速开发,稳定性足够开发。好的;发布代码线是一个完整的测试,必须保持足够稳定的代码线。相比之下,发布代码线对稳定性的要求要高得多。对待主线和活跃开发线的方式决定了两种开发模式的区别。了解了以上的基本概念之后,我们就来正式认识一下这两种不同的开发模式。主线开发模式和PR开发模式的明显区别在于对待主线和活跃开发线的方式不同。(1)MainlineMainline是一种特殊的代码线,一般被认为代表了团队代码的当前状态,作为子分支及其合并的基础。MartinFlower是这样描述主线的:一个单一的、共享的、充当产品当前状态的分支。需要注意的是,主线是代码线,主线开发模式是开发模式。(2)活跃开发线活跃开发线是开发者用来开发代码的代码线/分支。但是这条代码线的特点是足够稳定,可以保证开发。(3)主线开发模式主线开发模式是开发者直接在主线上进行开发工作的模式。此时,主线=活跃的开发线。在SCM模式中,作者对主线开发模型的描述如下:当您开发单个产品版本时,请脱离主线进行开发。主线是一条“主代码线”,除特殊情况外,您可以在上面进行所有开发。(4)拉取请求开发模式PR开发模式是开发人员在分支上开发,然后将分支合并回主线的开发模式通过拉取请求的主线。此时,mainline!=activedevelopmentline。两种模式的比较通过上面两种开发模式的介绍,我们知道两者一个明显的区别就是主线和活跃开发线是否是同一条代码线。然后,我想从集成设计、代码线稳定性变化、起点三个方面对两种开发模式进行比较。主线开发模式的集成设计比PR开发模式简单。首先,让我们看一下代码线和CI设计的问题。主线开发模式下:mainline=activedevelopmentline如果你需要一个非常稳定的代码线(stablecodeline)来做release相关的或者releasebugfixes等,你一般会砍一条releasecodeline,这时候:releasecodeline=stablecodelinePR开发模式下:featurebranch=activedevelopmentline注意:这里的feature分支是指PR对应的分支。通常采用PR开发模式,因为主线需要非常稳定,所以此时:主线=稳定的代码线所以这两种模式对主线的稳定性要求不同,主线开发模式对主线的要求较低主线的稳定性。公关发展模式。另外,codeline和CI的联系非常紧密,因为CI的触发来自于代码的变更,而代码的变更来自于特定codeline的commit。因此,在考虑设计我们的CI时,我们还需要考虑如何设计我们的代码线。从SCM模式来看,对私有工作空间的要求是不同的。注:图片来自《Software Configuration Management Patterns》官网在SCM模式图片中可以看到:私有工作空间模式的“Completewith”需要集成构建和私有系统构建等,集成构建和私有系统构建都需要一系列测试(冒烟测试、单元测试、回归测试)。在主线开发模式下,开发者通常在本地完成开发并通过代码线政策的提交要求后,直接将代码合并到主线中。在这个过程中,开发者会进行频繁的集成,与主线的集成一般在一天之内。由于我们在远端只有一条活跃的开发线(主线),只有在主线上提交才会触发CI操作。这时候我们CI的数量等于主线的数量,其实就是1个CI。但在PR开发模式下,开发者通常会在PR获批后进行主线集成,因此集成时间通常为数天甚至一两周。此时,由于主线=稳定线,但由于特性分支并不经常集成主线,为了防止特性分支在集成时给稳定线引入严重的错误,也需要特性分支执行更多测试。但是更多的测试通常意味着更多的开销。为了让本地开发高效,构建和测试通常都放在CI上。因此,在PR开发模式中,通常一个PR(featurebranch/activedevelopmentline)对应一个CI。这时候我们CI的数量等于主线+特性分支的数量。可见,主线开发模式和PR开发模式在CI的设计上有所不同,其根源在于代码线的稳定性影响了私有工作空间的需求。注意:不断的将主线上的代码合并到PR的活跃开发代码线上不叫集成,因为集成是拉和推两个方向。这在MartinFlower的《Patterns for Managing Source Code Branches》文章中也有解释。与PR开发模式相比,主线开发模式下主线的稳定性在持续集成下更容易发生变化。虽然在主线开发模式下,主线=活跃开发线,但是我们对主线稳定性的要求可能没有稳定线/发布线那么高。但是由于频繁和持续的集成,主线/活跃开发线的稳定性会发生变化并趋于稳定,而在PR模式下,活跃开发线和主线的稳定性不会一起变化。在进行比较之前,我们先来看一下LauraWingerd在她的书《Practical Perforce》中提出的一个“豆腐”模型。“豆腐”模型是对代码线稳定性和质量的非正式评估。“豆腐”模型会根据以下四个方面来衡量代码线:软件离发布的距离有多远必须对变更进行审查和测试的严格程度变更对进度的影响有多大代码线的变化有多大注:图片来自《Practical Perforce》从图中我们可以看出,releasecodeline在“tofu”模型上处于最高层(firm)。一般来说,不会有太大的变化,并且会有严格的审核和测试要求,即使是微小的变化也可能会影响发布进度。主线中等,代码变更需要通过测试,但距离发布时间较远,对发布计划的影响中等。开发代码线处于最低(软),往往变化迅速,距离软件发布最远,甚至可能没有对其最新开发进行测试。现在回头看看我们的两种开发模式。PR开发模式中,主线和主动开发线是两条代码线,对应的政策也不同。这里可以说,我把主线的政策套用到活跃发展线上,试图提高活跃发展线在“豆腐”模型中的地位,从而间接提高主线的地位。但实际上,开发者始终是在活跃的开发线上进行开发,而不是在主线上。在活跃的开发线被集成到主线之前,它们总是两条代码线。另外,PR的开发模式和持续集成也是分开的,因为PR决定了集成频率的上限,只有PR通过后才能集成到主线。这种分离使得PR开发模式下的主线难以享受到持续集成带来的好处,比如更早发现问题、解决问题,降低风险。所以在PR开发模式下,无论开发线离主线有多近,主线和开发线都会一直处于一高(主线)一低(开发线)的状态。但在主线开发模式下,主线和活跃开发线是同一条代码线。那么在“豆腐”模型中,由于是同一条代码线,这意味着开发线/主线的上限由主线改为发布线。其次,由于主线开发模式本身对持续集成有很好的支持,其稳定性在持续集成中趋于稳定。因为代码变更集成到主线的频率非常高,所以每次合并其实都是对主线质量的考验。另外,如果大家能坚持“一旦有失败的commit,所有开发者的第一要务就是修复错误,不能将commit提交到主线”,那么主线的质量会趋向于维持在较高水平。主线开发模式的起点比PR开发模式更有利于团队的长远发展。主线开发模式和PR开发模式都需要维护代码质量,但这两种模式的出发点不同。主线开发模式的出发点是对团队中开发人员的信任,加上自动化测试套件维护代码质量,而PR开发模式的出发点是对陌生开发人员的不信任,所以需要依赖人工审查以保持代码质量。基于不同的出发点,我们经常可以看到两者被用在不同的项目中。开源项目和商业项目是非常典型的例子,不同的项目背景往往有不同的组织结构。开源项目通常由一些信任的核心成员和不信任的陌生开发者组成。任何陌生人都可以访问开源项目的源代码,并通过创建分支和提交PR的方式为项目做出贡献。并且对于这些任务,在提交PR之前,项目的核心成员都不确定或者不知道这个人以及完成这个PR需要的时间。PR开发模式很好的解耦了主线和非信任开发者之间的依赖,既不影响现有软件的状态,又为想要贡献软件的人提供了更低的门槛,并且保证了核心成员在项目中有充分的发言权软件的质量。商业项目则不同,通常由技术负责人和一些开发人员组成。技术负责人会对团队中的开发人员有一定的了解,对某个功能什么时候完成会有相应的计划,整个团队通常会有一定的信任度。因此,PR开发模式常见于开源项目,而主线开发模式常见于商业项目。在相互信任的团队氛围中工作通常既有利于团队,也有利于个人。因此,主线开发模式对开发者更加友好。由于团队信任不是一成不变的,有时使用PR开发模式是个好主意。以我个人的经历为例:刚开始工作做开发的时候,项目中刚好使用了PR开发模式,经常会去找有经验的前辈帮我看PR。不同于codereview,PR往往给codereviewer一个更全面的视角,因为它不局限于codereview的时间,review者也可以根据自己的想法对整个PR实现代码有一个详细的了解。因此,PRreview相对于codereview而言,会更仔细,对代码质量的控制也会更严格;而对于reviewer来说,根据相应的指导和建议修改代码,也能快速提升代码的能力。但在加入项目一年多后,在大家更加了解和信任之后,他们采用了相对松散的主线开发模式和代码审查的方式来管理代码质量。结语尽管PR开发模式随着remoteworking的普及而越来越流行,但我们还是需要谨慎使用。在使用PR开发模式时,我们需要合理确定PR的大小,避免因为PR过大导致codeline成为featurebranch。同时需要团队提出一个合理的机制来保证PR审核的及时性,否则这种开发模式会极大地阻碍团队的开发效率。相比之下,我个人更喜欢主线开发模式,在团队沟通上有天然的优势。开发人员实际上通过集成进行交流。如何快速知道别人的代码改动是否会影响我,如何快速知道我的代码对主线的影响,如何把我的代码放在一个大家都能看到的地方,让别人知道我的改动。解决这一系列问题的根本是:与主线快速集成。其次,集成的设计成本也低于PR开发模式中一个CI对应一个PR(activedevelopmentline)。最后,说到团队信任的氛围,主线开发模式的出发点是营造相互信任的开发环境,同时赋予每个开发者自由向主线提交代码的权利,这就是一条更加健康、可持续发展的道路。
