网络知识 娱乐 基于主干(Trunk)的软件开发利弊分析

基于主干(Trunk)的软件开发利弊分析

伴随OEM汽车软件自研的深入,风行于软件开发的主干开发模式(TBD )开始逐渐引入到汽车软件开发过程中。记得几个月前,客户发表了一篇公众号文章(汽车软件敏捷开发与分支管理),承诺撰写一篇补充文章。但因忙于客户项目的交付,不得空闲。现终得空闲,特记录主干开发模式在嵌入式产品开发过程中的种种注意事项,作为对自己承诺的回馈。


主干开发,Trunk Based Development,经常被称为“主线开发”或者“基线开发”。它实际上是一套代码分支管理策略,开发人员之间通过约定,向被指定为“主干”的分支提交代码,尽最大可能确保每个人看到的是同一份代码的最新版本,可避免分支合并的困扰,保证随时拥有可发布的版本。


主干开发是相对于我们之前常用的功能分支开发(feature branch)而言的。如图一所示,是典型的功能分支开发模式。


功能分支开发模式与主干开发模式各有利弊。在实际应用中,需要根据团队的实际情况,机动灵活使用。例如,如果某个功能非常复杂,开发团队需要尽可能避开其他团队干扰,全力攻关,实现该功能的主体。这种情况下,功能开发分支可以有效帮助团队避免来自其它无关功能代码的影响。但是,我们不得不说,这只能是临时措施,长远来看,及时回归主干却是不二之选。


图一


主线开发有别于分支开发或者功能分支开发的模式。实际上,无论是主干开发还是分支开发,都不是大问题。二者最大的差异是发生在Merge时刻。对于主干开发,开发者将自己的工作分成很少的批次,通过快速地,多批次的merge,减少了对主干上其他代码的依赖与影响,从而提高了开发效率。实际上,这个理念正是精益敏捷的“小批量,多批次,及时反馈”的典型应用。


与此相对应的,是功能分支开发。如图二所示,当功能分支长时间游离于主干之外,一旦Merge到主干分支时,将如同一颗炸弹扔进了平静的湖面,“一石惊起千层浪”,各种依赖,Trouble Shooting,会耗费大量的精力,甚至导致整个主干长时间无法编译,从而阻滞了整个开发的进程。尤其严重的是,如果整个产品开发存在多条功能分支,Merge 冲突会经常发生,将会产生断不清的官司。各开发团队可能相互指责,互不承认自己的代码有问题。Merge后的功能被破坏,也将相互推诿,认为对方搞坏了自己的代码,对方应该负责回归功能测试等等。


当然,读者可能反问,小批量提交也会出现阻滞整个主干开发的情况啊。是的,这个不否认。但经验数据告诉我们,这种功能分支的开发方式出现问题的概率要远远大于主干开发模式。主干分支开发的Merge过程,如果管理适当的话,概率会大大降低。


图二


主干开发的优点

  • 快速反馈机制

主干开发有两种主要模式,分别适用于小型团队(图三)和大型团队(图四)(注:图片来自于Introduction (trunkbaseddevelopment.com))。不管哪一种方式,其核心是频繁的,有质量的代码提交。开发人员可以按小时或者按天提交自己的代码,如此一来,开发人员可以快速得到代码的反馈(通过验收测试或者同事的代码评审),而不是将错误埋藏很长一段时间,才得到反馈。导致无法满足交付的最后期限。这种快速反馈的机制,对于团队的新同事,尤其有帮助。资深开发者可以随时帮助新人,评审其代码提交,确保代码质量。


图三


  • 团队意识的构建

软件开发过程中,很容易掉入“独狼”的陷阱。长时间使用功能开发分支,开发团队之间容易形成”我的功能分支”和“你的主干”这种泾渭分明的划分。通过主干开发模式,可以更好地培育团队合作的文化,构建“我们的主干”的想法。每一个团队会像爱护自己的眼睛一样维护主干的质量,保持其稳健与鲁棒性。


图四


  • 提高代码的重用性

