这周给 Databend 提交了一份关于 Databend 配置兼容性的 RFC,今天就结合实例展开聊聊如何撰写并提交一份 RFC。
关于 RFC
很多人都一再强调过 RFC 的重要性,@tison 在 如何参与 Apache 项目社区 中提到:
对于任何 non-trivial 的改动,都需要有一定的描述来表明动机;对于大的改动,更需要设计文档来留存记忆。人的记忆不是永久的,总会忘记最初的时候自己为什么做某一件事情,设计文档的沉淀对于社区摆脱人的不确定性演化有至关重要的作用。
我在 如何在开源项目中做重构? 也详细阐述了我对 RFC 的理解:
一个好的开源项目不仅仅是由代码组成,抛开开源共同体谈抽象的技术和代码是没有意义的。因此向开源项目提交大型的变更之前,我们必须要阐述清楚自己的想法,解释动机,让开源共同体知道自己想做什么,想怎么做。
这些落到纸面上的文档在讨论时能够补充信息,完善想法,构建出更好的设计。从长期角度看,文档能够帮助后来者理解当时为什么要提出这样的设计,从而避免重复踩坑。不仅如此,一份好的设计文档往往还能够影响、启发其他开源项目的设计,从而促进整个行业进步。
不难发现,运作良好的开源项目往往有着完善的 RFC 流程,他们之间的关系是相辅相成的:
那么问题来了:
如何撰写一份 RFC?
在我看来撰写 RFC 是一件非常自然的事情,不知道如何写的根本原因往往是准备还不够。提出一个新的想法总是简单,但是将想法落地成为可行的方案需要付出艰苦的努力,RFC 就是这种努力的具象化。
我在撰写 RFC 的时候通常会经历如下步骤:
- 收集背景资料
- 分析可行方案
- 撰写 RFC
- 社区讨论
收集背景资料
最辛苦也是最容易被遗漏的一步就是收集背景资料。
在有了一个好想法之后,我们需要查阅历史的 RFC 和相关 Issues/PR 来了解这个这个想法是否可行。在这一步,我们需要搜集足够的资料以回答下列问题:
- 在此之前,相关的模块是如何工作?现在遇到了怎样的问题?是否确有必要做改动?
- 在相关领域有没有类似的工作,现況如何?在其他项目中有没有类似的参考经验?
- 过去有没有考虑过这个想法?当初为什么否决了它?现在情况发生了怎样的变化?
收集背景资料能够让我们掌握更多上下文,对相关模块了解更深入,避免提出想当然的改进。其次,收集背景资料能够避免我们进行重复的工作。毕竟太阳底下没有新鲜事,新想法可能早就已经被前人尝试并论证过不可行。
在提出 RFC: Config Backward Compatibility 之前,我做了如下工作:
- 与提出相关问题的用户进行了一对一的沟通,了解了他的诉求和需要
- 阅读 Databend 与 Config 处理相关的核心逻辑,了解现在如何处理
- 了解其他类似项目如何实现 Config 兼容逻辑
分析可行方案
在充分掌握背景资料后,下一步需要分析可行的方案。
技术问题往往存在着多种可能性,不要尝试找到唯一的正确答案,而要在多种方案中做分析和调研并挑选出相对更好的方案。有时会需要开发出简单的 Demo 以验证自己的想法。
我在这一步中经常会犯下列的错误:
- 偷懒:明知道存在其他的可能性却不主动调研。这往往会导致进入讨论阶段时被社区指出并且非常被动地做追加解释,往返消耗的时间远大于自己事先想考虑清楚。
- 路径依赖:设计方案时只考虑应付当下的问题,没有跳出来看到更好的可能。
- 自我辩护:诞生想法的同时往往也会附赠一个大概的思路,在调研的时候就很容易带有明显的偏向性,即使方案出现了比较严重的问题也不愿意放弃,而是不断增加扭曲的设定。
- 提前实现:在调研方案的时候就已经写出了完整的实现,最后方案的分析变成了具体实现的讨论和复制。
这些问题往往会导致后续撰写 RFC 时出现明显的偏向性,得出的结论有失偏颇。轻则 RFC 做大幅度调整,重则与维护者出现严重的分歧,甚至言语冲突。尽量避免类似的问题,努力做到公正客观的评价不同实现方案。当然,每个人都有自己的技术偏好,所以也不用过于苛求公平,只要能够把优势与弊端分析清楚即可。
撰写 RFC
在完成准备工作之后才真正进入到撰写 RFC 的阶段。不同的项目往往有着不同的格式与要求,需要因地制宜地做改变。大部分 RFC 都会包括如下内容:
- 背景/动机
- 详细说明
- 基本原理
- 未解决的问题
- 未来可能性
背景/动机
这个章节需要说明本次变更的背景和动机,解释为什么要做这个变更,根据不同的情况,需要具体说明也会略有不同。
比如新功能需要说明为什么现有功能无法满足需求,它将会支持怎样的使用场景,预期的产出是什么;功能重构修改则需要说明现存的实现存在着哪些问题,之前做过什么尝试等。
详细说明
这个章节则需要具体阐述项目需要怎么做变更。同样需要根据项目实际情况做调整,以 Rust 为例,它将该章节分为两部分:
- Guide-level explanation 解释从用户的角度看,这个变更带来了怎样的变化,引入了什么新概念,增加了什么新语法
- Reference-level explanation 则从技术的角度说明如何实现,与其他的特性如何交互,需要考虑哪些边缘 case
注意在这个章节中不要大面积地粘贴具体的实现,而是简明概要地说明思路并辅以必要的代码,这将有助与 Reviewer 理解宏观思路而不是陷入到实现细节中。
基本原理
基本原理章节中需要讲解为什么要这样来实现。
- 有没有其他的实现方案?他们各有什么优缺点,什么原因驱使我们采用了 A 方案而不是 B 方案?
- 现存技术如何?其他项目是怎么实现的,他们各自考虑的出发点如何?
未解决的问题
这个章节需要说明本变更的局限性,比如在 review 中提出了某些目前还无法解决的问题,可以将他们记录在该章节中。
未来可能性
未来可能性章节用于记录本变更未来的可能性,已经考虑到但是没有在本次变更中实现的想法。
比如引入了一个新特性,这个特性在未来能够与其他的特性做怎么样的结合。或者在 review 时有人提出超出当前 RFC 的范围的想法,可以先记录在这里供后人参考。
社区讨论
在完成 RFC 后即可提交到社区进行讨论。
在经历这么多工作之后,我们自然不希望自己的 RFC 被否定,所以我们需要积极地参与社区讨论,回应社区成员的关切,根据大家的反馈来调整自己的实现方案:补充没考虑到的地方,调整实现的思路,必要的时候对 RFC 做拆分,实现其中的一部分等等。身处同一个开源共同体,社区成员的利益是趋向一致的,大家的终极目标都是为了把项目做好。将他人的反对意见视作对自己想法的攻击,消极地与社区成员做对抗往往不利于 RFC 的进一步推进。
我为 Databend 提交的 RFC: Config Backward Compatibility 曾经是一个更大的 Scope:Versioned Config。在原始的 RFC 中,我计划为 databend config 引入 version 的概念,从而能够自信的做出破坏性变更。但是社区成员对该设计提出了复杂性的质疑,他们普遍认为这个设计太复杂了,会引入非常多冗余的代码。不仅如此,Databend 目前还没有发布稳定版本,客观来看没有引入版本化配置的必要。结合社区的反馈意见,我做出了自己的回应:
Our community members have great concern about the complexity of introducing a versioned config at this stage(no stable release, no production users). It seems better to only split
outer
andinner
configs and leave the decision of versioned config till the future.
调整本次 RFC,将版本化配置的想法移动到了 Future possibilities
,本次变更只是引入一次重构,将暴露给用户的配置和内部使用的配置做拆分。在做出本次调整后,社区给出了 LGTM
并合并了这个 RFC,进入到具体的实现阶段。
总结
撰写 RFC 是跟社区沟通交流想法的有效渠道。通过完成收集背景资料、分析可行方案、撰写 RFC和社区讨论等步骤,结合项目的实际,我们谁都可以写出一份翔实可靠的提案。
给自己最爱的开源项目提交一份 Proposal 吧!