查看原文
其他

如何为复杂项目做贡献

Bytebase 2022-05-27

作者|Mitchell Hashimoto

链接|https://mitchellh.com/writing/contributing-to-complex-projects
作为一个活跃的开源项目维护者和贡献者,我经常会被问及:「通常你从哪里入手?」「如何以做出有价值的改动为目标,来上手新项目?」,以及 「怎样才能理解一个复杂项目的内部原理?」

这些问题适用于任何软件项目,无论它是开源还是闭源,玩票还是专业性质的。而我应对的方法也是相同的。不过,专业性项目的最大不同之处在于你可以直接和其他工程师交流,即使不是职责所在,他们也愿意帮助你。而面对开源项目,你基本上只能自力更生了。

我建立了一套可复用的模版,用以帮助大家理解复杂项目。我不奢求这套模版对所有人都有效,但我希望它能帮助人们有信心去尝试了解复杂项目,并做出自己的贡献。

本文中的「复杂项目」一词指实现有一定理解难度的软件项目。这个定义是主观的,同一个项目在一些人看来是复杂项目,在另一些人眼中可能并非如此;反之亦然。


第一步:成为用户


要理解一个项目的原理,首先要做的就是使用这个项目。你不必达到精通的地步,但依我个人的标准,至少要使用该项目捣鼓出一些正儿八经的东西才算达标,哪怕它小或者简单。举例来说,在为 Zig 编程语言 做贡献前,我先用它创建了一些真正的库。

作为一个用户,你可以全面了解整个项目的能力。阅读文档和上手操作是截然不同的两种体验。要跨越理论和实践之间的鸿沟,完成一些玩具项目是一个重要步骤。

另外,你会开始了解该项目的约定俗成。这些约定俗成是项目的文化基调,也能帮助你理解项目为什么是这样工作的,为什么有这些功能。这种做法之所以重要,是因为它能让你和其他为这个项目工作的人产生共鸣。这样做也可以帮助你理解哪些改变是对项目有意义的,而哪些不是。

我也强烈建议你加入社区。你可以加入 IRC 或 Discord,参加当地的 meetup,观看演讲等等。在这一阶段,多听少说。这样做的目的是让你形成共鸣,并掌握项目是如何工作的。单是通过观察他人获得的收益,也往往让我感到惊讶。


第二步:构建项目


学习构建项目的方法,然后产出一个可运行的二进制文件(或者对等产物)。不要去考虑开发系统和依赖等一系列问题,只需参照教程、网站和一切可靠资料,不停重复从源代码到可执行文件的步骤即可。

学习构建项目之前不要阅读代码。我看到不少人还没学习构建项目就开始阅读源码,结果卡在了那里。对我来说,不破不立,实验并拆解项目是上述学习过程的一部分。倘若不先学会构建,对项目进行实验和拆解就会相当困难。

不要纠结于功能完整的构建产物。复杂项目的部分功能有赖于正确的外部依赖、系统和配置。凡此种种,无需介意。我们的目标是先拿到一个可以在系统上大致能跑起来的东东。随着过程的推进,你的经验和信心会逐渐增长,然后去尝试构建功能更完整的产物。

在这一阶段,我也建议你学着跑通项目的测试用例。这将帮助你在后续更容易地实验和拆解项目。复杂项目通常有着复杂的测试用例,因此只要能满足实验的需求,你可以只使用测试用例的一部分。


第三步:学习热门路径的机制


为了理解项目的原理,我采用的方法是「trace down, learn up」。

Trace Down

我会从某个功能点或用例出发,从最外面开始构建该功能的代码路径。在这一过程中,我会记下途径的文件、代码和函数,但不会去尝试理解所有机制。这便是「trace down」的阶段。

举例来说,研究 Zig 编译器时,我首先跟踪用以从 Zig 源代码构建可执行程序的 zig build-exe命令。通过跟踪,我找到了zig CLI 的源码和子命令 build-exe,再到一个「编译」子系统;这个系统又调用了词法分析器 (lexer) 和语法分析器 (parser) 等。除了跟踪这一路径所必须的内容外,我不会去探究其他的实现细节。

从跟踪笔记中,你通常能对项目的运作机制产生一个总体概念。基于文件名、函数等内容,你通常可以开始拆分项目的主要子系统。之后你便可以将整个学习过程划分为规模适中的几部分。

不要试图掌握所有东西。人们常犯的错误就是试图一行行读完项目的全部代码,结果一连好几天都迷失在其中,最终变得灰心丧气。保持专注,按照功能点,逐个击破。

小诀窍:挑选功能时,选择你作为用户熟悉的功能。如果可以的话,尽可能挑选表面上看起来简单的功能。例如,开始学习 Zig 的编译器时,我选择跟踪的第一个程序就是一个简单的两数求和程序。


