|
作者:marinewu,腾讯PCG工程效能平台部专家腾讯PCG工程效能平台部自2020年开始进行大仓基本能力建设,并在2021年与工蜂合作成立了代码大仓研效联合项目组。在此,我们想分享大仓/单仓踩过的坑。我们认为这些坑是真实存在且很难避免的,不是小马过河。单仓并不简单。成功的单仓所带来的效果绝不止简单的代码聚合,但成本是大量的工具支持以及工程实践。单仓像放大镜,可以将优秀的工程实践以极低的成本推广,但同时也会将错误迅速放大。同时,向单仓迁移的过程也有相当程度的风险。本文会详细讨论单仓的益处、挑战,以及我们对挑战的应对之道,以供参考。单仓/大仓是什么?单仓:指Monorepo,或单一仓库,是指将多个项目放在同个仓库中的版本控制策略。单仓在实践中可能有不同规模,如中心级、部门级、BG级、公司级单仓。业界最著名的单仓是Google的google3,除一些开源项目,Google内其它所有代码都放置于其中,有超过20亿行代码和超过86T的容量。目前公司内部成功应用的单仓大多在部门级及以下。大仓:指大型规模的仓库。“大型规模”并没有严格的定义--在本文中,我们将大仓定义为“单机已无法承受其容量的仓库”。一个粗略的估计:大于五千万行代码或容量超过100G的仓库即为普遍认可的大仓。单仓是我们的目的,而大仓则是(终极)单仓的必然结果。为什么要发明单仓?单仓的设计核心起源于"OneVersion"的哲学,并内化了规模化的思想。Scalability可规模化在软件工程中,我们希望“解决一次,解决全部”。即,任何的一个良好的工程实践,都以自动化、规模化的手段几乎零成本地推广到所有的团队及代码。这样可以减轻开发人员的心智负担,使之将更多的精力放在创造性的工作上。典型的例子包括代码静态检查、自动化测试及持续集成、开发流程标准化、工具统一化等等。单仓极大地简化了规模化:当所有代码都放置在一处,并且高度统一,则所有的工具都可以规模化地在单仓上作业。SingleSourceofTruth->OneVersion从单可信源到单一版本Singlesourceoftruth(SSoT)原则,是指开发人员在任意时间可以确定代码仓库内的哪个分支是唯一可信依赖源(SSoT)。在CVS中,单一来源是核心原则;在DVS中,如git,在现代的业界实践中也采取了该原则,即要求永远都只有一条主干,且所有的分支(除了发布分支)最终都会被收拢回主干里。单一版本(OneVersion)则更进一步,是指在任意时间,代码库内的每一份组件、每一个依赖只有一个版本。对内部库而言,这意味着使用主干开发(见下),并且必须在主干HEAD上依赖。这是一个非常强的约束——这意味着除了终端制品,任何一个内部被依赖的库都不能通过分支发布,而必须保持自己在单仓的主干上一直是发布状态。对外部依赖而言,同一个第三方库在单仓中永远只会引入一个版本。不强制SSoT/OneVersion的版本控制策略往往通过制品/版本分支发布。这意味着在整个依赖关系图之上还有一个版本的维度。这也是在开源社区/SDK发布商通常采用的策略,其根本原因在于并非完全掌握下游用户的情况。这样除了导致较高的维护成本,还会导致依赖关系难以满足(Dependencyhell,依赖地狱)。主干开发Trunk-baseddevelopment基于版本控制的协作模式一般分两种:分支开发:每个分支对应一个功能,开发者在这个分支上开发,直到最后完成功能后合并回主干。这是现在小仓下的主流模式。主干开发:每个分支只对应一个简单的修改。每个开发者在分支上完成修改后经过CR尽快合入主干。这是在单仓下推荐的开发模式。两者的主要区别在于分支存活时间:保持主干始终健康,将所有的commits尽快小批量合入的是主干开发;以feature为单位,当feature完成之后再重新合入的是分支开发。虽然听上去差异微小,但从分支开发迁移到主干开发对研发模式的影响深远。请务必确保您的团队深入理解前置需求(挑战)和长期影响(益处)。下文将分别阐述。Case:APIDeprecation考虑以下常见场景:一个公共库的作者如何deprecate旧的API并提供新的API作为替代品?小仓场景:这个公共库按版本分支或按制品发布。API可以在下一个版本直接更新,并强制API调用方对API进行更新。这样的好处是API提供方的责任较简单,坏处是每一个API调用方都需要自行更新代码,并且API提供方无法保证自己的新版API已经被使用。单仓/主干场景:公共库只提供源码依赖,并不按版本发布。这样,我们需要保证公共库在主干上始终是正确的。APIdeprecation时,API提供方可以查找所有API调用方,并且发动大规模自动修改,原子性地将所有的旧API调用更新至新的API。这样的好处是API调用方的责任更简单,但是这种修改只有在能够查找API的所有用户、并且整个持续集成水平较高时才有可能。从上面的例子可见,在单仓/主干的场景下,很多代码的维护工作可以左移并规模化。减少重复劳动、大规模的工业化,是我们启用单仓的原动力。益处以下的益处基于开发者视角和代码维护者视角。再次提醒,单仓想要达成预期效果需要工具、流程和文化三方面的准备,以及长期大量持续维护。贸然迁移到大仓不但不会带来收益,反而会导致项目和代码管理彻底混乱。请确保您对挑战部分有所准备。主干开发主干开发是为了进行持续集成。频繁地、小批量地构建/测试是持续集成的关键。通过主干开发可以:避免较长的稳定期,使持续集成可以持续运行,均摊每次合入的风险使不同特性的开发成员可以及早地了解其它团队可能对代码结构的影响避免合流日必须大量合并冲突的痛苦降低CR的难度和成本:主干开发下每个合入主干的提交更小,而CR的难度随提交修改量高于线性增长,因此CR的难度均摊更小。另外,强制小批量合入主干的提交可以将CR的责任从分支所有者转移到代码所有者,这更利于对代码质量的长期维护。更容易的代码复用与分享在大仓下,所有的代码会变得更公开透明。这便于我们抽象出共同的需求,建立公共库与公共框架,也便于我们学习更优秀的代码,以及寻求常见问题的解决方案。另外,我们对公共代码的调用可以变得更简单:直接从代码层面依赖,而非必须使用包管理/制品/跨仓库的依赖。例子:查询API的使用方法需要一个调用非常复杂(例如一个有几百个fields的protobuf)的API。如何才能正常调用?Stackoverflow里不存在答案,而码客似乎也不适合提问,难道只能找API的原作者询问用法?如果在大仓中,可以查找该API已有的在生产环境中的调用作为极好的参考。相反,在纷乱的小仓,缺少好的代码检索工具,很难确定API到底在哪里被调用。需要:工具编译系统:统一的优雅的编译系统可以降低代码的复用的工作量。工具权限控制工具代码浏览工具开放协作的文化大仓模式下会带来/诱发合作模式的改变。我们认为现代软件开发关键在于团队合作,因此一个能够增加透明度、允许更高层面合作的开发模式能够促成更透明更亲密的合作。更进一步,根据Conway'slaw,一个所有人的代码都公开透明、亲密协作的组织能够产生更加公开透明、结构亲密协作的产品(注意:这里协作不代表耦合)。在大仓模式和主干开发的模式下,我们可以更方便地主动去为其他组的项目提交代码,例如修复缺陷或实现我们所需的新功能。这个组可能是姐妹组,也可能是相隔甚远的项目组。我们认为作者对代码有太强的归属感,即有强烈的领地意识,对开放协作文化有害。隐藏代码或不允许别人触碰自己编写的代码是反模式。允许别人修改,自己由CR做最终决定,是更为开放和有效的合作模式。例子:公共库/框架一个公共库/框架的诞生往往通过两种途径--自顶向下和自下向上。自下向上指通过逐步聚合抽象公共需求和组件。例如一个顶目的开发过程中,基于实践抽象出自己项目的公共库;慢慢地,该公共库与其它项目的公共库的共同部分会产生相当程度的重叠,我们便将这部分交集进行整理和泛化,升级为更高级别(如产品线)的公共库。经过更进一步的抽取和整合,最终形成公司级别的公共库。如果没有足够的透明度和协作,这种聚合几乎是天方夜谭。需要:流程·代码评审:开放协作的文化必须要通过严格的流程来制约流程·结构管理:需要规范来控制大仓的目录结构,否则随意的创建目录会非常快速增加仓库熵以致于不可控原子修改AtomicCommits原子修改指我们可以在一个提交中修改在单仓中的多个项目的代码并同时生效。这对仓库始终保持在一致状态至关重要。与此相反,在小仓场景下,我们需要在每个需要修改的仓库发送提交,这意味着原子性不能保证,即我们无法保证这些提交同时生效。一个典型的例子是同时修改API接口、实现以及所有的API调用者。如此,我们就可以避免同时维护同一接口的两个版本。注意:原子修改也无法解决C/S不统一的问题。即对API的更改,即使对实现和调用的更新和API更新一起是原子提交,也无法解决C/S面对的接口不一致的问题。因为C/S的发布周期不会因为原子提交而实现原子化。大规模修改LargeScaleChange由于开发人员可以访问所有项目,因此我们可以批量地进行大规模的自动化的简单修改,例如API更新、自动清理长期未被引用代码等,以维护代码质量。这种由工具团队发起的大规模的修改在大仓开发模式下十分常见。需要:工具·持续集成:只有保证足够高质量和覆盖率的自动化测试、足够优秀的持续集成才能让我们有足够的工程信心进行大规模的修改。工具·大规模修改:为了能自动化进行大规模修改,需要能够自动按规则大范围修改代码、自动格式化代码的工具。代码治理简单代码单一的存放位置使得代码作业更加简单。例子:代码扫描我们希望建立知识库,因此希望扫描所有相关仓库内的markdown工程文档。小仓场景下,我们需要维护所有相关仓库的列表,克隆并扫描每个仓库。单仓场景下,只扫描一个仓库即可。依赖管理简单在大仓里,第二方和第三方依赖可以直接依源码导入,并且只有一个版本。这样可以极大地简化我们对依赖的治理。而且也可以减少已经标准化的项目对引入第三方依赖的成本,因为这些引入的依赖需要标准化,如Bazel化。注意:第三方依赖的治理本身是需要大量工作的。详见[挑战·依赖管理]。挑战以下挑战基于单仓维护者视角。这些挑战是我们在以往大仓开发/大仓迁移过程中真实踩过的坑,而非臆想。我们鼓励所有考虑迁移单仓的团队在迁移前认真检查自己是否对以下挑战有所了解并做好准备。另外请注意,以下尚未穷举所有挑战,您需要随时准备迎接新的挑战。单仓不是银弹。事实上,单仓/主干开发不应该做为一个孤立的工程实践,而更应该被视为一个工程实践的放大器--除了固有的挑战,它可以实现工业化,放大其它的流程/工具的收益;但是如果没有足够有效的工具和严格的流程及实践控制,大仓几乎就是灾难的代名词。下文中“单仓所需的工程实践”并非严格的限制,但这正是陷阱所在--不满足这些前置的单仓还不如小仓。小仓虽然也需要这些实践,但是至少可能不会有这么强的扩散效应。如果妥协,即在单仓内完全采取和小仓一样的严格隔离项目并采取分支发布的策略,则迁移单仓的意义基本被消解。我们将挑战分为两类:单仓:由于多个项目在同个仓库中所带来的治理挑战大仓:由于仓库的体量导致的可扩容性挑战所有大仓引起的挑战在下文有特殊标明。如果您的单仓规模较小可暂时忽略,但请做好仓库规模增长的准备。权限控制代码迁移到单仓会引入对权限控制的新要求。提交权限目前我们的提交权限以Git仓库为最低颗粒度。在单仓中,仓库级别的权限控制不足以支持多个团队、多个项目在单仓内作业。因此“项目”级别的权限控制是必须的--在单仓场景下,这需要目录作为最小颗粒的权限控制。同时,我们不建议照搬小仓的提交权限机制,即“只有目录的owner才能在该目录下提交”。我们认为提交权限需要在CR层面解决,即并非“只有有权限的人才能提交”,而是“只有有权限的人作为作者,或者作为评审者同意提交,才能提交”。我们的实践:引入类似于Chromium的OWNERS机制。机制的关键在于:OWNERS,而非commit的作者,为代码质量负责。OWNERS机制需要具有高度的灵活性。默认,OWNERS的权限为所在目录树。但是OWNERS也可以为文件(per-file)级别提供颗粒度。OWNERS的定义不应该在仓库根目录,而应在所在目录。并且,OWNERS文件应该是可继承的。例如,/foo/bar/...的OWNERS应该是:我们与此同时将设立一个工具以自动为commit自动分配reviewer,因为在大仓下寻找正确的reviewer会相对更困难。可见权限一个不太常见的、与大仓的设计目的相反的需求是隐藏代码。有一些代码是必须需要维持私密性的,例如与权限相关的代码或者具体高商业价值的代码。所以这些代码需要只对一部分人可见,并需依赖大仓其它代码进行编译。这里有几个选项:在大仓内提供更细颗粒度(如目录级别)的可见性控制。需要代码托管方的支持。私密代码放入小仓中,以小仓的权限控制来保证保密性。大仓保持所有代码的可见性。注意:这意味着可能有小仓向大仓的依赖,可能需要版本控制工具的支持对大仓的依赖。我们的实践:暂时未遇到,视情况解决。清除提交一个更加少见的需求是清除一个提交。例如,一个提交泄露了高商业价值信息,需要及时清除。如何从仓库历史里以及从开发者本地的克隆里及时清除是一件困难的事情。在小仓场景下,我们可以简单地将提交所在仓库归档为私有,将该提交之前的所有历史复制到另一个新的仓库,将新的仓库公开并删除旧的仓库。在单仓场景下,其影响面会比原来大。我们的实践:暂时未遇到,视情况解决。巨大代码存储空间大仓需要的存储空间可能会非常大。以Google为例,2015年时已经有20亿行代码,仓库超过86T。这个大小对版本控制造成了非常大的挑战,尤其是默认Git会下载整个仓库。这对代码托管方、开发、代码分析方(如CI/数据分析/...)等原本可以单机承载单仓容量的系统都会造成更大的压力。我们的实践:我们与代码托管方,即工蜂,合作成立了代码大仓研效联合项目组,以解决扩容问题。可扩容性(该部分为大仓引起的挑战。如果您的单仓规模较小可暂时忽略,但请做好仓库规模增长的准备)可扩容性的核心挑战在于,如果工具是以仓库为操作单元,则其随开发人员数量的增长往往不是线性的,而是以不低于二次方增长的。举例,考虑每次持续集成都编译整个仓库,则如果人员增长10倍,可以粗略假设仓库大小增长10倍,提交频率增长10倍,则整个编译量会增加100倍。我们认为所有的所有开发工具和流程都应该优化,使之随仓库的规模上升线性增长。如果无法保证,请保证您的单仓较小。版本控制版本控制工具将主要面对两个挑战:代码存储空间大原生Git对仓库的最小操作单位是仓库,即不支持部分检出。这有其设计目的和历史原因,因此原生Git并不适合大型仓库。为了缓解这个问题,Git本身做了很多尝试,如支持Git-Lfs,GitSubmodule,Gitsparsecheckout,Gitpartialclone等,但是更像是在打补丁。主干进展迅速大仓乃至单仓的提交非常频繁,主干进展很快,因此需要频繁进行rebase。这对rebase的性能有所要求,并且可能需要您提交锁的设计。如果使用Git作为版本控制工具,在迁移到大仓之前,请确保您对仓库大小有所控制,包括:使用Git-LFS,尽可能地把非代码的大文件,如资源,托管到LFS。定期归档Git历史,保持.git文件大小可控。强制采取Squashandmerge方式合并,保持主干简洁。我们的实践:我们对Git进行了改造,开发了基于懒加载的检出方案。基于前文所提到的CodeaccessAPI,我们实现了对文件的按需检出。在初次clone时,所有的文件都只有元数据被下载,只有当需要获取文件内容时才会真正下载文件。构建系统我们强烈推荐在单仓使用统一的构建系统。至少,每门语言应该使用统一的构建系统。否则,您可能使用的不是一个单仓,而是一个将众多小仓放在一起的空间。我们认为一个适合大仓的构建系统应该满足以下要求:构建可以是局部的。整个大仓应该作为一个方便彼此之间的依赖,但是在构建时应该允许只编译当前感兴趣的模块。构建可以是增量的。有限的修改应该只触发有限的重建,并且可以保证正确性。构建的配置应该是显式的、代码化的。理想状态下,一个repo+branch+commit+buildtarget即可以完整地、唯一地确定编译产物。这对CI/CD非常重要,即CI的正确性可以保证制品的正确性。依赖关系应该是显式的,包括传递依赖也应该是显式的。这样可以方便我们对系统模块化,并且更好地管理依赖,包括第一方以及第三方依赖。一个常见的错误是,把构建速度作为选择构建系统的唯一指标。另外,我们推荐统一每个语言的编译器以及对应版本。我们的实践:我们采用Bazel作为唯一的构建系统。Bazel有以下优点:Bazel的编译是局部的。Bazel的编译是增量的,并且提供了分布式中间结果缓存以及分布式中间结果构建接口。搭建分布式缓存和分布式构建集群对Bazel的编译速度提升效果非常明显。Bazel的编译声明是显式的,并且所有的依赖关系也是必须的显式的。Bazel是跨语言的:Bazel的核心是一个构建引擎,所有的语言支持都是以DSL的扩展实现的。如果要建立单仓,推荐考虑Bazel作为构建系统。但是请注意:Bazel本身对构建系统需要有较深理解。我们推荐在您的组织内培养一定数量的专家对构建进行维护。请不要使用Bazel4.0之前的版本。Bazel4.0是第一个LTS版本,在此之前的版本Bug甚多。另外请尽量只使用LTS的版本。(大仓体量下的)持续集成一个能够支持大仓体量的、可伸缩的持续集成系统是高度不平凡的。在设计持续集成系统,请确保已考虑以下可能需要解决的问题:持续集成的自动测试应该测试哪些目标a.全量测试:朴素地编译整个大仓里的所有项目、执行所有测试可能并不是一个可行的方案,因为耗时可能过长。b.人工精准测试:人为定义一个“项目”,当项目内的文件被修改时,执行所有项目相关的测试。但是该策略可能难以保证所有被影响的目标都会被测试到,尤其当修改的代码是公共库时。c.精准测试:从构建系统出发,计算被修改的文件所可能影响到的所有测试。但是这需要构建系统的支持(这也是为什么我们推荐Bazel的原因之一)。另外,大型测试,如需要搭起多个服务的系统测试,依然需要一个不基于依赖关系的测试策略,即需要人工精准测试。自动构建/测试的体量可能超过单机承载量,即使已经采取了精准测试。您可能需要将测试任务分片。持续集成的构建机需要快速拉取代码进行构建和测试。请注意在大仓的体量巨大时,构建机可能每次构建都重新下载整个仓库的代码可能并不可行。您可能需要在构建机上缓存代码或者采取其它的裁剪策略保证您的持续集成不会因为下载代码花费过长时间。您可能需要严格执行持续集成的“主干永远是绿的”政策,即,主干上持续集成变红/变黑之后,受影响的团队需要立刻修复问题,或者,无法快速修复时,还原相关修改。a.您可能希望通过"PreMR100%成功"保证主干永远是绿的。遗憾的是,这在大仓中无法做到。由于主干进展很快,我们在执行PreMR集成测试时的主干HEAD与实际合入时的HEAD可能相差甚远,这可能导致中间会由于被依赖文件的修改而发生合入之后的测试失败。因此您始终需要PostMR,且PostMR需要更大范围的测试,因为这种情况只有在PostMR的自动化测试才能发现。b.您需要对主干上“失败肇因识别(culpritfinding)”准备策略。这是指,PostMR由commitA失败的测试(记为T)未必是由A导致的。如果T上次测试通过的commit是B,则所有在(B,A]之间的所有commit都可能是肇因。您可能需要对所有测试目标一段时间持续集成执行历史的记录。并发的执行自动测试的请求可能超过构建机的数量。您可能需要机器锁,或者一个更好的构建队列调度器。当PreMR和PostMR同时在排队时,您可能会需要给PreMR更高的优先度以不阻碍开发人员的工作。我们的实践:我们设计了新的持续集成服务。质量保证所必需的工程实践主干开发注意从分支开发到主干开发需要大量的学习成本,不可能一蹴而就。未实操过主干开发的开发者很可能难以想像如何采用这种方式工作。我们推荐进行大量的培训,甚至类似于导师制,来培养团队主干开发的习惯和能力。我们强烈不建议使用考核等行政手段作为唯一的推进手段,因为向主干开发的切换并非只是意愿问题。为达成主干开发,除了开发人员的培训,您需要在单仓中以下工具/实践:小批量开发。开关系统。当一个特性尚未开发完成,您需要在主干中通过开关将该特性屏蔽,使用户和其它特性完全不受该特性影响。开关系统分为两类:编译时开关和运行时开关,区分在于开关可以判断状态的阶段。我们推荐:通过编译时开关进行主干开发协作;通过运行时开关进行上线发布以及A/B实验。这并不局限于大仓。高优先度的代码评审:相比于开发,CR的优先应该更高。保持CR流程的畅通是主干开发的必要前置。保持主干健康。当主干上出现了构建/测试失败,需要开发者停止当前工作并立即修复CI问题。持续集成,见下。(单仓中的)持续集成主干开发和持续集成是相辅相成的关系。尽快往主干的提交使得持续集成可以更细地触发,而持续集成是使主干代码始终保持在可发布状态的主要保证。在单仓中,请确保测试的质量及覆盖率足够支撑持续集成。持续测试是持续集成的主体。如果没有足够的测试,持续集成能起到的作用非常有限。持续集成应该标准化。您不会希望看到单仓内的每个项目都有独立的几十条流水线,整个单仓有几万条流水线。这样无法达到持续集成所希望达成的质量保证。持续集成需要尽快得到结果。保持持续集成的畅通是主干开发的另一必要前置。我们的实践:我们要求测试质量和覆盖率必须达标。我们将测试的质量和覆盖率置入EPC指标。我们对流水线进行集中治理和标准化。代码评审代码评审在大仓+主干开发的模式下非常重要。因为大仓的代码的合作程度更高,依赖关系会更加复杂,在大仓内的坏提交可能会比原来在小仓内有数百倍计甚至更高的破坏性。在维护左移与加强协作的情况下,代码的维护者很可能不是代码变更的作者,而是代码变更的评审者。如果没有足够良好的代码评审文化和实践,如果代码评审形同虚设,就无法通过代码评审来保证代码变更质量,则您可能需要考虑先培养代码评审的文化。高频次的细致的代码评审对代码评审工具有更高的要求。代码评审工具需要和工作流程以及其它工具,如在线IDE、静态检查、持续集成和持续发布等等。代码评审的记录是宝贵的工程资料和技术积累,需要永久保留并且需要便于审阅回顾。代码评审的流程需要清晰简洁,代码评审页面应该直观、响应速度快且无bug。否则,开发人员可能会在被评审要求反复改代码之时迁怒于代码评审工具。我们的实践:我们采取了严格的CodeReview的政策:创立了PCG工程技能认证体系(https://iwiki.woa.com/space/SkillsCertification),引入了Readability机制。我们投入了大量精力培养工程师的代码评审文化,并且提供了大量的CodeReview的培训。CodeReview的质与量作为考核标准。结构管理大仓的结构管理需要慎重设计。随着开发人员的增加和仓库规模的增长,需要考虑如何将维持大仓目录结构,使得各团队在合作的同时又不失结构。一个好的大仓的目录结构应该是易于在未知情况下定位项目所在目录的,并且比较稳定。我们推荐在设计结构目录时考虑以下问题:考虑不同语言的代码命名规范、存放惯例以及定位原则。假如,C++路径与引入完全一致,而与此相反,Java没有类似的强制规定,但是惯例以com.tencent...为package前缀。它们应该被放在何处?API的放置。第二方、第三方依赖的放置。是否提供每个项目的目录结构的设计准则。是否提供脚手架。从经验上来看,与其说设计结构目录是设计一个最好的,不如说是设计一个最不坏的。依赖管理正向依赖:需要认真治理所有的第三方依赖。在大仓里如果不进行第三方依赖的治理,大仓/主干开发的优势就会慢慢消散。第三方依赖主要有两个治理方向:依赖可信:指所有的第三方源应该是可靠的。当前,我们对外部依赖并没有明确的限制,内部仓库会从Github甚至其它低可信源不受限地添加依赖。由于缺乏足够的代码审查,且导入众多且难以追踪,当前我们缺乏工具和流程解决第三方依赖的安全问题,如对高危漏洞的修复以及对外部源投毒的防御等。另外,这导致我们无法审查外部代码引入的LICENCE是否合规。依赖发散:指一个第三方库可能有同时有多个版本,或者一个版本的多个拷贝,或者一个版本的不同生成产物,在生产环境中使用。依赖发散可能会导致依赖地狱:指通过版本号来进行包管理时,由于依赖关系过于复杂,导致能够满足所有包之间依赖关系的版本配置不存在。一个理想的单仓是自包含的,即,所有的外部依赖都以源码的形式被引入了单仓。单仓自身能够完成对所有项目的构建而不需要构建时下载外部代码/制品。与此同时,您可能需要思考第二方库在大仓中的位置。反向依赖:大仓往往难以被外部仓库源码依赖。如果大仓内的库需要被反向依赖,请确保您有应对策略。通常有两个方案:允许外部仓库以轻量的方式依赖大仓,如制品依赖、或者按需依赖等;将被依赖的代码同步到一个小仓中,以供外部依赖。推荐实践及工具以下并非硬性要求,但是我们仍然积极推荐您准备以下实践及工具。另外请注意,我们推荐您制定并维护严格的工程规范,例如命名、分支拉取、发布策略、比腾讯代码规范更详细的语言规范。静态检查静态检查(staticanalysis),包括基于规则的代码扫描、基于抽象语法树的静态分析和基于编译插桩的分析工具。静态检查可以提升代码质量,并减少代码评审的工作量。静态检查的范围可以比现有代码扫描广泛得多,比如:APIdeprecationandautoreplacementDeadcodedetectionandautoremovalErrorpronecodesuggestion我们追求的目标是:将尽可能多的CR意见转化为静态检查。静态检查一般在请求MR时触发,若有可能,尽可能将静态检查左移,在开发/编译时触发。我们的实践:我们将在持续集成阶段执行标准化的静态检查,提高静态检查报告的强制力,并在代码评审阶段要求开发人员做出反应。同时,我们将提供IDE插件以实现静态检查左移。轻量的代码浏览/检索当我们拥有一个大型仓库,有一个良好的代码浏览器可以减少代码阅读的成本,可以更好地达到代码的复用和分享。我们希望拥有一个代码浏览器,并做到:轻量,理想情况下零设置成本;完整,能够看到整个大仓的代码以及历史版本的代码,以及blame信息;有用,具有语义索引,能够看到所有的引用与被引用,以及实现跳转;可以检索。但是,现有的浏览代码的方案都不完美:工蜂尚缺少语法索引IDE等本地阅读工具需要下载并进行本地索引我们的实践:我们希望有一个带有语义索引的代码浏览器,类似于https://cs.opensource.google。WebIDEWebIDE与代码浏览器一起可以降低代码修改和提交的成本。对于一些小的修改,例如纠正typo,轻量的工具可以极大地增加提交者进行修改的意愿。大迁移迁移总是困难的,尤其是与开发者日常工作息息相关的大规模的仓库迁移。当您要制定将众多小仓迁移至单仓的计划,请确保您考虑过以下核心问题:1.您是否需要提供紧急回迁方案?提供回迁方案会极大地增加迁移复杂度。如果您的单仓规模较可控(如
|
|