开源社群的注意力经济

《大教堂与集市》中有一个著名的 Linus 定律,“只要眼睛多,bug 容易捉”。在这本书里面,作者讨论了开源社群的集市开发模式,以其开放的特征,以及提供源码从而支持参与者基于相同的真实的源码进行高效交流,驯服了大型软件开发的复杂性。诚然,在其论述中,基于源码的,快速发布反馈缺陷报告和补丁修补的开源协同方式,能够制造出 Linux 这样的大型开源软件。然而,这一论述却有一个隐含的前提假设,那就是开源软件得到了足够多的关注。

Linux 无疑得到了全世界开发者的海量关注,并且一直如此。开源运动的启蒙阶段,大量的软件都被开创式的制造出来,彼时少有其他开源竞争者的存在,因此眼球或者叫注意力能够投放到的目标是相对有限的。另一方面,开源社群彼时仍是小众圈子,天然地对进入其中的注意力有一个质量筛选的机制,除非货真价实的黑客,否则很难在当时的开源社群当中生存。

然而,如今的开源软件从覆盖面到数量上已经彻底超出了所有人的预期,在软件渗透到社会生活的方方面面的前提下,形成了几乎任何软件都包含开源组件的“软件吞噬世界,开源吞噬软件”的格局。越来越多的开源参与者,已经观察到“眼球不够用”的情况了。换句话说,整个开源共同体的注意力供给总量,不足以应对所有开源软件高效发展的需求。

在这样的前提下,开源社群的参与者,应该考虑如何分配自己宝贵的注意力,以促进自身利益与开源社群发展需要的双赢;开源社群的维护者或说开源软件的作者,还需要额外考虑如何吸引开源参与者乃至整个社会可能提供的注意力,从而维系开源软件的高质量,并放大高质量开源软件的影响力。本文将从开源共同体具体的案例出发,讨论这两个问题。

勿以善小而不为

讨论如何合理分配参与者的注意力,提高开源协同效率之前,首先要做到的是增加有效的投入到开源社群当中的注意力总量。为此,我经常在宣传开源之道的过程中提到的观点就是“勿以善小而不为”。也就是说,如果你发现了开源社群值得改进的地方,无论是关于软件代码的还是社群建设的,都可以尝试推进落地,而不用因为担心这个改进微不足道,而自我否定了采取行动的理由。

这一点我在《如何向开源项目提交第一个 Pull Request?》视频里也提到过。许多开源新手刚刚接触开源协同,就是从经典的 fix typo 也就是修复拼写错误入手的。从这样一个具体的工作出发,熟悉了开源社群的合作方式,知道如何把自己脑海中的一个改进想法落实到一个具体的补丁,如何与其他社群成员沟通并推进补丁合并到主分支。另一方面,开源软件的迭代与演进,虽然很大程度上是以实现新功能和架构设计作为主路线和里程碑的,但是使得一个软件能够应对严肃复杂的生产环境,稳定、可靠且高效地服务用户需求,占据补丁多数的反而是细微的改进,一个一个缺陷的修复,以及日积月累的代码重构。

我接触的第一个开源社群是 Perl 6 语言社群,参与 Perl 6 语言社群的第一个 PR 就是有关文档翻译的改进。从这个第三方文档的参与开始,我把 Perl 6 的官方文档整个梳理和修改了一遍。在此过程中,我了解到语言设计的背景,目前遇到的问题,以此在知乎上写了若干介绍 Perl 6 设计和使用的文章,改进 Perl 6 的测试套件 Roast中间语言 NQP 的代码。

可以说,没有当时“勿以善小而不为”的冲动,我就错过了一次乃至以后许多次能够推开开源世界大门的机会。因为这样的理念和由此带来的经验,我在得到一份与 Apache Flink 相关的实习工作以后,就轻车熟路地订阅了项目的开发者邮件列表,并且积极地从代码迁移工作出发了解 Java 软件工程的最佳实践,从修复并发问题的工作出发熟悉分布式系统的常见模式和问题,参与讨论向其他经验丰富的工程师学习,最终能够独立撰写功能提案并落地实现,在一年半以后成为了 Flink Committers 之一。

