之前跟大家聊了很多如何参与一个开源项目,今天跟大家聊聊如何维护一个开源项目。

开源项目并不是代码的集合:每一个开源项目都能够视作一个小型的公司,它有自己的市场定位,有自己的战略目标,有自己的用户受众。因此开源项目维护有着大量工程以外的工作,包括但不限于跟用户沟通,设定路线图等等。本文旨在为加入开源维护者行列的新人提供一个基本的指引,讲解开源维护者应该需要做的事情。

First Day

开源维护者在开始一个项目之前,需要首先搞清楚项目的目标市场及其定位。

明确市场

开源项目的发展也大体遵循着强者恒强的马太定律:一个特定的垂直领域往往只能容下一到两个广泛使用的项目。

所以在开始一个项目之前我们就必须考虑清楚:

  • 项目想要哪个领域发展?
  • 这个领域都有哪些重要玩家?
  • 我们项目有哪些不可替代的优势?

如果我们不能够比现有项目做的更好,不如直接放弃加入现有项目。否则我们就需要进一步对领域进行细分,找到自己的差异化优势。

比如最近 SmartX 发起了一个新的开源项目 virtink,一个更轻量的 Kubernetes 原生虚拟化管理引擎。相比于社区现有的 kubevirt,它不考虑支持遗留硬件设备的模拟以及桌面应用场景能力,而是聚焦于在 Kubernetes 上运行现代化的云端虚拟化负载,因此 Virtink 就可以做到以更安全轻量的方式支撑虚拟化负载。

当然目前市场并不是一成不变的:字节跳动开源的 monoio 最开始市场是基于 io-uring 的 thread-per-core 模型高性能 Rust Runtime,但是在社区的演进中逐渐加入了对 tokio mio 的支持。

开源项目的维护者需要审慎地考虑项目的发展来选择是否加入或者进入某个领域。

立下愿景

文章合为时而著,代码合为事而写。开源项目必须要能够解决实际的问题,否则就无法形成一个可靠的开源共同体。

对开源维护者来说,为项目立下一个清晰的愿景就非常重要,它能够明确地向社区传达自己想要解决问题。

以 Rust 生态中的众多 HTTP Client 实现的愿景为例:

  • hyper: A fast and correct HTTP implementation for Rust.
  • reqwest: An ergonomic, batteries-included HTTP Client for Rust.
  • surf: Surf is a Rust HTTP client built for ease-of-use and multi-HTTP-backend flexibility.

能够清楚的看到,hyper 专注于提供快速和正确的实现,而 reqwest 则专注于提供易用,功能完善 Client,相比之下 surf 则更强调易用和多种 HTTP 后端支持的灵活性。这样的不同愿景将会为项目吸引来不同的用户群体,并将帮助维护者厘清项目需要增加怎样的功能,而哪些需求则需要被放弃。

Hyper 的作者在 hyper 1.0 timeline 中将明确 VISION 作为第一项重要工作:

Define the vision. Before anything else, we need to define what hyper even is. This will be an abstract vision showing what the goal and purpose of hyper is, based on what our users need.

最终,Hyper 将它的愿景定义为:

hyper is a protective and efficient HTTP library for all.

