如何阅读一个开源项目的源码?

Posted by AlstonWilliams on February 17, 2019

大约半个月之前,我稍微了解了一些分布式系统的理论知识.但是它们很枯燥,而且有的内容,在我看来都是过时的东西.于是,我想到了,找一个分布式系统的项目的源代码,来了解分布式系统的实现.首先我想到了Kubernetes,但是我担心因为对其了解不多,而不能成功地理解它.所以我选择了先研究Docker.

我们都知道,Docker是用Golang语言写的.而我在阅读Docker的源码之前,对Golang一无所知.

其实我很久之前,就想找一个开源项目的源码来阅读.为此我上网搜了大量的关于如何阅读一个开源项目的源码的文章以及提问和答案.但是,它们都前篇一律.并没有提供什么实质性的帮助.

所以,这次,我稍微了解了一下Golang语言(只是花了大约一上午的时间,照着官网上的Tutorial走了一遍),然后,就撸起袖子干了.

从阅读Docker的源代码到现在,也有一些阅读开源项目的源代码的感悟,这里记录下来与大家分享一下.当然,这个项目的源代码我并没有读完,读了大约一个周了.所以,以后如果有什么感悟,我还是会更新这篇文章的.

选择一个正确的开源项目

我们阅读一个开源项目的源代码,不外乎有几种原因.一种是感兴趣,想了解其实现原理.或者对于框架类来说,我们用到了它,在使用过程中遇到了坑,Google解决不了我们的问题,就只能自己看源代码找问题.或者是跟我一样,想看看工程中是如何实现一个系统的.

比如,如果你想知道如何实现一个服务器.开源的有Apache, Nginx, Lighttpd.那如何来选择呢?

我的选择标准是:

  • 结构清晰
  • 使用我熟悉的语言
  • 功能比较精简

其中最重要的是第一条和第三条,结构清晰的代码能让我们对其架构比较清楚,而功能精简的代码,能够让我们专注在我们的关注点上.

语言这种东西倒无所谓,即使使用不熟悉的语言,无非就是花点时间熟悉一下语法而已.

在阅读源代码之前,如果能从网上找一个相应项目的架构图,也是会事半功倍的.

了解对应的开源项目的使用

这一点也很重要.如果你都不清楚该开源项目的使用方法,那根本就不能清楚的找到切入点,也不能有所侧重的阅读源码.

比如说,你要阅读Docker的源码,而你连Docker如何使用都不知道.那明显说不过去吧.

选择正确的版本

我开始只是从github上面pull了Docker的master的版本.编译出来是17.04版本的.而我本机上使用的是1.12.6版本的.我在读到containerd的部分的时候,想调试一下,然而,在containerd进行健康检查时,老是遇到libcontainerd: containerd health check returned error: rpc error: code = 14 desc = grpc: the connection is unavailable这个问题.读到这部分的源码,没有发现什么问题.猜测是libcontainerd的版本的问题.于是就checkout了1.12.x的源代码,重新编译,没有任何问题.

而且,如果你选择了一个和你使用的版本不一致的源码来研究,如果它增加了什么特色,你阅读源码时还可能不知所云.

选择正确的IDE

对于项目结构不复杂的项目,使用Vim来研究代码还是可能的.但是Docker这种项目,你用一个试试?保证你痛不欲生.所以我选择的是IntelliJ IDEA + Golang插件.等Jetbrains家族的Golang IDE出来,就切换到新出的这个.

对于Vim这种没有跳转到变量定义,方法定义的编辑器,查看起来是及其麻烦的.

记得写注释

注释很重要.特别是开源项目的注释一般都较少.如果你不写注释,很快你就会忘记你看过的源码的含义了.另外,写注释时,最好带上步骤.比如Docker的docker daemon启动的main函数中,我会写如下注释:

// Reading: 1 - Initialize the configuration of daemon
// Reading: 2 - Config the api server
// Reading: 3 - Config the log
// Reading: 4 - Check daemon root

其中// Reading:部分表示这个注释是我在阅读的时候为了便于理解而加上的.1 - **部分表示这是daemon启动过程的第几步.Initialize the configuration of daemon**部分表示这部分代码的作用.

通过记录下步骤,你会对其架构以及其流程越来越了解.

你也可以通过画流程图的方式,记录一下流程.

查询相关资料

一般来说,这种开源项目的实现,都会涉及到内核中某些我们不熟悉的功能.比如,NIO中用到了epoll来实现,Docker是用namespacecgroup来实现的.这部分就要花大量精力去查询相关的理论知识的.

还有很重要的一点,就是清楚项目的总体架构。这样我们在读源码时,才能从宏观上看到自己读的过程,不至于迷失。

这一部分,可以阅读相关的论文。比如,如果你要查看Hadoop的源码,那么《MapReduce: Simplify data processing in cluster》就是必须要阅读的,你要想研究HBase的源码,那么《Bigtable: A distributed storage system for structured data》这篇论文,也是必不可少的。

如果你不习惯阅读论文,那么就去寻找相应的书籍。那种比较权威的。其中一般都会有关于这个项目的大体架构。

比如,如果你想知道Hadoop中一个作业是如何提交的,那么在《Hadoop: The Definitive Guide》这本书中就能找到答案。

深度挖掘项目

一些人在阅读开源项目时,会只是关注功能的实现。

我个人感觉这样比较不妥。

我们还应该从编码规范上,从设计模式上,从模块化的角度来审视这个项目。这样,我们能够得到更多的内容。

像Hadoop这种项目,可以从设计模式上学到不少,它内部应用的状态机模型,会对我们有很大的启发。

此外,如果我们能够对这个项目进行充分地性能测试,寻找出来它针对性能优化的点,那么,这个项目的理解应该就算透彻了。

自己动手造一个

在你阅读完这个项目后,你再怎么理解这个项目,这个项目终究还是别人写的,而你的编码水平其实并没有提高。

所以,我们也可以尝试着自己实现一个精简版的,并对它进行性能测试。跟开源项目作比较,逐步调优,这样我们的能力才会不断提高。