通过扩展实现可持续增长

Title

通过扩展实现可持续增长

Patlet

InnerSource 项目收到了太多的贡献,这会导致维护变得困难。通过在核心项目之外提供扩展机制,维护者可以用最小的成本和维护开销扩展项目功能。

问题

随着对成熟 InnerSource 代码库的贡献数量迅速增加,代码评审和维护的负担也随之加重。这将导致大量代码评审任务积压或过早拒绝新功能贡献。

项目团队如何才能更快地发布新功能、鼓励创新和实验,同时又能很好地维护代码库呢?

故事

有一个战略性项目,旨在将某一领域内的最佳创新集合到一个通用栈中,从而实现通用基础架构的重复使用,并提供标准的用户体验。通过 InnerSource,组织内从事该领域工作的各个团队都有机会开展合作,并将自己的创新成果贡献给通用代码库。

然而,当多位开发者同时进行大量并行贡献时,代码库的维护变得愈发复杂。这种情况给项目维护者带来了巨大的挑战。他们不仅要肩负确保代码质量的重任,还需要通过多种沟通渠道积极促进社区的健康发展。

项目维护人员面临职业倦怠的风险,原因如下:

  • 待评审的贡献者 PR 不断积压。

  • 工作不满意: 维护者的大部分时间都花在社区支持上,没有创新的空间。

  • 缺乏成就感: 并非所有贡献的功能都有足够的用户需求,并因此被采用。

  • 发布耗时: 代码库中的功能越多,测试时间就越长。

  • 维护活动增加: 随着新功能的增加,会出现更多错误。

我们常常在新功能正式发布前,投入大量时间对其进行完善和优化。然而,这种做法存在一定风险:如果这些精心打造的功能最终无法满足潜在用户的实际需求,那么我们为追求卓越代码质量所付出的努力可能就会付诸东流。

上下文

  • 一个具有战略意义的 InnerSource 代码库正随着多名员工对新功能的贡献而迅速扩展。

  • 评审人员与贡献者的比例悬殊导致 PR 积压越来越多,这拖慢了向社区发布新功能的速度。

  • 代码库质量下降,用户体验受到负面影响。

  • 代码库维护人员不堪重负,无法及时应对大量贡献和社区支持的增加。

  • 一些贡献的功能没有得到用户的采用,甚至可能完全处于休眠状态。然而,即使这些功能未被使用,它们仍然会增加维护开销。

  • 组织正在投入巨资强化新功能的贡献,以便在社区探索这些想法之前保持质量标准。

  • 这种模式适用于任何一种情况:

    • 维护者发现自己经常拒绝新功能提议,以控制项目规模。然而,这可能会抑制社区创新,限制项目的发展潜力。

    • 为了减少项目积压,新功能在没有完整文档、加固或测试的情况下就被发布,造成了糟糕的用户体验。这也在扩大代码库的规模,增大依赖关系图,使其难以维护。

约束

  • 维护者和产品所有者希望允许扩展、鼓励创新和试验,但又不过于限制贡献,同时还要保持良好的代码和质量标准,以保证用户体验。

  • 为达到产品标准,需要花费大量时间对功能进行加固和全面测试,但产品所有者可能希望在投入时间使功能成熟之前,允许更快地发布新的创新,供采用产品的用户探索。

  • 维护者希望鼓励社区分享将产品功能与其他用例相结合的创新,而不给主代码库增加更多的依赖性。

解决方案

允许扩展/插件进入大规模的 InnerSource 代码库,可以减轻代码库维护者的维护负担,并允许更快地发布新功能供采用产品及探索。这就将功能的维护工作转移给了扩展所有者,并允许主代码库支持已被更广泛采用且更具战略性的功能。

扩展机制为潜在的核心功能提供了一个有效的筛选渠道。它不仅充当了新功能的孵化器,还为社区创造了一个自然的强化环境。这种方式使得大量功能完善和优化工作能够在一个更加开放和灵活的环境中自然进行,避免了在耗时耗力的正式评审过程中进行这些工作。

