在 GitHub 上做贡献,就是赛博捐精

1 minute read

Published:

在 GitHub 上到处开 issue、提 pull request,就是赛博捐精。同一套机制,原样运行。

作者:Koutian Wu;GitHub: ktwu01

不对称在三个地方:复制成本、贡献数量、谁来筛选。跟性别无关,跟两种捐献谁更高尚也无关。开源社区花了二十年用”协作”“社区”“共建”这些词把它盖住,盖不住。

为什么这个类比成立

一个 issue 或 PR 携带作者的一小片信息:一个问题定义、一个设计偏好、一个编码模式、一套关于系统应该如何运作的想法。这份信息进入别人的项目,与别人的想法相遇。

于是有几条结构性上的相似:

  • 贡献者可以大规模贡献。 一个人可以给很多仓库开 issue 或提交补丁。
  • 复制的边际成本很低。 一旦一个想法被表述出来,它就能被改编、并在别处提出。
  • 过程是开放的。 贡献者通常不需要雇佣合同、机构归属,也不需要对维护者事先认识。
  • 接收方做选择。 维护者决定是拒绝、修改、合并还是忽略这份贡献。
  • 大多数贡献不会传播。 很多 issue 得不到任何行动。很多 PR 永远不会被合并。少数进入项目历史、影响后来的版本。
  • 贡献者与一个更大的基因库交换信息。 评审把贡献者的假设与仓库的架构、标准、积累的知识结合起来。
  • 署名存续,控制变弱。 Git 记录着作者身份,但维护者可能修改、重构、回退、或最终替换这份贡献。

这个过程里也有愉悦。你注意到一个问题、构想一个干预、把它送进世界,然后等着看它是否连上了。当它连上时,你的想法就成了一个比自己更大的系统的一部分。

为什么落点在捐精这一边

关键区别在经济和生物层面。

捐卵涉及有限的数量、医疗干预、恢复时间、筛查和可观的身体成本。精子则能以大得多、边际成本低得多的数量被生产与捐献。

大多数 GitHub 贡献属于第二种成本结构。想法和文字可以被复制。issue 可以很快提交。一个补丁有时可以跨仓库改编。平台允许高数量贡献,几乎没有正式障碍。

这并不表示贡献没有成本。理解一个代码库、复现一个 bug、设计一个兼容的修复、写测试、回应评审,可能耗费数天甚至数月。这个类比的适用范围是随手开 issue 那一端;越靠近深度工程,它越失效。

issue 和 PR 是两种东西

当我们不再把所有 GitHub 活动归为一类时,这个隐喻就更有用。

GitHub 动作实际贡献了什么
开一个 issue一个问题、观察或请求
写一份提案一个问题模型和一条可能的方向
提交一个 PR一份请求进入代码库的实现
评审代码选择、纠正和重组
合并被接纳进入项目的谱系
维护对贡献能否存续负责
分叉一条有着不同选择压力的新谱系
回退移除一个在其环境中失败的特征

一个 issue 接近于把一个想法释放进一个群体。一个 PR 携带得更多:它把想法打包成一个必须与既有生物体交互的产物。

但一个认真的 PR 仍然不能只是被描述为捐献。一旦作者研究仓库、谈判设计选择、修订补丁、添加测试、并在合并后仍保持在场,这种关系就变成了合作开发。

类比在哪里失灵

软件终究是软件。

第一,代码允许精确复制。生物遗传不允许一个捐献者以几乎为零的成本把同一份完整贡献送进成千上万个环境。

第二,GitHub 贡献保持着可编辑性。作者可以在评审后修订补丁、把它拆成更小的改动、撤回它、或者几个月后带着更好的设计回来。

第三,项目可以逆转合并。一次合并可以回退。一个仓库可以被分叉。一个实现可以在提交仍留在历史里时被替换。

第四,维护者做的远不只是选择。他们解释本地约束、引导工作方向、补上缺失的上下文、有时合著最终方案。在贡献改变项目之前,接收项目先改变那份贡献。

最后,衡量成功的标准是存活。一个没人用、产生了维护成本、或者在下个版本里被移除的合并 PR,几乎一无所成。真正的检验只有一条:这份贡献是否还在解决一个问题。

让人不舒服的教训:充裕让小择事变更重要

当贡献变得便宜时,注意力就变得稀缺。

开源平台让大规模分发想法成为可能。同样的机制也带来了模糊的 feature 请求、重复的 issue、路过打一枪的补丁、AI 生成的 PR,以及把验证成本转移给维护者的工作。

这改变了核心问题。它不再是:

我贡献过多少个仓库?

而是:

我的贡献为那些对项目负责的人,消除了多少不确定性?

一个有用的 issue 证明问题存在、界定其范围、并与已知案例区分开来。

高数量产生曝光。它本身并不产生影响。

这句话我写这篇文章时就相信。后来我自己开始维护项目,才知道它的实际代价是什么。

补记:当我自己成了那颗卵子

写这篇文章的时候,我站在贡献者这一边:想的是怎么把自己的想法送出去,送到更多项目里。

后来我自己做了几个开源项目,视角就翻了过来。现在收到 PR 的人是我。

这是一种很奇特的体验。以前我琢磨的是怎么让别人接受我的东西,现在我琢磨的是,什么样的东西值得让它进来。

到处提 PR 是赛博捐精,那维护者就是卵子。准确说,是卵子外面那层透明带。它的工作就一件:把绝大多数来访者挡在外面。