主干开发模式可以让各种在研的项目代码及早集成,提高代码的重用性。据德国汽车工业提供的数据,每一台新车型中的大部分软件的改动,其实只有10%的差异性。但传统上,如图五所示的典型车型软件开发,项目与项目间隔离,基本采用类似功能分支的开发模式,加上核心研发能力在供应商手里,代码重用性非常低。


  • 提高开发的效率

主干开发,通常与CICD联合使用,提高了开发效率。通过将大的交付物拆解成,可以快速提交的小批次交付物。通过大量的自动化测试检测提交的质量,大大降低代码风险及Merge的痛苦。而这些测试为”绿色“的代码会被发布到生产环境上,提高了效率。


图五


主干开发注意事项


基于主干的开发,我们看到了诸多的好处。但是,是否就没有任何缺点了呢?其实,在主干开发模式中,我们同样会碰到各种坑,需要谨慎管理,扬长避短。


典型主干开发的全景框图如图六所示,开发人员将代码Merge到主干中,通过CI持续集成,确保主干健康。根据产品发布的需要,通过主干做Tag点,拉出发布分支,交付到对应的车型 (如图所示的发布分支A,发布分支B)。当然,在最后的发布分支冻结前,可能有多条发布分支,用于开发过程中的集成与验证验收工作。这里,我们忽略掉这些细节,姑且认为发布分支A是相对成熟,可以发布到对应车型上,用于汽车的型式认证。


  • “避坑”指南一

确保主干的可用性。设想每天都有几百位开发人员在向主干提交代码。如同向一条清澈的河流中排水一样,如果任何开发人员的不自律,可能会导致主干河流污浊不清。更严重的是,导致主干拥塞,出现“Broken the Trunk”现象。后果是主干无法编译,所有后续工作停滞。笔者之前所在的开发团队,曾经因为主干阻塞,无法编译,导致全球几百人的测试团队等待长达两周的时间,震惊朝野。为了避免这个问题,好的实践是,在如图六所示的Merge点,实施必要的质量管控,包括但不限于,必要的Pre-Merge测试, 静态代码检查,主干阻塞晴雨表以及代码评审等工作。通过这些必要的代码门禁和监控手段,确保主干的通畅。


  • “避坑”指南二

保障主干质量。同样,代码合入主干后,并不意味着代码质量就万事大吉了。很多时候,虽然本身的代码质量没有问题,但不幸的是,该代码破坏了既有功能,甚至导致系统无法启动。所以,强有力的自动化测试环境,庞大的自动化测试用例集,不断更新的测试用例等是保障主干清爽的第二道关卡。同样,从开发者这里,养成好的开发习惯,例如,合适的feature toggle ON/OFF开关,合理的条件编译设置,都是避免自己的代码破坏既有功能的好的实践。主干的代码质量(且不谈技术债)是如此重要,曾经出现过全球几千人的团队,“Stop and Fix”,停止手头的功能开发工作,全力解决主干的代码问题。这个成本是我们难以承受之重。


  • “避坑”指南三

代码的可追溯性。可以想象,如果每天有200多次代码的合入,如果一个月后,测试团队反馈你开发的某个功能或者修复的某个缺陷存在问题,估计你要哭倒车底下。几千次的代码合入,到底哪些合入是与这个功能或者缺陷相关的呢?所以,好的代码实践是确保代码的可追溯性。


  • “避坑”指南四

没有完成的新功能。如图六所示,Feature A -1部分代码已经合入主干,但它能够完整运行的时间点,必须等Feature A-2的代码合入才能完成。这种部分代码合入的情况,会经常发生,对后端的测试团队和主干代码质量造成很大的干扰。本质上讲,没有人愿意将没有完成的新功能(如一个没有实现功能的按钮)发布到生产环境中给用户使用。为了解决这个问题,我们可以使用新功能开关,在新功能没有开发完成之前,这个开关一直保持关闭,这些新的功能将会被隐藏。当新功能开发完成,测试通过后,再将开关打开或者将开关移除。不仅如此,还可以使用这个开关来做 A/B 测试。


  • “避坑”指南五