时至今日,在这样的观点的驱动下,我往往是看到软件有可改进空间,就会尝试参与贡献。如果某个社群碰到了我曾经解决过的问题,或者有解决思路正好缺事实检验的困难,我也会评估自己的精力尽力而为提供帮助。GitHub Stats 的个人资料页上显示我曾经参与过一百多个开源项目,其中许多只是我碰巧看到了或大或小的破窗,顺手为之做出修复。

我相信解决开源社群的注意力投入和分配的问题,不是在总量一定的注意力的基础上,舍弃一些应该投入的工作,人为选择“重要”的工作,而是激发开源参与者持续产生有效的投入到开源社群当中的注意力,提高开源协同的效率,减少不必要的损耗,从而使得每一项应该投入的工作都得到相应的关注。

遵守软件工程实践

关于不必要的消耗投入到开源社群当中的注意力的话题,前两天我在发布了一条推文讨论。其中典型的就是为了追求数字,违背软件工程最佳实践,选择低效的方式参与开源协同。可以从两个方向来看这类反面教材。

开源参与者的动机

第一个方向是开源参与者的动机。

去年六月份,Linux Kernel 邮件列表上出现了一封反对刷榜式提交补丁的邮件。

这封邮件首先提到删除死代码和修改拼写错误,对于一个软件来说都是有价值的。但是,如果同一个人或者同一个组织的人,在短时间内把同样的平凡的变更拆分成若干个小的补丁提交,以达到增加被合并的补丁的数量的效果,那么这将会过分浪费项目维护者的时间。试想,一个参与者可以在阅读文档的过程中提出修改拼写的补丁,但是他在阅读一篇文档的过程中发现的三个相同或位置上临近的拼写错误,非要拆成三个补丁提交上来,这就确确实实浪费了评审和合并补丁的时间。

尤其是对于并非首次参与的开发者,成熟的开源社群实质上对其有一定的心理预期,上述邮件的作者 Qu Wenruo 也提到

It’s OK for first-time/student developers to submit such patches, and I really hope such patches would make them become a long term contributor. In fact, I started my kernel contribution exactly by doing such “cleanups”.

But what you guys are doing is really KPI grabbing, I have already see several maintainers arguing with you on such “cleanups”, and you’re always defending yourself to try to get those patches merged.

这并不是说,修复拼写错误和重构代码是新手的专利,实际上,大部分开源项目的核心开发者也经常做着类似的事情。这里的区别就在于你是按照一个符合软件工程实践的心态以合适的节奏和平均质量产出补丁的,还是为了追求数字而刻意反复多次要求项目的维护者立即处理这类平凡的变更。我想对于绝大部分开发者来说,都能够感受到与这两种合作方协同的区别。开源协同应当是一种高效的协同手段,如果开发人员朴素地就能感受到一种行为是违背这一目标的,那么它一定是错的。

当然,对于偶尔看见的平凡变更,我会很乐意顺手合并。如果出现频繁刷榜的人,我的观点与 Qu Wenruo 在后续回复中提到的一致。要么,可以考虑开发一个工具系统的扫描同类问题,例如 rust-clippy / golangci-lint / spotless 等就是系统性处理代码中的风格问题和容易导致错误的片段的工具。要么,至少先表明你或你的团队将要做这样的改动,并且明确时间,与项目维护者达成一致后在确定的时间段内集中提出类似的修改,以一种 BugBash 的方式来处理。这也是我在 Apache ZooKeeper 社群推动采用 Checkstyle 来减少补丁之间无谓的空白符变更的方式。

这种在开源社群当中提前讨论的模式,不只适用于前面提到的平凡变更,对于复杂的补丁,其实更应该提前沟通而不是直接甩一个合并请求。我在两年多前写《如何参与 Apache 开源项目社区》的时候就提过这个观点。

