22

如何阅读一份源代码?

 5 years ago
source link: https://www.codedump.info/post/20190324-how-to-read-code/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

阅读源代码的能力算是程序员的一种底层基础能力之一,这个能力之所以重要,原因在于:

  • 不可避免的需要阅读或者接手他人的项目。比如调研一个开源项目,比如接手一个其他人的项目。
  • 阅读优秀的项目源码是学习他人优秀经验的重要途径之一,这一点我自己深有体会。

然而,读代码比写代码还是更难一些,原因在于“ 写代码是在表达自己,读代码是在理解别人 ”。因为面对的项目多,项目的作者有各自的风格,理解起来需要花费不少的精力。

我从业这些年泛读、精读过的项目源码不算少了,陆陆续续的也写了一些代码分析的文章,本文中就简单总结一下我的方法。

先跑起来

开始阅读一份项目源码的第一步,是先让这个项目能够通过你自己编译通过并且顺利跑起来。这一点尤其重要。

有的项目比较复杂,依赖的组件多,搭建起一个调试环境并不容易,所以并不见得是所有项目都能顺利的跑起来。如果能自己编译跑起来,那么后面讲到的情景分析、加上调试代码、调试等等才有展开的基础。

就我的经验而言,一个项目代码,是否能顺利的搭建调试环境,效率大不一样。

调试手段

调试手段,大体分为以下两种:

  • 加调试语句。为了做到这一点,你需要先了解项目如何加调试日志,可能需要修改项目的日志级别支持输出一些在调试级别的日志,等等。
  • 断点调试。并不是所有项目代码,跑起来之后都自带调试信息能够断点调试的。所以在自己的调试环境里需要先确定这一点。比如一些C相关的项目,基本都是”./configure & make”来编译,但是makefile中的编译flags使用了O2之类的优化选项,此时需要自己先手动修改成”-O0 -g”,即编译生成的二进制中不优化且带上调试信息。

总之,在能够搭建自己的调试环境之后,还需要想办法确定一下如何加上调试日志以及断点调试。

使用顺手的工具

好的工具会让你事半功倍,这一点应该很多人都同意。

我阅读Go代码的时候,喜欢使用IDEA,这个IDE工具可以完美的做到以下几点:

  • 符号的定位、跳转、查找符号被引用的地方。
  • 左边能够展开一个源码文件中的所有符号。

反之,很多人推崇的VSCode,我几次尝试使用用来阅读Go和C类代码,都觉得不够顺手,查找符号能力不行、也没有地方可以看到一个文件中出现的符号。

C\C++类的代码,在尝试各种工具之后,还是使用Vim+Ctags+Cscope来写C、C++代码。

情景分析

假如有了前面的基础,已经能够让项目顺利在自己的调试环境跑起来了,那么就可以对项目代码进行情景分析了。

所谓的“情景分析”,我的理解就是自己构造一些情景,然后通过加断点、调试语句等分析在这些场景下的行为。

以我自己为例,在写 《Lua设计与实现》 时,讲解到Lua虚拟机指令的解释和执行过程中,需要针对每个指令做分析,此时用的就是情景分析的方法。我会模拟出来使用该指令的Lua脚本代码,然后在程序里断点调试这些场景下的行为。

情景分析的好处在于:不会在一个项目中大海捞针似的查找,而是能够把问题缩小到一个范围内展开来理解。

“情景分析”这一概念不是我想出来的名词,比如有这么几本分析代码的书籍,如: 《Linux内核源代码情景分析》《Windows内核情景分析》

利用好测试用例

好的项目都会自带不少用例,这类型的例子有:etcd、google出品的几个开源项目。

如果测试用例写的很仔细,那么很值得好好去研究一下。原因在于:测试用例往往是针对某个单一的场景,独自构造出一些数据来对程序的流程进行验证。所以,其实跟前面的“情景分析”一样,都是让你从大的项目转而关注具体某个场景的手段之一。

厘清核心数据结构之间的关系

虽然说“程序设计=算法+数据结构”,然后我实际中的体会,数据结构更加重要。

