总体上是这几点:
- 以祈祷、读文档、读注释、看issue为理解代码的主要手段
- 以读代码、debug为辅助理解代码的方法
- 以debug为验证理解的主要手段
- 每日整理当日理解的内容
- 不理解的留个印象,先跳过
为什么要祈祷?
不论怎么说,还是挑著名一点的项目吧!
- 为了文档和注释比较好而祈祷
因为作者不一定太关心文档和注释:大多数人以正确实现为主要目标。所以要祈祷文档和注释是正确的,或者说大多数是正确的。
为什么不考虑没有文档和注释的情况?理由很简单,如果该项目不出名,作者也懒得维护,所以这些东西没有或不完善是很正常的。
- 为了有人已经研究过你想要弄明白的东西,并且保留了博客而祈祷
有些东西,在文档和注释里都没有写明白。注释受限于“当前的上下文”而不能够照顾到全局,而文档受限于“写文档的人对项目理解程度、时间、薪资和文字功底”。
博客,也会受到“写文档的人对项目理解程度、时间、薪资和文字功底”的限制。我认为一篇真正优秀的博客,要能被人理解,至少被目标人群理解,又能够照顾到博客主题的方方面面的细节,并且能够做到自洽。我勉强能够做到自洽,细节也勉勉强强,但是让多数人理解确实很难。
为什么要读文档?
如果文档的质量足够高,那么文档中会记录代码每个部分,至少是关键部分的概览,并且还会关联到代码中的变量名。
如redis的backlog,实际上你可以直接通过grep在代码中找到对应的变量,到此就可以直接推测哪些函数在操作backlog,这样可以帮助你理解代码。
为什么要读issue?
issue是用户对作者提需求,所以它至少可以解释为什么有这个功能。
但是能够提issue的人都应该是程序员,所以不论是作者还是用户都会讨论这个功能或bug的原因、设计细节。
比如redis用的epoll,是epoll_create而不是epoll_create1,原因就是兼容旧版本内核。
为什么要整理?
理由很简单,代码是复杂的,而人容易忘事情。
且不说读代码,就算是写代码。你今天写了一个函数,两周后你看到了,可能会在想“这是谁写的shit”。
不要相信手中的草稿纸会记录清楚这一切,因为首先纸笔写下的内容不方便修改,你可能要把一大堆东西写到一片很小的空间里,其次草稿纸就是帮助你理解一小段东西的,所以会很乱,所以第二天来了,就再也不想看了。
我曾经就很喜欢用草稿纸,结果是虽然写了很多,但是第二天几乎还是从头来。看看我用了多少草稿纸吧,里面的笔是一个参考物。
如何整理?首先利用电子技术,画一遍执行流程图,流程图旁边至少标记这是哪个函数做的事情,甚至你可以精确到哪一行。然后记下你今天研究的问题是什么,下一个要研究的问题是什么。如果有网页,记得也贴上。
如果还用上了断点,记得也保存一份断点。
这里确实放一个流程图会好一点,但是我省略了函数名。
对于不理解的内容
因为读代码的时候,对整个项目的理解度不足,所以出现了一些难以理解的部分,可以考虑跳过。
怎么对不理解的内容留个印象?
首先,如果可以的话,看注释,或者利用关键词查文档,大概了解一下这是干嘛的。比如redis在复用rdb的时候,注释里说这是复制缓冲区。但是你不知道这个缓冲区是什么,也不知道其作用。查看了一下,操作了backlog,得知这个是复制命令的历史记录的位置。
/* Perfect, the server is already registering differences for
* another slave. Set the right state, and copy the buffer.
* We don't copy buffer if clients don't want. */
if (!(c->flags & CLIENT_REPL_RDBONLY))
copyReplicaOutputBuffer(c,slave);
但是还是不理解怎么办?没办法,你只能在整理的时候,标记好在哪个文件的哪些地方有哪些不理解的地方。
理解基础部分
读代码的时候,你至少要明白整个程序最基础的部分,比如线程模型。然后还要找到一些明显很通用的东西,比如TCP连接的封装。如果你不读线程模型,那么你就不知道一个流程走完了,其接下来的步骤。
比如一个PING/PONG,收到了PING,然后设置写入回调来发送PONG。如果不理解线程模型,这个很可能就被忽略了。
当然还有一些东西要注意的,比如定时器,一定要知道作者的设计。我个人不建议redis的定时器的实现,因为它要做太多事情了!一个serverCron里面做的事情非常多,既要管cluster,还要管主从复制,还要更新自己的状态,还要负责重连!
当然我暂时没想明白什么样的定时器能够同时做到编写时容易,且读代码的时候也容易的。但是我知道至少得是一种注册式的定时器,就像Reactor中注册回调函数一样。
利用debug技巧
正如之前所说的,redis的定时器是通过修改全局变量来影响的,所以导致一个问题:你很难知道这个全局变量改了之后影响了什么。
所以一些debug技巧是很有必要的。
gdb的watch,还有rwatch可以帮到你很多,但是他们的效率可能很低。有时候一个watch,等它出现断点要等很久。如果真是这样,那么你可能设置的不是硬件断点。一般建议直接用watch -l 变量
。设置硬件断点一般可以很快的查明到底是哪里修改了变量,哪里读取了变量。