对于大的特性修改,国内开发者特别是一些写多了内部代码想也不想就提交的人,会犯的一个常见的错误是没有修改的背景和抽象设计,直接就 pia 上去一坨代码,英语又差,别人看不懂他也解释不通。其实代码的提交是一个协作的过程,需要达成共识,并不是说甩一脸代码别人就会去看,特别是 Java 之类的很多样板化的修改的代码,diff 贼多信息量贼少。对于任何 non-trivial 的改动,都需要有一定的描述来表明动机;对于大的改动,更需要设计文档来留存记忆。人的记忆不是永久的,总会忘记最初的时候自己为什么做某一件事情,设计文档的沉淀对于社区摆脱人的不确定性演化有至关重要的作用。只有记下来最初是为了做什么事而做出的这个改动,以后移交代码或者教授新人的时候才好援引和解释。

其实,这也是提高开源协同效率的方式。核心参与者的注意力是有限的,层次化地讨论问题,渐进地融入到开源社群当中,能够逐步取得现有团队的信任,并在其他核心成员的帮助下完成需要更多投入的工作。

对于真的毫无意义的补丁,我会说明情况并拒绝合并。典型地,例如增加 Getter/Setter 式的自解释代码的测试,这类测试是冗余的;或者把 do-while 循环改成 while 循环一类的纯风格变更。

《ZeroMQ 权威指南》里提到,如果一个缺陷报告无人理睬,那么就代表着不是个问题,应该即时关闭。Apache SkyWalking 的维护者也表达过类似的观点,如果没人想修复一个问题,那么就说明这不是个问题,至少不是一个紧急或常见的问题。我认为,具体的策略是一回事,清晰地让社群成员及潜在的参与者尽可能了解社群的风格,确实能够有效减少不必要的摩擦和注意力消耗。

关于代码风格,我的观点是每个深入到模块当中的开发者都会逐渐把模块的风格和结构重构成最符合自己认知的模样,从而使得自己能够高效地相应需求做出后续变更。所以,长期参与的开发者,浸淫在某个模块当中的专家,才有权决定一个模块应该以何种风格来写成。这也是我前面在提到参与其他开源社群时,是“评估自己的精力尽力而为提供帮助”隐含的一层意思。如果只是我个人风格偏好不同,或者我有一个想法,但是并不能保证这个想法落地之后遇到衍生问题能不能及时响应,出于基本的负责任的态度,我不会认为项目的维护者理应接受这个看起来合理的提案。

开源维护者的动机

第二个方向是开源维护者的动机。

开源维护者很大程度上决定了开源社群的发展方向,他们的认识、行动与倾向将影响社群招徕何种注意力,以及注意力将会投入到哪些工作当中。如果开源维护者没有仔细考虑这个问题,或者简单地将开源协同所需的注意力投入等同于广告营销式的注意力收割,那么在实施过程当中就会引起开发者的反感和挑战。

@Xuanwo 在上个月的一篇文章《开源运营当论迹不论心》里以 TDengine 运营的“灭虫活动”为案例说明了这种缺乏考虑的弊端。文章当中提到,希望通过物质激励或者传播技巧招徕更多参与者,这本身无可厚非。但是,违反软件工程的实践,从维护者的角度刻意采用一种更低效的方式“改进”代码,利用人工地毯式搜索的低效特点,制造相同需求需要更多参与者的结果,虽然赢得了短期的喧闹,过后却是一地鸡毛。

这篇文章下面的评论当中 TDengine 的创始人提出了这个活动的动机。

@jtao1735 作为 TDengine 的创始人,认真读了这篇博客,很认同一个观点,那就是开源运营需要帮助开发者去解决真正的问题,包括 TDengine 的核心技术问题,而不是一些简单的 typo, 对于有一定经验的程序员而言,意义不大。我们当时发起这个活动时,是因为在几个高校巡讲,想吸引在校大学生加入进来,不少学生连 GitHub 账号都没有,因此把门槛大大降低了一下。

我对这段话也做了回复。

其实运营本身就有一些 marketing 的属性。“帮助”这个词太笼统了,不如说向社群成员和潜在的社群成员展示和分享 TDengine 是什么,TDengine 要解决什么问题,TDengine 是怎么解决这些问题的。从这三个角度出发,哪些地方是合适的切入点,谁是目标受众,就会清楚很多了。