发布分支的拉出节点。发布分支是正式交付的版本,用于车辆的生产以及相应的型式认证。该分支的拉出点, 如图六所示的TagA,TagB点,需要详细规划。拉出点太早,导致应该具备的功能,如Feature A-2,无法如期进入发布分支。如果拉出点太晚,后续的产品验证与验收工作就无法如期完成,导致整个项目的延期。当然,读者可能争议,我可以及早拉出,后期通过OTA做升级。这样做,除了增加了开发成本(需要不断Parity发布分支与主干的内容),而且,并非所有功能都使用OTA。工信部发布的《关于加强智能网联汽车生产企业及产品准入管理的意见》,对汽车产品准入做出严格规定,汽车企业不能再简单通过OTA来更新已经通过型式认证确认过的汽车参数。


图六


  • “避坑”指南六

发布分支的问题管控。基于主干的开发要求开发的工作尽可能在主干上。即使软件代码从TagA点拉出发布分支后,在测试的过程中出现任何产品问题,开发人员需要将代码先提交到主干,然后,通过CherryPick的机制,将相应的代码合入到发布分支中,确保问题被修复。在这个过程中,需要有很好的机制确保应该合入发布分支的代码正确合入,不应该合入该分支的代码不能合入。这个环节管控不好,会出现发布分支始终不稳定,测试验证问题难以收敛,对车这种强法规要求的开发项目,造成很多问题。


  • “避坑”指南七

发布分支的生命周期管理。发布分支上车后,可能在未来相当长的一段时间内,分支将会存在,直至智能车的生命周期结束。这个过程中,可能还会有OTA新功能升级。这时候采取的方法可能有几种:在发布分支上做开发 (这实际上是严重违背了基于主干的开发理念);在主干开发,将新功能的代码commit到发布分支上;废弃原发布分支,重新发布新的分支。方法不同,优缺点不同,带来的结果也不同。不管哪种方案,发布分支的代码复杂度,代码量,与硬件的兼容性,对性能的影响,等等,需要产品发布团队详细规划。


  • “避坑”指南八

发布分支的内容管控。发布分支的内容需要发布项目团队做好详细规划,确保相互依赖的功能可以同时发布。避免出现虽然功能Feature B已经发布,但其潜在依赖的功能Feature D却没有同时发布,出现功能B无法工作的现象。如果该功能是用户高感知的功能,我相信项目团队恐怕又要熬夜修复了。同样,如果功能代码没有很好的追溯机制,在开发过程中一旦出现变更,需要将某个功能从发布版本中剔除。开发人员不得不加班加点,去查找每一行代码,并将其从分支中删除。这种做法费时费力不说,删除后的代码还不得不重新进行验证,弄得团队怨声载道。


  • “避坑”指南九

主干功能冲突。如果主干持续时间很长,会出现不同的车型要求的功能发生冲突现象,例如,如图六所示,Feature C只是车型B需要,而其他车型不需要。在这种情况下,如何合理编制编译的Make file,或者合理架构功能开关,需要项目开发团队相互协调沟通。


  • “避坑”指南十

主干代码的持续集成。主干开发的优势是需要强大的CI能力。如果某个团队,虽然号称的是主干开发,但主干代码却无法做到持续集成,不能及时给开发人员以正向反馈。最终会导致开发人员不得不回归分支开发,在自己的分支上做编译,测试。这样的话,主干开发就会名存实亡。


写在最后:


基于主干开发的“避坑”指南,只是笔者在管理开发及项目交付过程中碰到的种种问题。现实工作中可能远不止这些。也欢迎各位小伙伴反馈你碰到的问题。


最后,无论是分支开发还是主干开发,他们各有各的优势。开发团队需要根据自己的实际情况进行筛选,扬长避短。不能简单认为主干开发就是优于分支开发,或者分支开发优于主干开发。另外,我们必须强调,无论哪种开发模式,只是“术”层面的方法,最终要解决的是如何高效,快速,高质量地交付产品。无论是主干开发还是分支开发,持续的集成,及早发现问题,解决问题,是我们追求产品成熟度的不二法则。