Go语言项目的代码阅读技巧

转载自:原文链接

作者:李佶澳

说明

之前在阅读代码方面存在认知误区,一开始就从入口开始阅读,并不停地跳转,试图理清每一个细节。这种方式会让阅读代码的过程比较煎熬,耗费时间长效果却不好,不是正确的做法。正确的做法是先看森林再观树木,即先对整个项目的代码结构有基本认知,摸清项目代码的实现套路,然后按需逐行阅读。

一:摆正认识

摆正认识,对阅读代码这件事有正确的认识很重要,核心思想指导行动,方法构建在核心思想之上,如果核心思想不正确,方法就不可靠。核心思想用比较现代的语言表述就是:元认知。

我们要阅读的代码不是一堆杂乱文件和字符,而是经过深思熟虑、精心安排好的,是有内在规律的,越是庞大成熟、被广泛使用的项目越是如此,这一认识是后续方法的基本盘:

  1. 要相信有几十年发展历史的软件工程是有料的,我们现在能看到重要项目不会是杂乱无序的;
  2. 要相信前辈大牛们创造的各种编程技术是在降低编码阅读难度的;
  3. 要相信重要开源项目的核心开发人员的水平是很高的,他们在方法论方面的认识和水准超过绝大多数人;
  4. 要相信技术水平高的人是喜欢制定并遵循套路的,这些套路会让工作更简单,阅读开发代码的过程更轻松。

在初次阅读代码的时候,首要目标就是找到「套路」,并时刻保持清醒,避免过早陷入细节而忘记了首要目标。

二:找到入口

万事开头难,刚刚开始接触一个全新的陌生项目时,是最难受的,因为一无所知。这时候如果自己能够把代码编译成可执行的文件,并知道了程序的入口位置,内心的焦躁就会大幅减少,因为剩下的就是时间问题了。

除非是构建过程特别复杂的项目,否则一定要自己编译一遍,并阅读 makefile 或者编译脚本,大概了解作者维护这套代码的方式。大部分开源项目都会在 README.md、Install 等文档中详细介绍编译、部署的方法,通常是2~3个命令,无脑执行即可,但有时候会遇到比较烦恼的挫折。

第一个常见挫折是因为国内特殊的网络环境,一些需要从网上获取的资源被卡住,导致构建不通过,这个挫折除了找一个全局代理的「维劈N」似乎没有其它的破解方法了。

第二个常见挫折是有些项目的构建过程本身就很复杂,并且大量运用 makefile 和 shell 的特性,为寻找程序入口增加了重重障碍。这个挫折除了硬着头皮去看或者直接找人问,似乎也没有其它破解方法了。

另外现在比较流行用 docker 做环境编译,这种方式使构建环境统一带来不少益处,但对于要掌握构建过程的人来说,在 makefile 和 shell 之外又需要掌握一套工具,对于完全不了解 docker,特别是对一些经验尚浅的在校生来说,是个不小的障碍。

有些项目还用到了其它比较小众的构建工具…没有什么好办法,多看多学,见多了就熟了!

三:梳理目录

找到程序入口之后,不用着急从入口阅读代码,先把所有的目录看一下,大概了解目录的层次结构和用途,这样会事半功倍。如果没有搞清楚目录结构,就从入口开始阅读代码,每次跳转都进入一个完全陌生的目录或文件中,连续跳转几次就完全陷入到具体目录中,找不回主线了。

在梳理目录的时候,如果目录下有说明文件一定要仔细阅读,然后快速扫视目录中每个文件的内容。文件开头的注释要认真看,文件中比较复杂的结构体、类、函数或方法列表等要关注,通过快速扫视对每个目录的用途有一个大概的判断。

这个阶段酌情了解一下函数或者方法的实现即可,一般情况下只要看一下函数或方法的名字,就能大概猜到这个目录中代码的用途。

即使完全判断不出某个目录的用途也不要紧,提前扫视一遍至少眼熟,在后面逐行阅读代码时,没准就串联起来并瞬间想通了。随着了解的目录越来越多,对整个项目的代码结构的认识越来越清晰,最终完成了从0到1的认知构建。

认知构建的过程是比较不愉快的,因为要在完全陌生的内容里折腾,贵在坚持,「眼熟」了,就顺畅了,最后如鱼得水、酣畅淋漓。有一个技巧可以应用,扫视的时候在心里不停地问,这段代码是在干嘛?逼迫自己从更高的维度去看和想,而不是只见树木不见森林的陷入到细节中。

四:逐行阅读

对项目的目录结构有了基本认知之后,接下来就是最后一步:从程序入口开始逐行阅读。

这期间经常遇到没见过的函数或者代码库,还有一些设计模式相关的代码。老外写代码特别喜欢用 factory、helper 等等,各种传递各种类型转换,还有各种自动生成的代码,往往要向下穿透好几层才能找到熟悉的直白执行的代码。