这个过程一点也不温柔。一次射精是上亿个精子的量级,能抵达卵子附近的是几百个的量级,最终完成受精的一般只有一个。而且卵子全程主动:精子和卵细胞膜一融合,卵内的钙波就触发皮质颗粒把内容物倾倒出去。门是分两道关的:几分钟之内,卵细胞膜先不再接受新的融合;接下来的半小时到几小时里,倒出来的酶把透明带一点点改性,让它彻底不能再被结合、被穿过。这套机制存在的唯一目的,是保证只有一个能进来。多进一个,胚胎就废了。

维护者干的就是这个活儿。只是开源社区不爱这么讲,讲出来太不”欢迎新人”了。

大部分 PR 应该被拒绝,这是维护者的本职

近两年多了一类新的来访者:AI 生成的 PR。

有人让模型扫一遍仓库,找出几个”改进点”,自动提交。标题规整,描述完整,diff 看起来也干净。但点开看,往往是:把一个没人调用的函数改了名;给一段本来就清楚的代码加上一堆注释;把 README 的英文润色成另一种同样正确但没更好的英文;或者修一个不存在的 bug。

我的先验很明确:默认这类 PR 质量堪忧,举证责任归它自己。

有人会说这是偏见,是对 AI 辅助贡献者的歧视。这个先验针对的是一件具体的事:你没验证。”你用了 AI”这件事我根本不关心。一个人用 AI 写出代码、自己跑过测试、能解释为什么这么改、出了问题愿意回来修,这个 PR 我照收不误,而且往往比手写的还好,AI 确实在降低好贡献的成本。我挡的是另一种行为:把生成成本降到零,同时把验证成本全甩给我。

而这个行为,在 AI 出现之后变得格外便宜。当提交一个 PR 的成本降到接近零,提交量就会涨到接近无穷,评审成本一分没降。维护者的注意力是这个系统里唯一没变便宜的东西。它是稀缺资源,就得按稀缺资源分配。

所以我的默认姿态是:举证责任在你,不在我。

一个 PR 想进来,至少要让我信三件事:

  • 问题是真的。 有复现步骤,或者有 issue,或者你能说清楚谁会踩到这个坑。不接受”我觉得这样更好”。
  • 解法和这个项目是一路人。 不另起炉灶,不为省三行代码引入一个新依赖,不把我的架构改成你习惯的那种。
  • 它不把维护成本转嫁给我。 有测试,有文档,边界情况你想过。合进来之后出问题,我不用从头读一遍才知道它在干什么。

缺一条,我让你补。三条都缺,我直接关掉,没有歉意。

讲个笑话:我两边都当过

我有一个补丁进了 git/git。就是那个 Git,全世界都在用的那个,Linus 写的那个。合并的人是 Junio C Hamano,commit 上挂的作者名字是我,每一次 git clone 都会把它带下来。那是一个一行的修复:.gitattributes 里 *.pl 那行写成了 eof=lf,应该是 eol=lf。

同一个我,在 kimi-cli 连着开了好几个 PR,被维护者一个接一个驳回。

这两件事的区别,恰好就是这整篇文章想说的区别。

进 git/git 的那个补丁,来路很具体。我先在自己的博客仓库里踩了一个真实的 CRLF/LF 坑,为了解决它亲手写了 .gitattributes,写的过程中才把 eol 和 eof 的区别记牢,第二天看 Git 自己的文件时才认出同一个错字。问题是真的,我踩过;解法只有一行,不动任何架构;维护成本是零。三条全中。而且我还先去搞懂了 Git 根本不走 GitHub PR 那一套,走的是邮件列表,得用 GitGitGadget,得签 Signed-off-by。

那批被驳回的 PR 呢?我现在看得出它们是什么:成本很低,卖相很干净,坑是别人的。我当时在做的事情只有一件:批量往外送。

所以那位维护者是对的。他在做他该做的事:当那层透明带。

而我现在坐在另一边,对着别人的 PR 做同样的判断。这件事的好处是,你很难再对拒绝这件事产生怨气。你知道对面在干什么,因为你干过一样的事。

拒绝别人,是一种需要学习的能力

我以前不太会拒绝。

收到一个 PR,哪怕它不好,我也会先想:人家花了时间,我这样是不是太苛刻?要不先合进来,以后再改?

这个想法是错的,而且错得很具体:合进来的代码,维护成本归我。贡献者提交完就走了,留下的东西我要养十年。一个”先合了以后再说”的决定,是把一次性的拒绝成本,换成了长期的维护成本。

成本不对称就在这里:拒绝一个 PR,代价是一次不愉快的对话,一次性的;合并一个坏 PR,代价是一条永久的技术债,外加一个先例:既然这种能进,那种也能进。前者你付一次,后者你付一辈子。

而且”怕伤人”这个理由本身是假的。真正伤人的是无视。一句”这个方向我们不做,原因是 X”,比挂在那里三个月不回复要尊重人得多。拒绝是把人当回事才做的动作:我读了你的东西,我认真判断了,我告诉你结论。挂着不回,才是真的不在乎你。

所以我现在拒绝得很快,也说得很清楚。

从”怎么把我的东西传出去”到”什么东西值得让它进来”,换的不只是角色,是能力。分发靠产量和脸皮,筛选靠判断力和说不的能力。前者可以用热情撑住,后者只能靠标准撑住。热情是会耗尽的,标准不会。

从传播基因到养育后代

这个类比在开源最零承诺的那一端最成立:开很多 issue、提出很多想法、把小块补丁送进陌生的项目。

但责任一增加,它就开始失效。

提交一个想法是传播。让它通过评审是选择。让它被合并是继承。在未来的版本里持续维护它,则是另一回事:养育。

这正是”在 GitHub 上只留下一串痕迹”与”和别人一起建造东西”之间的分界线。最出色的开源贡献者不只是广泛地分发自己的想法。他们待得足够久,帮助那些想法在与现实的接触中活下来。

相关文章: