我如何阅读代码?
1. 寻找一位好老师
一个优秀的项目就像一个好老师。我们可以从中学到各个领域的全方位知识。
但在开始阅读代码之前,最大的问题是:如何找到合适的编码项目?
星级越高的项目越好吗?从某些角度来看确实如此,但在gitstar-ranking 中,一个4k Star 的Repo 只能排在第5000 位,而一个至少有50k Star 的Repo 才能排在前100 名。光看star 数,有可供选择的项目太多了。
我认为,重点关注以下几个方向,一般可以选择合适的项目:
出于兴趣
首先要选择自己感兴趣的领域的项目。
很多代码片段枯燥且难以阅读(比如“飞”的位运算、莫名其妙的提高性能的语句,或者包含大量隐性知识等)。只有你有兴趣才能阅读它们。只有有读书的意愿和动力,你才能从中找到乐趣。
有时我们开始阅读代码的机会只是为了在工作中使用它。如果我们想更多地了解它的原理和设计,这是一个很好的起点。想必很多同学第一次看源码是因为一层层追查Spring的Class。
有时你可能只是认为某种技术很神奇,就像魔术一样。越是想不通,就越想知道它是如何“施法”的。
总之,一旦你感兴趣了,你就会想了解更多。但是,如果你读到一半就失去了兴趣,那就放弃吧。失去了这片草之后,还有一整片森林等待着我们去探索。
经典且广泛使用
拥有大量用户的经典项目经历了时间的考验并不断迭代,通常在设计上有很多突出的特点。
经典项目的维护者一般都是非常资深的工程师,而且也有大公司赞助,保证了代码的高质量。在这类项目中,你可以在阅读过程中学到很多知识,包括架构抽象、性能优化、工程化等。
常见且典型的项目包括:Go、Kubernetes、MySQL等。
尺寸合适
代码过多的项目有时非常出名,但也不可避免地令人生畏。事实上,你可以找到很多代码库,虽然行数不多,但仍然令人兴奋。
首先是各种语言的标准库,比如Java的Stream、Lock实现等。另外还有很多小而美的开源项目,比如redis、leveldb等。甚至很多经典大学课程中的Labs也有隐藏优秀的代码,例如xv6。
总之,这类代码几天或几周就能大致读完主体部分,特别适合学*设计思维。
2. 先看文档
选好项目后,我们对它已经有了基本的了解了。
此时,不要直接克隆代码。代码完全包含了所有的知识,同时也毫无保留地暴露了细节。如果直接进入代码很容易迷失方向。
认识事物,从整体到局部,从抽象到细节,是一个更容易让人接受的过程。
成熟的项目通常都有相对详细的文档。文档一般分为两类:用户的使用文档和贡献者的开发文档。
了解概览
通过阅读使用文档,我们可以快速了解项目的目的、解决什么问题以及从用户的角度来看这个软件是什么样的。除了看概述之外,我一般也会关注配置。通过所需的配置,我可以进一步了解软件的依赖关系和外部功能。
以TiDB 为例,其使用文档截图如下:
从左侧边栏可以了解到使用文档的结构包括介绍、部署、配置、参考等,这些部分是用户最关心的内容。
架构和模块
优秀的开发文档必须包含整个软件的架构模型以及关键模块的设计。
通过阅读架构图和高层设计,软件的原理和解决问题的思路一目了然。有一定经验的读者在看到架构设计后甚至可能对软件工作流程有一个大概的了解。
上图是TiDB 开发文档的截图。我们发现它不仅包含架构设计,还详细告诉读者如何开始代码、如何贡献、详细的设计流程等。相对完整的开发文档除了架构设计之外,还会包含关键的信息模块。关键模块可能涉及核心逻辑的设计和数据结构,以及边界的合约和交互方法。对于Go语言这种不断发展的开源编程语言来说,甚至建立了一个Proposal存储库来跟踪各种提案的设计、讨论、代码和发布,比如等待了10年的通用提案:
了解了架构模型和关键模块后,当你真正打开源代码时,你可以将包、文件名、接口等包含的知识映射到整体结构上,在脑海中形成完整的画面。
其他必备知识
这些前提知识对于我们的理解会有很大的帮助。通过学*这些知识我们可以更加了解软件的详细设计。
3. 再读代码
阅读完文档后,就可以开始看代码了。为了防止在代码中迷失,我们阅读时可以遵循几个原则:
从入口处开始
虽然我们可以通过架构模型以及包和文件划分的关系大致判断哪些代码是核心代码,但从入口开始更符合大脑的思维方式。
因为入口代码的工作一般是先初始化各个模块,然后调用主线程或者启动主服务。这种简单明了的工作顺序可以防止我们一开始就遇到困难。循序渐进的过程使大脑更容易产生奖励。
如图所示,简化后的kubelet启动入口主线逻辑非常清晰。以此为起点,可以从三个方面查看配置细节、创建kubelet的细节、启动的细节。
抓住主线,从抽象到实现
主线是如何从输入一步步生成输出。在这个过程中,会涉及到多个模块,每个模块都有自己的输入和输出。
当我们按照函数调用和数据传输的方向一步步进行时,随着抽象层次的不断降低,涉及到的细节也越来越多。这个时候,我们应该及时回头,不要一路看到尽头。很容易迷失其中。
一个好的设计会有合理的抽象。根据开发语言的不同,我们可以通过查看包、接口、特性、公共方法列表、头文件等快速获取抽象信息,逐步拼凑出程序的主线。明确主线后,逐步展开抽象,阅读具体的实现代码。
仍然以kubelet为例,kubelet是负责整个节点运行的核心,有很多工作。
但看它的代码分包结构,很明显,不同的功能点被划分到了不同的目录中。结合初始化逻辑,进一步深入各个功能目录,我们还可以发现kubelet的模块设计遵循多个管理器。核心协作模型。好的抽象就像洋葱,具有清晰的层次。
边读边记录
当你第一次了解一个项目时,你可能对结构和流程没有清晰的把握,所以同时读、写、画非常重要。
有时候跳跃很多,以前看过的东西后来就忘记了。因此,记录关键路径的具体函数名和模块名可以帮助我们快速回溯到入口点。
有时你会遇到一个知识盲点,需要拓展。为了不打断主线思路,可以先记录下来,再找时间学*。
另外,当遇到不直观、难以形成概念的代码表达,看一遍又无法理解时,就需要画图来帮助理解。
一个典型的例子就是学*B+Tree的分裂、合并、上下移动时。看代码是很不直观的。如果你想理解这种内容,画图就神奇了:
必要时使用调试
出于正确性、性能等考虑,某些代码的表达可能会比较混乱。人类的思维偏向于秩序。用软件开发来类比,我们更容易理解Happy Path,而忽略分支细节。
当你搞不懂某段代码为什么要这样写时,你可以通过实际运行并添加断点调试来找到真正的原因。
一个有趣的例子是:在环形队列中,判断队列是否为空需要检查头指针和尾指针是否重叠。下图中的代码来自于无锁环形队列的空判断实现。
从逻辑上讲,循环队列入队为tail++,出队队列为head++。先入队,后出队,所以tail必须大于head。那为什么在上面的代码中,除了判断tail - head==0之外,使用tail head的时候还必须认为是空的。这不可能发生吧?
实际原因是,由于环形队列是无锁的,并且无法保证tail和head之间的同步,因此不同的线程可能会因为调度因素而在不同时间读取值,结果实际上产生了tail head。
弄清楚这种情况的最好方法是实际执行数百万次测试,在发生tail head时使用条件断点停止代码,然后观察内存中的值进行解释。
4. 写篇文章讲讲整个设计
整理大纲
写文章时,目录是最重要的。一篇文章是否逻辑清晰、结构清晰,完全取决于大纲的设计。
由于我们的内容是关于软件的设计和实现,所以文章的大纲可以按照Why - What - How来展开:首先告诉读者为什么要设计这个软件,它要解决什么问题。然后描述了软件的架构模型、关键模块和主线流程。最后对具体实现进行了详细说明。
刚开始写文章时,我们的知识还不够深入和全面。我们可以先写什么和如何部分。加深理解后,我们就能明白这样设计的原因了。
以我写的一篇介绍Go语言运行时计算模型设计的文章目录为例:
可以看出,文章整体结构大致按照“设计意图与目标——核心概念讲解——具体实现——总结与拓展”的顺序进行设计。这种清晰简单的目录结构非常适合技术文章。
通过图纸描述设计原理并帮助分析设计意图
在介绍原理和实现时,用图来表达比贴代码更好。代码确实可以体现所有设计细节,但代码更重要的任务是充当知识和硬件指令之间的桥梁。相反,如果我们以图表的形式表达设计意图,会对人类更加友好,更容易阅读、理解和学*。绘画本身也是一个加深理解、去粗取精的过程。
下图是我读完leveldb后画的leveldb存储架构图:
作为存储引擎,LSM Tree的实现是leveldb的核心。 leveldb本身的源码非常清晰简洁。不过如果通过上图来描述LSM Tree的具体设计的话,会比贴代码容易理解很多。
大家想一想,为什么要这样设计,有什么好处呢?
当我们能够用图和文字来表达软件的完整设计时,我们对代码的理解就更加透彻了,甚至我们自己写一个新的也不是不可能。
我在阅读Go语言内存管理代码的时候,初步了解了tcmalloc的原理和实现,但是对于它所谓的线程缓存、无锁分配等卖点却没有深入了解。直到我回去看了CSAPP中动态内存分配的章节,结合ptmalloc和jemalloc的设计,相互比较和理解,才对tcmalloc的设计决策有了更清晰的认识。
本节讨论一些关于撰写文章的内容。对于技术写作,我推荐Thoughtworks Insight Team 制作的《技术写作手册》。
5. 讲个 Session,收获 Extra Bonus
如果你还有精力和兴趣,不如把文章的内容摘出来开个环节给大家讲讲。额外的努力将会得到额外的回报。
有过当讲师经历的同学一定知道,与人交谈中收获最大的人不是听众,而是讲师本人。如果要输出一小时的会话,准备时间可能需要十个小时。我们需要花数倍的时间去讲解,完善材料,理清思路,准备问题,甚至思考可能涉及到的扩展内容。做这些任务不仅提高了我们的会议质量,而且无形中不断地加强了我们对相关知识的理解。
梳理要点,逻辑自洽
Session成功的基础在于逻辑上是否能够一致。逻辑自洽的前提是要点必须清晰、前后呼应。
上一节提到的文章恰好是Session素材的来源,所以我会反复遍历整篇文章,希望能提取到需要的内容。这个过程往往伴随着不断发现文章中内容缺失和逻辑不一致的地方,此时文章得到进一步完善。因此,我常常发现,当整张Slide完成后,不仅成就感爆棚,文章也更加充实,理解也更加深刻。
去粗取精,练*表达
与写文章相比,谈论会话需要我们进一步去除细节,只保留核心思想。这本身就是对抽象能力的锻炼。
另外,自己清楚地理解一件事和能够向别人解释清楚是两个完全不同的概念。如何把核心知识讲给观众,让观众更容易理解,需要仔细思考语言表达。每一次成功的课程都是您表达自我能力的提高。
表达中最常见的问题就是根据课文朗读。我个人喜欢通过减少Slide的字数来强迫自己提高表达的逻辑性和连贯性。你可以尝试思考如果内容只是一张图片的话如何解释清楚这张图片,并用这种方法来训练你的表达能力。
找出观众感兴趣的内容
考虑观众的感受也很重要。如果内容不让人感兴趣、不喜欢听、或者难以理解、跟不上节奏,就很容易导致整个环节反应迟钝,让人觉得浪费了。通过聆听您的会议来他们自己。时间。
所以不仅要能说清楚,还要弄清楚观众感兴趣的是什么。合理设置内容,去掉枯燥但不关键的东西,调整讲解的顺序,穿插通俗易懂的内容以及精彩的部分,让你不断激发观众的兴趣。
最后,线下组织的效果比线上视频讲解要好很多。线下观众的注意力更加集中,互动性更强,演讲者更容易通过观众的表情和神态判断内容和速度是否需要调整。如果由于限制而必须进行视频会议,您可能需要经常停下来提出问题或主动寻求反馈。
用户评论
我是怎么读代码的
有9位网友表示赞同!
这个游戏教会了我如何解构和理解复杂的编程语言。
有13位网友表示赞同!
在“我是怎么读代码的”中,我发现了自己的代码阅读技巧。
有7位网友表示赞同!
通过这个平台,“我是怎么读代码的”的内容帮助我提高了解决程序员问题的能力。
有17位网友表示赞同!
第一次尝试“我是怎么读代码的”,我被其中的趣味性吸引。
有5位网友表示赞同!
游戏让我的实践代码理解能力有了质的飞跃,就像在“我是怎么读代码的”中一样!
有5位网友表示赞同!
"我是怎么读代码的"真的超级有意思,它让我明白代码背后的故事。
有9位网友表示赞同!
“我是怎么读代码的”将枯燥的概念变成了一种乐趣,我喜欢这种学*的方式!
有7位网友表示赞同!
这个游戏用有趣的情节讲述了编程知识,尤其是阅读代码的部分,“我是怎么读代码的”做得很好。
有11位网友表示赞同!
我从未想过通过游戏来学编码,“我是怎么读代码的”改变了我对编程的理解。
有12位网友表示赞同!
"我是怎么读代码的"让我在娱乐中进步,真的很赞!
有15位网友表示赞同!
“我是怎么读代码的”的每一关都是对编程知识的精彩挑战,我喜欢这种学*之旅。
有19位网友表示赞同!
如果你是编程初学者,“我是怎么读代码的”将帮助你建立一个好的基础。
有14位网友表示赞同!
"我是怎么读代码的"以互动的方式教我如何快速地阅读和理解其他人写的代码。
有16位网友表示赞同!
借助“我是怎么读代码的”的情节,我对逻辑和代码的结构有了更深的理解。
有14位网友表示赞同!
这个游戏不仅有趣,“我是怎么读代码的”还让我对编程有了更深层次的认识。
有12位网友表示赞同!
"我是怎么读代码的"完美地融合了教育与娱乐,是我学*编程的新方式。
有16位网友表示赞同!
在“我是怎么读代码的”中,我掌握了阅读和分析代码的强大工具。
有8位网友表示赞同!
“我是怎么读代码的”帮助我在实践中提升我的编码技能,效果绝佳!
有13位网友表示赞同!
如果你正在寻找一种方法来改善你的代码阅读能力,“我是怎么读代码的”是完美的选择。
有19位网友表示赞同!