Learn up

跟踪完一个功能后,是时候学习各个子系统的工作方式了。在跟踪阶段,我从最外层开始,例如 CLI 和 API 调用。而在学习阶段则相反,我常常从最内层开始。

选择从最内层着手,是因为这部分内容最为基础,抽象层次最低。越往上层,抽象层次也越高。如果你不理解底层的组件,也就很难理解上层的抽象。

开始学习某个子系统时,我递归式地重复「trace down, learn up」。我首先检视项目的公共 API 接口,学习每个 API 的调用方式。这也是更上层的服务使用这一子系统的方式,所以这不仅帮助我当下的学习,也能帮助我在进阶到更上层时,能理解的更加清晰。

实验和拆解

在 「trace down, learn up」的过程中,我发现实验和拆解对学习事物的运作机制很有帮助。这也是为何在开始解读原理之前,关键的一步是先要学习构建项目。

添加新日志,运行一小部分新增功能,修改现有的功能等,然后再重新构建项目,看看会发生什么。这也是测试你对机制理解的好方法。

比方说,学习 Zig 分词器 (tokernizer) 时,我添加了新的词汇 (token),然后观察到分词完成,但语法解析失败了。接着学习下一个系统(语法解析器)时,我就拿新添加的词汇再干些事情。诸如此类。

辅助资料

在这一阶段,搜刮任何媒介资源,帮助你理清复杂代码。书籍、视频和博文等等都可以。如果有涉及到内部运行机制的文字资料,读就是了!

不过,你不能指望单靠这些资料就能融会贯通。和第一步所说的「成为用户」类似,若要「成为维护者」,没有什么能够替代亲自下场折腾源码。这是理论和实践之间差异的又一例证。

小诀窍:如果没有关于内部原理的学习资料,那就试着自己写一份吧!学习 Zig 时我就是这样做的,并写了关于 Zig 编译器原理 的系列文章,因为我找不到最新的相关资料了。写作是巩固学习成果的好方法。这也能在日后为其他贡献者提供帮助。


第四步:研读并复现最近的提交


学习项目原理的最后一步是研读有关我学习过的子系统的近期提交记录,以此来检测自己能否充分理解这些改动的原因。这算是我的「课后习题」。

我会查看整个项目的提交历史,或是我学习的子系统下某个文件或文件夹的提交历史。接下来,我会先研读解法(提交中的改动),又或者,我会查看它修复的 bug,并尝试自行修复,看看能否产出类似的解法。

为了「解决问题」,在代码仓库里,我会查看刚好处于当前正在研习的提交之前的提交内容。如果这次提交是 bug 修复,我会复现 bug,然后尝试自行寻找解决方案。最后,我会把自己的方案和之前的维护者或贡献者的提交做一个比较。

我给自己的唯一提示是改动规模,也就是 VCS diff 上显示的+/-行数。我建议开始时尽量不要选择代码行数超过 50 到 100 的改动。


第五步:做一个小小的改动


我喜欢从小任务做起,再逐步扩大。在这一阶段,你已经理解了项目的技术组件,那么,是时候去了解它的人为因素了。这里的目标是做一个小改动,来学习贡献和审核的流程。

通常最难的部分在于要找可做的小改动。在这个问题上没有什么银弹。我会浏览 issue,看有没有适合贡献的内容。有时我的尝试刚开始就失败了,有时我会放弃整个 issue,转而去试下一个。但最终,我总能找到一个合适的。在寻找或者去解决 issue 上花费的时间可能长到让人垂头丧气。但这就是融入的代价。项目通常会用「contributor friendly」标签来引导新来的贡献者。

现在大多数项目都会在文档中说明贡献流程。所以,一旦实现了改动,就要严格遵循这一流程。如果你之前已经加入了社区,现在就是很好的时机找别人,请对方帮忙确认你的流程。

举例来说,我对 Zig 的首次贡献是一个只有三行的改动。但这就花了我两个晚上合计四五个小时的时间(这还不包括在之前步骤上花的时间)。如果我现在遇到这个 bug,想必花几分钟就能修复,但要达到这个熟练度需要时间的沉淀。


成功


至此,你已经了解了一个复杂的项目并且成功地做出了贡献!

别被复杂度劝退。我觉得有太多的工程师把一些带有刻板印象的复杂项目,比如编程语言、浏览器和数据库之类,看得过于高大上。我想提醒的是,所有项目都是由人发起的。如果他们行,那我也行。同样你也可以。

希望通过我的分享,能让大家更容易地上手复杂项目。

令人头大的代码审核

什么是数据库 Schema Drift

MySQL 样例数据库 Employee 的制作过程

数据库代码化——黄金三镖客


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存