因为结构定义了一个程序的架构,结构定下来了才有具体的实现。

Linus说: “烂程序员关心的是代码。好程序员关心的是数据结构和它们之间的关系。”

因此,在阅读一份代码时,厘清核心的数据结构之间的关系尤其重要。这个时候,需要使用一些工具来画一下这些结构之间的关系,我的源码分析类博客中有很多这样的例子,比如 《Leveldb代码阅读笔记》 、《Etcd存储的实现》等等。

需要说明的是,情景分析、厘清核心数据结构这两步并没有严格的顺序关系,不见得是先做某事再做某事,而是交互进行的。

比如,你如果现在刚接手某个项目,需要简单的了解一下项目,可以先阅读代码了解都有哪些核心数据结构。理解了之后,如果不清楚某些情景下的流程,可以使用情景分析法。总而言之,交替进行直到解答你的疑问为止。

多问自己几个问题

学习的过程中离不开交互。

如果阅读代码只是输入(Input),那么还需要有输出(Output)。只有简单的输入好比喂东西给你吃,而只有更好的消化才能变为自己的营养,而输出就是更好消化知识的重要手段。

其实这个思想很常见,比如学生上课(Input)了需要做练习作业(Output),比如学了算法(Input)需要自己编码练习(Output),等等。简而言之,输出是学习过程中的一种及时反馈,质量越高学习效率越高。

输出的手段有很多,在阅读代码时,比较建议的是自己能够多问自己一些问题,比如:

  • 为什么选择这个数据结构来描述这个问题?类似的场景下,其他项目是怎么设计的?都有哪些数据结构做这样的事情?
  • 如果由我来设计这样的项目,我会怎么做?

等等等等。越是主动积极的思考,就越有更好的输出,输出质量与学习质量成正比关系。

写自己的代码阅读笔记

我从开始写博客,就是写不少各种项目的代码解读类文章,网名“codedump”也源于想把“code内部的实现原理dump出来”之意。

前面提到学习质量与输出质量成正比关系,这是我自己的深刻体会。也因为如此,所以才要坚持阅读源码之后写自己的分析类笔记。

写这类笔记,有以下几个需要注意的地方。

虽然是笔记,但是要想象着在向一个不太熟悉这个项目的人讲解原理,或者想象一下是几个月甚至几年后的自己回头来看这个文章。在这种情况下,会尽量的把语言组织好,循循善诱的解释。

尽量避免大段的贴代码。我认为在这类文章中,大段贴上代码有点自欺欺人:就是看上去自己懂了,其实并不见得。如果真要解释某段代码,可以使用伪代码或者缩减代码的方式。记住:不要自欺欺人,要真的懂了。如果真的想在代码上加上自己的注释,我有一个建议是fork出来一份该项目某个版本的代码,提交到自己的github上,上面随时可以加上自己的注释并且保存提交。比如我自己注释的etcd 3.1.10代码: etcd-3.1.10-codedump ,类似的我阅读的其他项目都会在github上fork出一个带上codedump后缀的项目。

多画图,一图胜千言,使用图形展示代码流程、数据结构之间的关系。我最近才发现画图能力也是很重要的能力,自己在重头学习如何使用图像来表达自己的想法。

写作是很重要的基础能力,我一个朋友最近教育我,大体的意思是说:如果你在某方面的能力很强,如果再加上写作好、英语好,那么将极大方法你在这方面的能力。而类似写作、英语这样的底层基础能力,不是一撮而就的,需要长时间保持练习才可以。而写博客,对于技术人员而言,就是一种很好的锻炼写作的手段。

总结

以上是我简单总结的一些阅读源码时候的手段和注意方法,大体而言有那么几点吧:

  • 只有更好的输出才能更好的消化知识,所谓的搭建调试环境、情景分析、多问自己问题、写代码阅读笔记等都是围绕输出来展开的。总而言之,不能想一条死鱼一样指望着光靠看代码就能完全理解它的原理,需要想办法跟它互动起来。
  • 写作是人的基础硬实力之一,对程序员而言锻炼写作能力的手段之一就是写博客,越早开始锻炼越好。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK