👏欢迎关注我的公众号:令飞编程,Go、云原生、AI Infra、职场干货不错过!您的支持是我写作的最大动力!
本篇文章为 云原生AI实战营 中的一篇文章,现在免费分享出来,参加这次活动。本篇文章的背景是,分享阅读 Kuberentes源码的方法。
因为 Kubernetes v1.30.2 版本的源码有 5400770 行代码,23054 个文件,是个非常大的项目,在开始阅读源码之前,我们还需要学会如何去阅读 Kubernetes 源码。掌握一个好的阅读源码的方法,不仅可以降低阅读源码的难度,提高阅读效率,还可以使我们更好的掌握 Kuberentes 源码。
其实,从事编程行业,我们难免需要出于不同的目的去阅读源码,这些源码可能来自于工作中的项目,也可能来自于优秀的开源项目。可以说阅读、学习项目源码,是一个高频的场景,那么掌握一个有效的阅读源码方法就显得很有必要。因为这不仅能提高阅读源码的效率和效果,还能降低阅读源码的难度。
本节课,我就来分享下如何高效的阅读源码。下一节课,我会使用本节课介绍的方法,介绍如何高效的阅读 Kubernetes 源码。
为什么要阅读源码?
首先,我来介绍下为什么要阅读源码。
Linus 是 Linux 的早期作者,一句影响深远的话是“Read The Fucking Source Code“,源码这么 Fuck,但还要去 Read,这足以说明阅读源码的重要性和难度。
实际上,阅读源码不像围城(外面的人想进来,里面的人想出去),它是外面的人不想进来,里面的人不想出去。当我们跨进城内,你会发现(还是城外好,皮!)城内风光无限,源码的海洋任我们遨游!
那么我们为什么一定要阅读源码呢?阅读源码到底有哪些好处呢?在我看来,我们阅读源码无外乎出于以下几个目的:
工作需要: 工作中用到了某个项目,出于开发、修复 Bug 等解决实际问题的目的,我们需要知道项目的源码实现。从而可以直接基于项目开发新功能,或者魔改项目,开发工作中的需求;学习需要: 我们希望通过阅读优秀的源码,学习其中优秀的设计方法、实现方法等,以提升自己的技术水平,从而提高自己的职场竞争力。
上面 2 种目的,我们都可以总结为:想通过阅读源码,学习并掌握该项目及其技术,并为我们所用,要么用于工作、要么用于提升个人技术能力。
既然是想学习,那么为什么一定要阅读源码呢?这里我们先来看下,学习一门技术一般有哪些途径:
阅读书籍/课程: 最直接的方式是阅读相关的书籍或课程。例如 《企业级 Go 项目开发实战》书籍,或者课程,例如:《从零开发企业级Go应用》掘金小册。这类学习特点有以下几个特点:
介绍全面:这类书籍或课程,一般都有相对完善的知识体系,可以让我们能够系统的去学习这门技术,至少是核心技术全覆盖;质量高:这类书籍或课程,在出版时,需要满足出版社或平台对内容质量的要求,这些要求包括课程内容、课程质量、课程的编写格式等。有了出版社或平台的审核加持,课程质量会相对比较高。 阅读网上零散的技术文章: 我们还可以通过网上一些技术文章进行学习,这些技术文章散落在互联网上的各个位置,需要我们花费一定的时间去搜索。这类文章一般具有以下特点:
质量参差不齐:这类技术文章质量差异很大,其中不乏很优质的技术分享,但也不乏一些粗制滥造,凑数的技术文章。甚至还有很多产品 PR 文章;聚焦某个技术点:这类技术文章因为篇幅限制,通常会聚焦在某个技术点上。所以,你很难通过一篇技术文章,去全面的学习某一门技术。甚至很多时候,你网上搜了很多相关的技术文章,但这些文章加起来,大概率,还是不能将你想学习的技术进行全面的介绍。因为,如果你想系统的学习一门技术,那么一定需要作者能够通盘、成体系的去规划知识讲解的结构和内容。 参加技术分享会: 我们还可以参加一些技术分享会,例如:QCon 这类技术峰会,或者团队内、公司内组织的技术分享会。这些技术分享往往又对这门技术有比较好理解或者比较深使用的开发者来讲解,而且讲解的脉络也比较清晰,能够让我们对技术有一个比较好的整体理解。因为是技术分享会,所以我们还有机会向分享者提问,直接解决内心的各类疑问。因为这类技术分享会的时间、分享形式等限制,通过这类技术分享会,我们往往不能学习到某个技术的具体实现方式;阅读源码: 我们还可以通过学习源码去学习一门技术。因为一门技术,最终是需要落地的,落地的方式基本也都是通过源码的方式去落地。当然源码的展现形式多种多样,例如有 Go 源码文件、Shell 脚本文件、Java 源文件等。但毫无疑问,这些源码是技术的最终落地者,通过阅读源码,你不仅能够掌握技术,而且能够深入细节,知道这门技术具体是如何构建、如何使用的。
上面,我列举了 4 类学习技术的方法、途径。在我看来,学习效率最高、效果最好的方式是:阅读书籍/课程 + 阅读源码相结合。而且二者缺一不可。
通过阅读书籍/课程,能够让我们对一门技术有一个全面的认知,并且能够学习到比较核心的内容。因为这些书籍/课程,由作者编写,相当于作者首先要理解好这些技术,再将技术以一种易理解、逻辑清晰的方式介绍给你,这可以降低你的学习难度,提高你的学习效果。
书籍/课程毕竟是二手资料,最透彻的学习方式是直接阅读一手资料:源码。通过阅读源码,我们能直接 360 度无死角的掌握到技术具体是如何构建的,让我们心中对一门技术再无任何迷雾,因为心中迷雾没了,我们对这门技术掌握的就更加彻底,并且也更加自信。
另外,因为开源项目的源码一般质量都比较高,所以我们在阅读源码的时候,学习到的其实是优秀的设计思路、设计方法、架构方案、编程实现方式等。
阅读源码,还有另外一个好处就是,在阅读过程中,不断补充脑海中的代码仓库,这些代码仓库其实是我们现在及未来的财富。在未来如果我们需要构建相同或相似的功能时,可以直接使用这些源码,魔改甚至直接使用,从而提高我们的开发效率。
阅读源码有如此多的好处,所以我们可以及早养成阅读源码的习惯。在我看来,我们学完编程语言的基础语法、做过少量项目之后,边具备了阅读源码的能力,这时候可以找一些高质量、更工作、更预期技术栈相关的开源项目,阅读其源码。学编程的过程中在不断接收新的知识,进步很快。刚开始做项目时,进入实践领域,进步很快。然后就到了平台期,可能几年下来都是重复的增删改查,毫无进步。这段平台期实际是进行源码阅读的良好时机,能让你的技术持续进步。
我养成阅读源码的时间比较晚,这也算是我职业生涯中,有点小遗憾的地方。目前,我在开发项目的过程中、学习新技术的过程中,遇到一些不理解的地方,我基本很少去网络搜索看别人的理解了,而是直接阅读源码,通过源码感知一切。
如何阅读源码?
既然阅读源码如此重要,那么我们应该如何阅读源码呢?其实,有很多方法技巧,为了能够给你清晰的介绍这些方法技巧,我将这些方法技巧,按阅读阶段分类,并分别介绍给你。
整体来看,我们阅读源码分为 3 个阶段,阅读源码前、阅读源码中、阅读源码后。接下来,我们按阶段,来看下每个阶段都有哪些方法技巧。
阅读源码前:充足准备
阅读源码前,我们可以通过一些工具准备、知识准备等,来降低阅读源码的难度,提高阅读的效率和效果。具体包括以下核心内容:
带着目的/问题去阅读;知识预备;准备易用的源码阅读工具;了解源码目录结构和架构;部署源码;选择期望的阅读模块。
带着目的/问题去阅读
最有效的学习方式是带着目的去学习,目的可以是多样的,例如:找工作、开发需求、修复 Bug 或者学习开源项目的设计方法、某个功能的具体实现方式等。带着目的,可以让我们目标性更强,这个目标其实就是学习的自驱力,不至于让我们的学习变得迷茫。同样,阅读源码,也需要我们首先定好目的,有了目的之后,就可以选择性阅读,直到目的达成。
比如,OneX 项目是一个非常优秀、庞大的教学开源项目,里面有着非常多的功能实现案例。如果你只是想了解如何实现一个有限状态机或者异步任务处理引擎,那么你可以只阅读 OneX 的 onex-nightwatch 组件的源码,而不用去阅读 OneX 项目所有的源码,这大大降低了你阅读开源项目的难度。
如果你想阅读整个项目的源码,也未尝不可,但你得有个心里预期,就是耗时、难度都会更高,你可以匹配下自己阅读源码的目的,再去决定要阅读那部分源码。
知识预备
阅读源码,对于一部分人来说,其实是一个难度较高的学习方式,尤其是阅读复杂项目的源码。所以,在阅读源码前,我们可以学习一些关联书籍或课程,来让我们对这个项目有一个初步的了解,这可以从一定程度上减轻阅读源码的难度。例如:阅读项目的 README、Quick Start开始、官方文档、相关书籍和博文 等。通过这些知识的铺垫,我们在阅读源码时,更容易理解其中的实现思路、实现方法。
在进行知识预备的个过程中,我们可能会去阅读一些博客,有些博客里面充斥着大量的源码副本,并没有对这些源码进行深入的剖析、说明,知识粘贴了源码,并进行简单的介绍。遇到这类博客,我们也许可以选择直接阅读源码,在我看来直接阅读源码,甚至要比阅读这类博客,理解要深,阅读效率要高些。
开源项目可能会依赖其他框架,例如 OneX 项目依赖了 kratos 微服务框架,这时候,我们也可以提前去了解下所以来的框架、技术、Go 包等。
另外,一个项目是由某种,或某几种开发语言开发的,在阅读项目之前,我们还需要掌握相关的开发语言,这应该是最基本的预备技能。例如,如果你想学习 Kubernetes 项目,就需要预先掌握 Go 语言基础语法,并且最好通读 Kubernetes权威指南:从Docker到Kubernetes实践全接触(第6版)。
准备易用的源码阅读工具
阅读源码之前,我们最好有一个顺手、易用的源码阅读工具,这类工具不仅可以提高阅读源码的效率,还可以减轻阅读源码的难度。例如:Go IDE、Goland、VSCode 等。在阅读源码时最经常用到的 IDE 功能是:代码跳转、代码回跳、全局源码搜索、源码文件历史、函数/方法文档查看等。
这类源码阅读工具,对阅读源码的效率提升是非常大的,可以说这是个必备的前置准备。
了解源码目录结构和架构
在阅读源码之前,我们可以浏览整个源码目录,从整体上了解,源码目录的目录结构、构成、每个目录的功能等。另外,我们还可以通过项目文档,了解项目的整体架构,对项目的构建方式有个整体的认知。
这种低成本、快速的整体了解,有助于我们后面的源码阅读。
部署源码
在我们储备了相关的背景知识、技术、工具之后,接下来还需要部署好项目。这一步对于我们阅读源码的效率和效果提升也是巨大的。用“跑不起来的源码不读”来形容部署源码的重要性,一点也不为过。
只有将源码部署成功了,我们才能够打通源码的整个阅读、编译和使用流程。部署好项目有以下好处:
可以随时,根据需要魔改源码,通过观察程序运行行为/输出,等来理解代码实现;可以魔改、编译、部署源码,源码既然能够成功运行,也就具备了为我所用的条件;可以阅读项目使用文档,并请求部署好的源码服务,掌握项目是如何使用的,这可以极大的提高我们对项目的掌握程度,并在阅读源码的过程中,帮助我们理解源码。
选择期望的阅读模块
我们还需要根据我们的阅读目的,选择合适的源码模块进行阅读。一个开源项目往往有大量的源码,这些源码不一定都是我们需要的,也没有必要全部阅读(除非你明确知道你就是想阅读所有的源码)。我们可以选择预期的模块精细阅读,提高阅读源码的效率和效果。这些模块可以是某个服务组件、也可以是源码中的某个模块、甚至某个函数。
例如:如果你想学习 Kubernetes 调度器是如何实现的,那么只需要阅读 Kubernetes 源码仓库中的 kube-scheduler 组件的源码即可。
阅读源码中:高效阅读
在我们进行了充足的阅读前准备之后,接下来就可以在选择的 IDE 中,阅读源码。在我看来,有以下几种途径,可以提高源码的阅读效率和效果:
找到阅读入口;阅读 example 和单元测试用例;阅读代码注释;纵向为主,横向为辅;借助 ChatGPT 和网络搜索;写单测协助理解代码;写注解、做笔记、勤思考;调试阅读:魔改、编译、部署、测试。
找到阅读入口
开始阅读前,首先要找到源码的入口处。如果你想阅读整个服务组件,那么可以尝试从该组件的 main 函数处开始阅读。如果是想阅读某个模块,那么可以从模块的创建入口开始阅读。总之,我们可以根据情况选择合适的入口代码,进行阅读。如果你找不到 main 文件,可以使用以下命令来协助你查找:
$ grep -R 'package main' *
为什么要从入口处开始阅读呢?因为我们阅读源码,总要有一个阅读顺序,从入口处开始阅读,既是一个开始,又是一个现成的阅读顺序,引导者我们,从头开始了解整个逻辑实现。从入口处阅读,也能让我们了解源码所有的逻辑,不至于错过某些逻辑,导致后面的代码阅读起来不知所以然。
阅读 example 和单元测试用例
很多开源项目项目下都会有一个 example 目录,里面存放了一些使用示例。这些使用示例,通常短小精湛,可以帮助我们快速了解如何去使用或实现某个功能。在阅读源码时,我们也可以关注下这些 example,也许对理解源码会有帮助。
另外,如果是 Go 项目,优秀的开源项目,通常也会有很多 _test.go结尾的单元测试用例,如果有需要,我们也可以阅读这些单元测试用例,看下单元测试用例中,具体是如何调用某个函数,如何使用某个功能的。
阅读代码注释
优秀的开源项目,一般都有详细的注释,在阅读源码的过程中, 遇到一些比较难懂的代码段、函数、方法或变量等,我们可以多关注下这些标识符的注释,这有助于我们去理解这些标识符的作用。
下面是 Kubernetes 代码仓库中的一段代码,可以看到标识符上面有详尽的代码注释,这些代码注释可以有效的帮助我们去理解 DefaultMutableFeatureGate :
package feature
import (
"k8s.io/component-base/featuregate"
)
var (
// DefaultMutableFeatureGate is a mutable version of DefaultFeatureGate.
// Only top-level commands/options setup and the k8s.io/component-base/featuregate/testing package should make use of this.
// Tests that need to modify feature gates for the duration of their test should use:
// defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.
DefaultMutableFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureGate()
// DefaultFeatureGate is a shared global FeatureGate.
// Top-level commands/options setup that needs to modify this feature gate should use DefaultMutableFeatureGate.
DefaultFeatureGate featuregate.FeatureGate = DefaultMutableFeatureGate
)
纵向为主,横向为辅
项目的源码量有多有少,但同行代码量都不少。我们不可能一开始阅读,就能把所有的代码都透彻理解。如果一开始阅读源码的时候,就奢望面面俱到,把遇到的每个源码分支都去阅读一遍,没太多必要,也会加大阅读源码的难度。
比较高效的阅读方法是,纵向为主,横向为辅:
纵向:跟着源码的实现逻辑,一步一步往下阅读。这里的逻辑指的是主体逻辑。主体逻辑通常由很多横向的代码去实现。在需要的时候,我们可以去阅读这些横向代码,详细了解,某个功能的具体实现;横向:某个功能的具体实现,这些功能,可能由大量的带来去实现。
在阅读代码的过程中,我们可以围绕着代码的主体逻辑阅读,并在需要的时候,阅读分支代码,也即某个功能的具体实现。我的建议是,第一遍可以主要梳理主体逻辑,略读重要的分支代码。第二遍,可以选择一些不理解、感兴趣的横向代码进行阅读。
提示:哪些代码为主体逻辑代码,并没有标准答案,你可以根据字面意思,自行理解,选择。
另外,在阅读源码的过程中,要学会取舍。第一遍可以主要关注主题逻辑的实现,不要去深挖细节。在需要的时候,或者第二遍、第三遍阅读时,我们可以再去深挖细节。
借助 ChatGPT 和网络搜索
在阅读源码的过程中,你肯定会遇到一些疑问,这时候,可以借助 ChatGPT 或网络搜索,尝试解决问题。在我阅读源码的过程中,遇到自己不明白的,也经常会问 ChatGPT,如果感觉 ChatGPT 回答的不如意,也会直接 Google。
另外,ChatGPT 的训练具有一定的滞后性,如果是新版代码,ChatGPT 不一定能很好的回答,这时候可以直接通过网络搜索来解决。
写注解、做笔记、勤思考
人的初次记忆,往往时间不长,为了让你今后再回过头来阅读时,能够更加轻松,也可以在源码上添加阅读注解,写上自己的理解和源码剖析。写注解,也是一个思考的过程,会起到理顺思路、加深理解和记忆的效果。
当然,我们还可以记笔记,好记性,不如烂笔头。我们可以将一些难点、重点、源码逻辑、实现思路等任何你觉得需要记录的知识,以笔记的形式固化起来,后面可以反复阅读,加深理解和记忆。
在阅读的时候,我们也可以把一些想法、疑问记录下来。在阅读完代码后,重点攻破这些想法和疑问。
在阅读的过程中,我们可以多问几个为什么,例如:为什么源码会这么设计?如果是我我会怎么实现?某个功能是如何实现的?这个功能能用到工作中吗?解决这些为什么,会延长完成阅读源码的时间,但却能够让我们学习到更深、更多的知识,而且这种为了解决为什么,而去阅读和理解的学习方式,效果会更好。其实,我们学习源码,目的并不是快速的阅读完,而是能够学习到优秀的技术。所以,多问自己几个为什么,然后去解决这些为什么。
在阅读的过程中,我们还可以通过绘制架构图、UML、流程图等方式,来梳理整个代码逻辑,这种方式可能比做笔记更耗时,但是效果也是出奇的好。
为什么要把勤思考和写注解、做笔记放在一起呢?是因为,我的很多疑问是在我写注解、记录笔记时,理顺思路时产生的。也就是写注解和做笔记会“强迫”你去思考,问题会在思考中产生。
写单测协助理解代码
在阅读源码的过程中,如果我们对某个函数/方法或功能模块有疑问,这时候也可以编写单元测试,测试这部分函数/方法,查看运行效果,根据需要魔改函数/方法,运行,并观察效果。通过这种方式,也能帮助我们去理解代码。
当然,我们也可以直接编译运行整个源码,观察运行效果,这种方法,成本相较于编写单元测试用例,会更高点。
调试阅读:魔改、编译、部署、测试
我们可以通过对源码的魔改、编译、部署和测试,来观察程序的运行流程和效果,这种方式可以有效的帮助我们理解代码。在我阅读源码的过程中,经常使用这种方法,来阅读源码,帮助理解。这也是为什么,前面提到 “跑不起来的源码不读,是因为如果源码运行不起来,我们就缺少了一个有效的阅读源码助解途径。
源码阅读后:持续实践
阅读源码后,我们其实还可以通过一些方法,来加深源码学习效果,并让阅读源码过程中,学到的技术、理念等发挥真实的价值。这些方法有以下几种:
学以致用;重复阅读;知识分享;持之以恒。
学以致用
在阅读完源码后,我们其实还可以通过魔改代码(其实就是二次开发),复用整个源码。或者直接复用源码中的包/工具。通过魔改、复用,可以让我们在使用过程中,不断加深对源码的熟悉和掌握。
另外,通过复用项目源码,还可以提高我们的开发效率,这些优秀的项目源码,也在为我们构建优秀的工作项目添砖加瓦。
另外一个学以致用的思路就是仿写项目。如果工作中,我们找不到复用代码的场景,那不妨,我们自己假定一些需求场景,然后用学习到的源码来构建。或者最简单的方式,照着抄一遍源码。
在动手编写的过程中,你会对学习的源码有一个更深、更多的认知。
重复阅读
优秀的项目源码,通常比较庞大,而且有些源码也比较复杂,看一遍很难通透的理解和掌握。所以,我们还可以通过重复阅读,去不断地理解、掌握这些源码。
知识分享
在阅读完源码后,我们还可以通过输出一遍文章,来剖析源码。输出源码剖析文章,会给你带来很多收益,例如:
二次学习: 在编写源码剖析文章的过程中,其实我们是在二次学习。在整个过程中,我们需要梳理源码思路、了解源码实现细节、补全源码知识等。这个过程对于,我们对源码的理解和掌握,也起着至关重要的作用;知识固化: 可以将所学到的、所理解到的知识通过文章的形式固化起来,作为笔记,方便未来的自己重复学习,理解;知识产出: 一篇源码剖析文章, 其实某些时候也算是工作产出的一种,因为这种文章会给团队内其他同事带来价值,所以也是我们对团队的贡献;扩大影响力:可以通过输出一篇高质量的源码剖析文章,来扩大自己在团队内、公司内或者行业内的影响力。尽管一篇文章带来的影响力有限,但影响力再小也是影响力。
我们可以将源码剖析文章,发布在团队内、公司内或者社区一些知识分享平台上,例如:CSDN、掘金、知乎等。或者在团队内组织一次分享,将自己的学习结果、心得体验等逻辑化、体系化后分享给团队内、公司内或者行业的其他同行。
持之以恒
阅读源码不是一个一次性的学习方式,在我们的开发生涯中,我们可以不断的去阅读我们想学习的优质源码,在这个过程中,不断的去学习、提高自己的技术能力。
Go 语言优秀开源项目推荐
上面我介绍了如何阅读源码。这里,我再来介绍一下 Go 社区中,一些值得阅读的优秀开源项目:
Gin:轻量级的 Web 框架,非常受欢迎。源码量不大,但质量很高;OneX:高质量、易维护、高扩展的企业级 Go 项目+ 云原生项目开发脚手架。有配套的高质量网课(https://konglingfei.com),非常适合 Go 开发工程师学习、阅读,提升自己的开发能力;Kubernetes:开源的容器编排平台,用于自动部署、扩展和管理容器化应用程序。项目比较复杂,源码量很大,但能学到很多优秀的架构理念、设计方法和代码实现;Etcd:分布式键值存储系统,用于存储和管理分布式系统中的配置数据和元数据。想学习分布式技术的,建议阅读下。Etcd 在云原生领域用的非常广;Tidb:开源的分布式 NewSQL 数据库,具有水平扩展性和高可靠性,支持分布式事务和多数据中心部署。源码也非常值得阅读。
总结
本节课,详细介绍了如何阅读源码,根据阅读阶段,整理的阅读方法如下:
本文最后还分享了一些非常值得阅读的开源项目,例如:Gin、OneX、Kubernetes 等。