在这个愿景的推动下,hyper 有如下设计宗旨:

  • Open
  • Correct
  • Fast
  • HTTP/*
  • Flexible
  • Understandable

为了实现这些愿景,hyper 开始了一系列代码清理工作,将不符合这些宗旨的代码都予以清理和移动,不再保留在 hyper 主项目中。

Every Day

在项目的日常维护中,开源维护者需要为开源共同体清理障碍,营造更好的贡献环境。

集成测试

开源维护者必须维护一个良好的集成测试基础设施:在开源协作的环境下,不可能有一个测试团队来为所有 PR 运行测试,因此社区需要一个持续的集成测试服务来保证所有的 PR 都通过必要的测试再合并。

这个集成测试服务必须:

  • 快速:在可容忍的时间内返回结果。PR CI 时间越长,对贡献者来说体验就越差。
  • 稳定:能够稳定可靠的返回测试错误,而不是让贡献者经常怀疑是不是 CI 服务器出问题了。
  • 公开:能够公开的查阅错误日志,而不是一个完全的黑箱。
  • 可重现:贡献者能够方便的在本地重现错误,不需要反复 commit 来触发测试。

达成这些,一方面需要可靠稳定的测试基础设施,另一方面需要维护者维护好测试代码以及测试使用的脚本,缺一不可。

版本管理

开源维护者需要维护好一个稳定的版本发布周期。无论采用什么样的版本发布规则都可以,重要的是跟社区上下游达成稳定的预期。

鼓励下游依赖自己项目的 tag 而不是特定 commit 或 branch,这一方面有利于下游定期升级到最新版本,另一方面能避免意外地破坏用户。

在稳定的版本发布周期基础上,维护者可以定期的跟社区分享自己对未来的规划,设定 roadmap,管理用户们的预期。

用户沟通

前面提到开源项目都是为了解决特地的问题而存在,跟用户保持沟通能够更好的解决问题。维护者可以通过 Issues 接收用户的反馈,通过 Blog 和 Roadmap 向用户传达自己对项目的规划。

在跟用户沟通时有这样需要注意的地方:

  • 避免低声下气:在开源共同体中大家是平等,维护者并不是用户的免费劳力,面对恶劣的态度和不合理的需求可以直接给予拒绝,甚至根据 CoC 采取合适的惩罚。
  • 避免骄傲自满:对维护者本人也是如此,不要因为自己是维护者就对贡献者或用户恶言相向,平等是相互的。
  • 避免全盘接收:很多时候用户对项目缺乏完整的了解,提出的需求和方案往往是基于目前状态的改良。维护者需要避免全面接受用户的意见,根据自己的判断来决定是否实现特定需求。

保持专注

时常回顾自己项目的市场和愿景,不要屈服于诱惑。

在开源项目的维护过程中经常会出现各种热血沸腾的 idea,比如增加一个全新的功能,进行大规模的重构,对 API 进行完全的重新设计,架构进行大规模调整等。维护者需要回顾自己项目的愿景,决定是否要延拓项目的边界,三思而后行。一个非常好的办法就是写 RFC:写 RFC 能够帮助厘清需求的前因后果,需要的投出,产出的收益,让维护者冷静下来重新思考是否有必要进行这样的修改。在维护 OpenDAL 的过程中,我有很多的看起来很酷的 idea 都死在了撰写 RFC 的过程之中。

Last Day

公司内部的项目会关停并转,开源项目亦如是。随着兴趣的转移,社区的演进,用户的减少,项目会进入凋零期。此时需要能够坦然的面对自己的失败,尝试寻找维护者,在 README 中表明当前的状态,推荐替代的解决方案。

寻找维护者

在彻底停止项目之前,可以尝试在社区寻找愿意维护的人手。

cross-rs 过去是 Rust Embedded 社区的子项目,旨在支持将 Rust 交叉编译为不同的 targets。但是随着 Rust 本身的发展,很多 target 已经得到了原生的支持,Rust Embedded 社区对该工具已经不再有强烈需求,进而导致维护的力度也变低了。但是 cross 在社区中仍然有广泛的使用者,为此社区发起了 RFC for moving cross to its own organisation,将 cross 移交给独立的新组织 cross-rs

在移交完成后,cross 焕发了新的生机:有了稳定的发版周期,提交开始重新变得活跃,Issues 和 PR 开始有维护者进行 review 和 merge。

明确项目状态

但是在更多的时候,项目往往很难找到愿意接受的维护者。此时我们可以在 README 中明确的标注出项目当前的状态,比如:

  • 功能已经趋近完善,不再实现新功能,只接受 BUG Fix
  • 精力受限/兴趣转移/业务调整,项目不再维护

同时在 README 中推荐替代的方案,帮助用户快速进行迁移。

必要时可以在更新了 README 之后将项目设置为 Archive。

避免直接删库

买卖不成仁义在。

即使项目不维护了也尽量避免直接删库,这将破坏所有依赖本项目的用户,受影响不仅仅是用户的应用,还有身为维护者的个人声誉。

总结

开源项目的维护有很多代码无关的事情需要去做,希望加入开源维护者行列的新人在读完本文后能够有一个大概的方向。此外也希望大家能尊重维护者的劳动和付出,互相理解,共同把开源项目做好~