为了使扩展模式取得成功,在架构上有一些注意事项需要牢记:

  1. 易于创建: 为了获得社区参与,扩展需要易于创建。

    • 创建一个初始扩展的代码库模板,作为新扩展的起点。这允许扩展在新代码库中添加新功能,与核心项目分开。该模板应提供与主代码库相同的模块化结构,并包含打包和发布扩展的框架。

    • 确保随着主代码库的变更,模板得到良好维护。主代码库维护人员负责更新模板以确保其与主项目兼容。遵循良好的版本控制约定,例如 semver,可以使这更容易遵循。

    • 建议主代码库的维护团队提供详细的指南,阐明如何在新版本发布时,基于旧版本的模板对扩展进行更新。

    • 添加从模板开发的示例扩展,项目开发人员可以参考这些扩展来了解如何编写模式良好的扩展。

    • 放宽对贡献者创建扩展的要求,绕过评审,以实现更快的发布或试验。

  2. 低耦合: 拥有包含功能的模块化组件可以允许松散耦合,其中扩展的更改不会影响主代码库或其他扩展的质量。

  3. 依赖管理: 每个扩展都应谨慎地指定其所依赖的主代码库版本范围,就如同处理其他依赖项一样。在选择其他依赖项时,也要特别注意与主代码库的依赖关系,确保所选版本与指定的主代码库版本相兼容。这种做法不仅能够保证扩展的稳定性,还能通过扩展的测试框架有效地捕获与主代码库之间可能存在的任何冲突。

  4. 测试策略: 如何单独和组合测试扩展?

    • 单独测试扩展: 扩展模板将提供一个测试框架,供扩展开发人员用来测试添加的功能。这可以包括单元测试、运行时性能和质量测试的框架。

    • 与主代码库结合测试扩展: 扩展开发人员有一种模式良好的方法,可以针对主代码库的特定版本测试其扩展,而无需主代码库维护人员的参与。

    • 与其他扩展结合测试扩展: 为每个扩展提供全面的测试框架可能会显得过于繁重。如果扩展之间保持适度的松散耦合,冲突的可能性本来就较低。然而,若用户在组合使用扩展时遇到冲突,他们可以直接向相关扩展的维护者报告问题,由这些专业人士来解决兼容性问题。值得注意的是,当扩展发展到生命周期的后期阶段,并有机会被合并到主代码库时,它将与库的其他部分一起进行全面测试。在这个关键阶段,所有潜在的依赖冲突必须得到彻底解决,以确保整个系统的稳定性和一致性。

  5. 可发现性和可用性:

    • 通过显示用户已创建并希望共享以供产品使用的扩展的发布页面,使扩展易于被发现。

    • 允许在主项目中注册扩展,以便用户可以在原始项目中利用扩展,从而保持相同的用户体验。

  6. 扩展的生命周期和可维护性: 建立扩展从创建到移植到主代码库的生命周期,以及明确的所有权准则。

    • 扩展创建者继续维护扩展,提供任何支持并修复缺陷。任何未维护的扩展都不会从发布页面列出。

    • 创建何时可以将扩展移植到主代码库的标准,例如内部产品对扩展的采用以及对该功能的需求。

    • 扩展到主代码库的移植过程将遵循库维护人员制定的更严格的代码评审指南。

遵循这些原则可确保:

  • 开发人员无需编写大量样板代码,即可为项目生态系统添加新功能。

  • 主项目的所有用户都能以可重复的方式发现扩展功能;代码还没有合并入主库并不意味着它没有价值。

  • 维护者的负担会减轻,直到扩展证明它填补了主项目中的重要空白。

  • 核心项目的通用代码(如基类和实用功能)可以作为扩展项目领域的新开发的起点。这就避免了事后移植创新工作的需要,从而减轻了为项目开发新功能的总体负担。

  • 开发人员更有可能为他们的代码库做出贡献,并继续参与维护和社区建设,这也有利于整个项目生态系统的健康发展。

结果背景

  • 项目能够通过增加新功能来扩展,同时不会增加主项目代码库的维护成本。

  • 更快地发布新功能供社区探索,鼓励创新和试验。

  • 将耗时且成本高昂的代码审查和功能完善过程推迟到功能已经证明其实用价值之后, 以有效降低企业的投入。

  • 可能引入的一个后置问题——如果扩展无法完成一个完整的生命周期,会怎么样?

    • 如果一个扩展在一段时间内没有被采用,也无法围绕它建立一个支持维护的社区,那么就需要扩展所有者在他们希望的任何时间内继续维护它。如果扩展没有得到维护,就会被取消发布。

    • 如果扩展开发者无法继续维护其项目,而社区中的其他开发者希望继续支持该扩展,他们可以继续维护该扩展。

已知实例

  • IBM 公司采用了这一解决方案来扩展InnerSource 人工智能类库。扩展库为开发人员提供了一个灵活的平台,使他们能够通过添加新算法来丰富人工智能类库的功能。这不仅促进了技术创新,还为公司内部社区创造了分享和交流的机会。与此同时,核心库保持精简,仅包含经过充分验证和广泛采用的关键算法。这种结构设计确保了在项目规模不断扩大、贡献者增多的情况下,核心库仍然易于维护和管理。

别名

管理大规模贡献的扩展

状态

结构化

作者

  • Sukriti Sharma, IBM

  • Alexander Brooks, IBM

  • Gabe Goodhart, IBM

翻译校对

Last updated