每个层面的 contribution 都值得感谢,原文和我个人都不认为 fix typo 一类的工作不应该做,而是不用这样大张旗鼓的做。

想吸引在校大学生加入进来,不少学生连GitHub账号都没有,因此把门槛大大降低了一下。

这个说法也是不成立的。起码说明你的目的就是吸引在校大学生“进来”,也就是在 contributor graph 上出现,至于进来做什么,无所谓。这就是问题本身,而不能用来回应问题。GitHub 怎么用,首先 GitHub 自己有文档,而 PingCAP 为了解决这个问题,采取的做法是做一个系列视频【GitHub 新手指南】极简教程教你如何给开源仓库提 PR

最后一段也说明我在这一节开头的观点,也就是开源维护者的认识、行动和倾向将影响社群招徕何种注意力。

如果你关注如何将开源软件打造成高水平的软件。那么你所抛出来的问题就有一条明确的主线。例如 Flink 长期讨论如何在 Kubernetes 环境上部署,前后数年均在不断完善这项功能,从将其作为一个单纯的新的虚拟机环境,到利用 Kubernetes 的资源管理能力,再到整合 Operator 应用管理框架,吸引了许多专业人才贡献聪明才智和生产用例。

如果你关注的是如何提高无差别的 star 数或者 contributor 数量,而不是将它们作为主线发展的一个校正自己行为的辅助指标、副产品,那么为了这些数字,会去做一些与软件发展不相干甚至相抵触的事情,也就不奇怪了。

说到底,开源协同是一种上限极高、潜在参与人数极多的软件工程模型,要想在这样开放的环境下协同海量的潜在参与者,并不是一件易事。如果简单地想象成“开源即成功”或者“人越多越成功”,那势必得不到自己想要的结果。至于某些开源维护者把自己都不知道怎么做或者不愿意做的工作,异想天开地“向社群提出一句话需求”,幻想有个盖世英雄能够出面解决,那就是彻头彻尾的巨婴心态了。

《时代周刊》评价 Linus 的时候说,“有些人生来就注定能领导几百万人,有些人生来就注定能写出翻天覆地的软件。但只有 Linus Torvalds 两样都能做到。”这也侧面体现了开源协同所需要的品质之稀缺。同时,这句话也暗含着 Linux 的核心决策尤其是早期的核心决策,虽然已经有众多参与者的贡献,但是 Linus 是决定 Linux 要往何处去的那个人。这就反驳了上文提到的维护者想不清楚,反而希望另一个素未谋面的救星能够机械降神的荒唐。

类似的运营例子其实不少,例如 Alluxio 开源社区贡献积分奖励计划。其实单从市场声量和招徕用户方面,这些小奖品换注册、点击和转发等等,都是行之有效的 To C 营销手段。对于宣传大使(Ambassador)参与演讲和公开站台,如果采用报销行程和周边反馈,可能比起积分货币会是有效的方式。至于开源平台和技术上的评估,就不太适合用数量来简单衡量。

高质量的开源社群在软件开发上要的是精英社群,不能指望依靠一场活动“转换”一批参与者。这个过程是长期主义细水长流的,搞好流程和文档,找到外向型的社群成员,多多曝光就行。哪怕是搞活动或者专项冲刺,也需要注意和项目发展契合。比如 Apache 项目参与 GSOC 大多是社群切实需要的工作,而不是这样明显由脱离实际开发活动搞出来的标准化 issue 或 pr 数量。追求简单的数量容易引来刷榜的投机者,这些投机者投入钻营得越厉害,例如做出前文《开源参与者的动机》里提到的种种怪事,实际上社群整体的开发效率会受到越严重的影响。

如果真的希望投入资金激励社群开发活动,可以参考 The Perl Foundation Grants 的做法,建立一个拨款委员会和一套技术评审流程,把赞助者的钱用于激励完成社群期待完成的工作上。实际上,GSoC 的形式可以认为就是 Google 出钱给开源项目用于推动它们重要不紧急的工作的落地。让开发者运行开发者社群,才能更准确地吸引到匹配的参与者,并且利用好这些参与者投入的精力。

