浅谈如何阅读开源软件的源码----谈谈我读ruby049源码的体会
很长一段时间没有读源码,也没有更新csdn,但所幸的是,还是在断断续续的读。有时在网上找资料,发现,很少。有时,找来找去,还是只看到自己写的那些博客。而自己写的博客内容是很浅薄的。于是,心想,或许我来补充完整吧。网上读源码的人少,我来当第一人,把大家都带动起来。今天重点谈谈自己的一些思路。
第一、用成长型思维读源码。
前段时间看书,发现思维模式很重要,有人用成长型思维看问题,有人用固定型思维看问题。而读源码时,个人体会是,第一眼看到从官网上下载的源码,解压一看,这么多的源文件,随便打开一个文件,发现完全没有头绪,此时,一定怀疑自己,能看懂吗?自己是搞这块东西的人吗?此时,会有很强的失败感,一种常常的挫折感。
我以为,此时,树立信心很重要。就像出去旅游,当你走出家门,其实就已经做好了最重要的准备步骤。其它一切都是技术性的困难。
同样,当你把源码下载下来,解压了,从打开第一个文件开始,最困难的问题已经解决。此时,我们要坚信自己能看懂。
第二、当我们确定要做一件事时,方法就来了。
1.我想读ruby源码,我的第一个体会是读能找到的最古老的版本,不要读现在的版本。像ruby2.6.5现在有多少行?投入这么大的精力去读,划不来。为此,我学赵炯老师的做法,坚决只读最古老的版本。为此,我从rubychina上下载最早的版本,ruby0.49。
2.如果能对我们所读的软件的使用很熟悉,那读起来会事半功倍的。你可以去挖掘自己感兴趣的某个功能是如何实现的?就像侦探一样。但我其实对ruby也不太熟悉,没用ruby做过项目。于是先把ruby文件夹下的每个文件都打开,快速的过一遍。像README是日文的,还有几个文件也是日文的,那没法看。发现sample下面有一些测试文件,于是从它开始,我在最新版本的ruby下测试这些程序,而且这些测试程序还跑不通,需要对这些测试程序进行修改。修改的过程,其实也是学习的过程。
3.在本地,搭建环境,把软件跑起来。
这一步很关键。我原来曾幻想用ipad来读源码,真的。可惜没有找到好的软件app去打开文本。后来,我在一台旧笔记本上安装ubuntu后,再读源码。为此装最古老的gcc编译器,去不停的编译ruby0.49,总是不能生成ruby程序。没办法,就只能把一个一个的警告去掉,终于生成了ruby可执行文件。
总之,我以为,方法是人想出来的,只要勇敢的迈出第一步,事情就能办成。
第三、读源码时,要像陶渊明读书一样,不求甚解
什么意思呢?就是要允许自己有很多不明白的地方。要去掉不理解就不往下读,一定要彻底弄明白才向下读的习惯。
个人的体会是,不明白,没问题,总有一个地方能看明白。以这个点为基础,慢慢的向外扩散。就像红军打仗,以点带线,以线带面。星星之火,可以燎原。
好读书,不求甚解。用在读源码上,我对ruby049有了很大的理解。我比较喜欢用ruby的数组,为此,就读array.c这个源程序。读不懂,也硬着头皮向下推进。你千万不要计较自己不懂的。像parse.y这个bison文件,我一打开,妈呀,我学了bison,并且基本还能把gawk1.01中awk.y读懂,可一看ruby的bison文件,完全 不懂。我不是被它吓走。不懂,搞你不赢,那就躲。
总之,读源码,要学会躲。但不要放弃。现在的躲避,是为了以为的彻底解决。允许自己大面积的地方不懂,但不妨碍你一点一点的向前推进。找到自己基本能看懂的地方,一点一点的啃。有时真是寂寞呀,真想找个人说说话。但忍住。
第四、读源码时,要敢于怀疑
像我在读ruby0.49的源码时,其中在string.c
static
str_hash(str)
struct RString *str;
{
int len = str->len;
unsigned char *p = (unsigned char*)str->ptr;
int key = 0;
if (ignorecase) {
while (len--) {
key = key*65599 + *p;
}
}
else {
while (len--) {
key = key*65599 + toupper(*p);
}
}
return key;
}
请大家看
while (len--) {
key = key*65599 + toupper(*p);
}
其中p指针没动,当时在想,你要计算hash,肯定要对字串中每个字母进行计算呀,你不动如何算?
while (len--) {
key = key*65599 + toupper(*p++);
}
这样才对?可这是名人写的,我的怀疑对吗?后来我就加打印语句,我开始把打印语句只加在一个地方,结果调试时,没看到输出信息,当时就怀疑自己,是否搞错了。后来,想,应该没错了,于是把打印语句,再在另一分支也加上,如下所示:
if (ignorecase) {
while (len--) {
key = key*65599 + *p;
printf("2020[%c]",*p);
}
}
else {
while (len--) {
key = key*65599 + toupper(*p);
printf("2020[%c]",*p);
}
}
,总之,在读代码时,要把自己放在和作者同等地位上,要敢于质疑作者的代码。一句话,要敢于和作者进行斗。
为此,搭建一个环境,进行测试相当的重要。我原来想在ipad中读源码,现在觉得,当代码彻底读懂了,或者有了根本性的理解后,再用ipad之类工具,像看小说一样,读源码是可以的。但当你拿到一个陌生的代码,想通过ipad来读代码,真心不建议。就像树莓pi的发明人所言,ipad,kindle只是一个消费类工具,原则上不鼓励你修改代码,不鼓励你创造。因此,我们应该在linux上安装好gcc,vim,ctags来读源码。
第五、读源码时,要由使用开始
昨天读array.c时,发现对assoc这个函数用法不懂。于是我安装好ruby2.6,再在ri Array.assoc中学习assoc的用法。有了大致的了解后,再去读源码,就比较快了。
读源码是一个越读越快的过程,我发现有些函数,你从心智上不理解这个函数使用的场景,这时解读是很难的。
你想不通哪个场景需要这样一个函数,为此,也就很难理解这个函数的用法。
为此,先让自己熟悉使用方法,非常重要。只要你知道程序要解决的问题,你肯定可以看懂这个程序。
第六、读源码,更要读书
写到这里我有些凑字数的嫌疑了。为此,先用zf把每小节的内容折叠起来。看到大的框架写的是什么,再加具体框架。我的意思是,写作时,要采用金字塔思维,由大及小,而不要由小到大的写。
用到读源码上,我非常想建立源码的整体框架,甚至用那种函数调用逻辑关系的工具。但我还是想自己去总结。另外,过早陷入细节,会让我头脑装不上。为此,我个人建议,还是去读书,通过别人的总结,把程序的整体框架建立起来。比如ruby吧,我就仔细把《ruby元编程》《ruby原理剖析》《effective ruby》《ruby编程语言》等书,反复的读,尤其是ruby元编程,其中讲,解析方法的过程,讲得非常透。为此,昨天我不小心就看到了ruby源码中实现这一段的逻辑:
static struct RMethod*
search_method(class, id, origin)
struct RClass *class, **origin;
ID id;
{
struct RMethod *body;
NODE *list;
while (!st_lookup(class->m_tbl, id, &body)) {
class = class->super;
if (class == Qnil) return Qnil;
}
if (body->origin)
*origin = body->origin;
else
*origin = class;
return body;
}
matz把复杂的继承关系弄成一个单链表,这样程序实现起来,确实就简单多了。就像责任链模式一样。另外,《ruby原理剖析》这本书也很好。读源码之前,感觉这书好复杂,真读了源码,就发现,这书写得还是太粗。另外,是争对ruby高版本写的。其实,我想要的是类似新设计团队分析linux0.11那种细节的书呀。
第七、自己动手,把类的关系图画出来。
我第一次在公司参加一个项目,是关于一个语音卡的系统,当时,我用很多一开的纸,把程序的调用关系弄出来。像一个类,就写类名,这个类的成员变量,方法一一列出来,然后试着用自己的语言去描述,这个程序解决的问题。
现在读ruby0.49,我本不想这样做。但后来,发现脑袋真记不住,于是,又把类图画出来,把ruby.h,node.h等头文件中关键数据结构弄出来,读代码时,就想像着,代码如何把数据结构填写满。
我现在用了三个屏幕读代码,还是感觉屏幕不够。而当我把重要的数据关系,画在纸上时,我再看时,就一目了然。这能从宏观上让我把握程序。太妙了。难怪unix的创造人,如此重视数据结构,他说,他写代码时,几乎没有流程图,但会时时看到数据结构。原话不记得了,请参见《代码人生》。
今天就写这些,通过读源码,感觉自己更有自信心了。因为能搞懂牛人的源码,给了自己无比的信息,觉得自己能掌握代码,也就能掌握自己的人生。