第一个应对方法很简单:见得多了,就熟悉了;第二个应对方法是:掌握好阅读的深度。

如果不是特殊需要,遇到一个陌生的库只需要知道它是什么、能用来做什么怎么用,至于是怎么实现的可以先不关心。要把时间和精力用在主线上,别一头扎入某个库的细节,忘了当前项目的主线,变成研究另一个项目了。

特别是一些使用了框架的项目,先知道框架怎么用,把当前项目搞清楚后,再去研究框架自身是怎么实现的。

掌握好阅读深度非常关键,因为代码浩如烟海,是读不完的,只依赖标准库的项目还好一些,否则 A 引 B、B 引 C,最终代码量成指数型增加。要善于抽象总结,逐行读代码,不是逐行看字母abcd,而是揣摩这些代码表达的意思,透过代码把握作者的思路想法。

有时候代码实在太复杂了,那就有选择的阅读,譬如看到一整屏的初始化代码,如果去追究每个初始化是在干嘛的,等折腾明白了,黄花菜都凉了。我们先知道这里是在做初始化就好了,然后继续往下,等遇到困难或者有需要的时候,再回过头来研究相关的初始化代码。

Go项目的一些阅读技巧

平常阅读 Go 开发的项目代码多一些,因此只总结了几个还算实用的 Go 代码阅读技巧。

技巧1:忽视真正入口之前的代码

最开始找到的 main 函数常常不是我们想要的入口函数,例如正在阅读 gloo 的代码,语法意义上的 main 函数是:

源代码阅读技巧,gloo的入口main函数

但真正要阅读的程序执行入口是 SetupFunc 中的 RunGloo,直接从 RunGloo 开始阅读,前面的可以暂时不关心:

源代码阅读技巧,gloo的入口main函数

直接从 RunGloo 开始阅读,前面的可以暂时不关心。

Kubelet 的代码更过分了,app.NewKubeletCommand()->Run()->run()->RunKubelet()->startKubelet()->k.Run(),整整经过六次跳转,穿越了上千行初始化代码后,才知道真正的入口是 pkg/kubelet/kubelet.go 中的 Run()。

kubelet的入口

技巧2:接口的实现一般就在接口定义的下方

逐行阅读 Go 代码的时候,经常遇到一个尴尬,要看一个函数的具体实现的时候,跳转过去,发现是接口中的方法,或者要看个变量的类型的定义,跳转过去发现是个接口:

源代码阅读技巧,go语言的接口

这时候通常只能不断地回退,直到找到创建变量的代码,才知道这个变量的实际类型,进而找到接口方法的实现。 当前的变量常常经过了多次传递、赋值,等找到最开始创建的地方,也差不多晕了,下次看代码再次到这个位置的时候,一般早就忘了这个变量是咋回事了,又要重新找。

后来发现一个近乎「约定俗成」的规律,接口定义和接口的实现通常在同一个文件中,一般至少有一个默认实现,最常用的也是这个默认实现。因此找到了接口定义,也就找到了接口的实现:

源代码阅读技巧,go语言的接口和实现

这个很常见的做法不是语法上的约束,只是一种自律。不是所有的接口都只有一个实现,但为了不给自己添麻烦,绝大多数人一定是把接口的实现和定义放在邻近位置,譬如同一个目录下。

下图是 plugin 的接口定义和诸多实现(每个实现是一个子目录):

源代码阅读技巧,go语言的接口和多个实现

掌握了这个技巧,代码读起来就畅快多了,而且会充分享受到接口的好处。接口比具体实现高一个维度,通过查看一两个实现代码,明白了接口的用途,以后再看到接口方法,反而不执着于寻找接口的实现了。

技巧3:见到了类似于「类」的结构,要感动地想哭

接口、类、类似于类的结构都是我们好朋友,因为类的代码是高度内聚的,在当前文件中就能搞清楚这个类是干嘛的,能做些什么。还是以 kubelet 的代码为例,当发现真正的入口是 struct Kubelet 的 Run() 时,看到的是下面的景象:

kubelet的入口

上面红色箭头所指的函数都在 Kubelet 中,以后要查看 kubelet 某块功能的具体实现时,直奔 Kubelet 找对应的方法,直奔主题!只需要要记住有这么一个「类」,就再也不用从 main 开始一步又一步的寻找了!

技巧4:发现套路——类似于框架

以 kubernetes 的 kube-apiserver 代码为例,要找到它处理 http 请求的 handler 是相当费劲的!从两年前一知半解时写的笔记《Kubernetes 的 Apiserver 的工作过程》中可以感受到。

发现了套路后会豁然开朗,找到某个接口的处理逻辑非常简单:全都在 pkg/registry 目录中,每类资源占一个目录,每个目录的结构和文件内容都是高度相似。创建前、更新前以及其它阶段的处理代码都在这里:

kube-apiserver的http请求处理
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容