找到共同价值

既然应该让开发者运行开发者社群,那么对于这些承担了运营职责的开发者来说,具体应该怎么找到匹配的参与者并让这些参与者高效地参与呢?我想这里的指导原则是要找到开源社群和潜在参与者的共同价值。

这里需要强调的是,找到共同价值的过程,不仅仅是开源维护者发现潜在参与者的需求,匹配社群当中需要完成的工作,同时也是参与者理解社群的要求,配合其他社群成员共同创造价值的过程。

为什么强调这点呢?我在发布《共同创造价值》之后,有回复提到“共同创造价值,而不是欢迎谁给谁贡献代码,不存在谁给谁的事情”。这就是前半段所强调的,先来的社群成员应该平等地追求合作。我在观察多个开源社群具体的运营过程里也发现,存在“既看不起参与者,又要惯着参与者”的情况。这也是指标导向带来的不良后果。因为要追求数字,在执行过程中就弱化了对参与者的筛选,来者不拒;因为来者不拒,大量招徕的参与者水平不足,而又强行要求甚至需要他们完成复杂的工作。这样僵持之下,就变成了酷似考试请枪手,枪手还是无偿代打的局面。甚至于参与者在环境的误导下,认为维护者有义务解决他个人的问题。

这一点在《对话吴晟:真正伤害开源的是开发者本身》一文里也有提及。

真正伤害开源的,还是开发者本身。最常见的两个问题。一个是开发者,特别是中国的开发者认为,软件作者去帮助他人是天经地义的,因为整个软件是你写的,所以我来问你问题,你就应该有问必答。如果你不答,就认为你这个人摆架子。而不是考虑因为软件作者用了自己的时间提供服务,所以应该表示感谢。

所以我想要在这里特别强调“共同”这个词所隐含的平等的含义。

在这样的指导原则下,与人协作并不是一件难事。例如,我在成为 Apache Kvrocks (Incubating) 项目的导师之后,在构建过程当中发现了 CMake 脚本存在优化空间。我想起来在发布《CMake 是怎么工作的?》之后,@PragmaTwice 分享了他的一些经验,正好跟我想做的改进相符合。因此,我邀请他把他的经验实践在 Kvrocks 项目上,也欢迎他从 C++ 资深开发者的角度整体评审一下 Kvrocks 的工程质量。

很快,@PragmaTwice 就完成了一系列构建系统改造的工作,并且提出了若干个可以进行工程改进的思路。社群当中与他合作的维护者能够看出他在 C++ 方面的技术水平,并且交流起来没有障碍。我想如果他能持续参与到社群当中,并且愿意承担更多责任的话,成为项目维护者只是水到渠成。

其实,这才是开源协同本来就应该有的样子。因为自己想做,恰好有一个开源社群也有类似的需求,双方一拍即合按照软件工程的最佳实践推进开发。我想这就是向潜在的开发者宣传开源协同的魅力的时候应该着重提及的内容。因为如果你只盯着公司业务代码,尤其是在校学生都很难接触到实际的需求,对于技术成长有追求,希望看到自己的工作落地会怎么样的开发者来说,提供一个有现成软件基础的、有用户群体的开源软件,是一个很大的诱惑。

另一方面,这种共同价值并不局限于代码参与,甚至不应该局限于单一项目。我进入开源世界的大门是 Perl 6 语言社群,但是不再参与也有两年多了。我在完成 TiDB 测试迁移的工作的时候,前后协同了不下五十名开发者,其中绝大部分也在一段时间后离开。

但是,在这个过程里,首先我完成了工作工程上本身要达成的目标,提高测试代码编写和运行的方便性,减少不同测试的耦合,从而为软件测试覆盖和能够有自信地常态化重构做准备。此外,我做了一个完整的从提案到实施,从模糊的原则到具体每个场景的最佳实践的案例。此后开发的内存悲观锁等功能开发就可以 follow 这样的案例来进行。这样的软件工程实践能够影响一部分开发者,我已经很开心了。

最后,这些参与者当中有人留了下来,参与到其他开发工作当中;也有人告诉我,他是因为这个契机才开始参与开源社群。我想这跟我从 Perl 6 打开的大门进入到开源世界,最终在其他的开源社群当中发光发热,应该会是类似的。对于参与者来说,他所期待的基本价值之一是个人成长,那么我所维护的开源社群能够在他成长的过程当中跟他一起做出一些贡献,让这个软件更好一些,不也足够了吗?

当然,共同的价值可以是非常多元的。我个人的理想是提升整个软件行业,所以在写作的过程中难免会经常有所偏向。最近看到 Taichi 开源社群有个 Taichi Voxel Challenge 活动,邀请任何人基于 Taichi 创造自己的三维图形作品。这也算是邀请测试的一种形式。创造出来的作品看着非常有趣,“只是为了有趣”也是一个很好的共同价值。

Taichi 生成的庭院

如果开源社群的维护者所追求的“价值”,就是纯粹的数字,以满足自己依托开源软件创办的公司在宣传和融资或其他经营方向上的需要,或许前面提到的种种市场营销手段,就是维护者所追求的价值。这样的维护者与刷榜换取虚假的名声或者直接物质激励的投机者一拍即合,或许也可以找到某种共同价值。

但是,这与我所坚持和相信的“开源社群的目的是制造高质量的开源软件,开源协同是一种高效的软件开发协同手段”背道而驰。

我在写作《如何参与 Apache 开源项目社区》之前,就有许多人问过我“怎么才能成为 Apache Flink 的 Committer 呢”。那篇文章某种意义上是回应这个问题的,但是我并不是功利地说应该怎么去增加贡献量,怎么创造出新功能的需求,而是讨论怎么与其他社群成员协作,在交流和合作开发上有哪些技巧应该掌握。最后落脚点在“兴趣使然”,也就是目的是成为开源社群力量的一部分,齐心协力完成复杂系统的开发和价值交付。在这个过程里收获成长和信任,成为项目的 Committer 或者参与到整个 Apache 社群当中成为基金会正式成员,也就是顺其自然的事情。

同样,如果开发开源软件的最终目的只有商业成功,那么目前看到的可行的终局就是 MongoDB 或者 Elastic 的形式,也就是走向 Source Available 软件的道路,禁止其他参与者商用。实际上,我很推荐这些公司一开始就采用商业保护性质的源码可得软件协议,例如 Business Source License 1.1 或者加上 Commons Clause 等。如果你想清楚了这一点,你仍然能够通过拆分模块的方式生产开源组件,鼓励生态建设,或者简单地只考虑用户社群,用户当中有开发能力的人,本身对它采用的软件有改进的需求,也没有商业上的冲突,是可能会提交有意义的缺陷报告或者补丁的。但是,这是一种商业上的“免费增值”营销策略,与开源运动的精神就相差甚远了。同时,想清楚这一点,在开源的名义下追求参与者数量或者各类行为活动的数字指标,就是完全的无稽之谈。

开源软件的源代码是可以免费获得的,因此销售源代码必然是不可行的商业模式。开源软件的实施是有附加值的,许多企业服务公司实际上正大规模地使用开源软件组装出自己的解决方案。开源吞噬软件的背景下,供应链上的开源软件的维护是必要成本,因此 RedHat 通过订阅模式能够建立起自己的收入来源,SQLite 的作者为支付酬劳的客户实现功能和提供问题相应支持。开源共同体的生态潜在的价值是无穷无尽的,CockroachLabs 通过提供云上托管数据库服务盈利,为了调优,它需要改进 Golang 运行时的性能,这些改进最好是合并到上游,才能保证在 Golang 后续演进的过程里 CockroachDB 的代码依赖的逻辑自然不被破坏,能够平滑升级。因为这样的原因参与到上游开发建设的参与者,不需要掌握上游的某种垄断权利来盈利。