如何阅读一份代码?
这是一款来自小程序免费开发制作网的小程序demo源码,希望大家喜欢。
上文谈到了像读书一样阅读源码的重要性,今天谈谈如何阅读一份代码。我所谓的一份代码,其范围可能从几千行到数万行,有时甚至可多达数十万行。这些代码作为一个有机体,共同完成某些重要的功能。比如说几个著名的 full fledged web framework,祖师爷 rails,师叔 django 和小师妹 phoenix:
(此图片来源于网络,如有侵权,请联系删除! )
(此图片来源于网络,如有侵权,请联系删除! )
(此图片来源于网络,如有侵权,请联系删除! ) 三者对比很有意思 – rails / django 的代码数都在 200k 上下,而 phoenix 小了整整一个数量级,仅仅在 20k 左右。实现大致相同的功能,语言的表现力难道差距如此之大?这解释不通。实际上 phoenix 实现的功能,和 rails 对标,应该是 actionpack/actionview 两者加起来约 80k 的代码。而 rails 内嵌的 activemodel/activerecord 应该对标 elixir 的 ecto,恰巧又是 80k 比 20k。这种差异反映了语言表现力的差异,同时也体现了框架的成熟度的区别。 几十 k 体量的代码,就像一本不薄不厚的书,读着既不算太过吃力,也不至于读完意犹未尽。 有些巨型的代码库,如 linux kernel,块头堪比『战争与和平』,代码的规模宏伟到令人绝望,大大超过了我们能够阅读理解的范围。其结果是我们每每下定决心阅读,投入的巨大的精力,却像往一池湖水里投个石子,虽掀起一丝涟漪,但湖面很快就归于平静。
(此图片来源于网络,如有侵权,请联系删除! ) 读不下来,我们也不要太绝望,可以分而治之,先定个小目标,每次读一部分,比如 scheduler(20k)或者 memory management(80k)。 在选定合理的代码规模和要阅读的源码后,我们就可以清开书桌,摆上 mac,准备好笔墨纸砚,留出至少一个小时到半天时间,开始徜徉在代码的海洋。 由于上一篇文章(为什么我们要阅读源码?)从头至尾将阅读书籍和阅读代码进行对比,很多人会不禁联想本文会否和『如何阅读一本书』进行类比,提供同样的思路:基础阅读,检视阅读,分析阅读,对比阅读。不错,这些读书的方法对读代码非常有参考价值,比如说检视阅读,我们读大型代码项目,也是类似的思路:
但本文不会过多这样去对比 —— 大家真有兴趣,何不亲自读读那本书(读过的请带着读代码的角度再读一遍)呢?毕竟,它更加完备,更加系统。 我想通过另一个角度 —— 阅读的场景来谈谈如何阅读。谈到场景,很多人会联想到一本著名的书:Linux 内核场景分析。该书的作者显然把握了阅读代码的实质:循着一条线索,进行端到端的一个自成体系的内容的阅读。不同场景下,我们已知的信息,未知的信息,通过阅读想要达到的目标是不一样的,显然,方法也不尽相同。这就和读书一样:想要让自己明智,读史;想要让自己灵秀,读诗;想要让自己周密,研习数学;想要让自己深刻,攻读哲学等等。 接下来,本文就从若干阅读代码的场景开始,讨论个人的读代码的一点微不足道的心得。 场景一:为了破案而阅读代码这是我们最主要的阅读代码的场景。工作中,免不了用各种各样的开源系统(别人的代码)。使用的过程中,你会遇到各种奇葩的问题,这些问题可能源于对文档的理解不够,或者从网上抄一个已有的,并不完全适合你使用场景的样例,或者是真的撞上了八阿哥。在想方设法解决的过程中,如果同事帮不上忙,google/stackoverflow 不够给力,论坛上各种「急,在线等」也无人理会,你会开始抓狂,仿佛被摄魂怪缠上了一般,生活中的各种美好,希望,都开始离你远去。 这时,你不得不像 CSI 中的警探一样,顺着一点蛛丝马迹,开始剖析代码,试图从迷雾中还原真相。你会抛开一切纷扰和杂音,集中精力,带着线索,循着问题,读且只读和解决问题直接相关的代码。这种状态,我管它叫「猎手模式」—— 我们有如非洲草原上追逐离群斑马的狮子,把身上燃起的小宇宙集中于一点,眼睛紧盯猎物奔走的方向,腿如疾风,势如闪电,心中不断地盘算着雷霆一击究竟用锁喉,打脸抑或拉后腿胜算更大。道路上的石子划了脚,痛;飞奔时撞上了幺蛾子,烦,但这都不是事儿。就算远远的乞力马扎罗山上难得地挂上了两道彩虹,煞是美丽,自拍后发朋友圈定能破百赞,你也无暇顾及这些并不重要的细节。 专注,集中力量攻击且仅攻击一点是这样场景下阅读代码的主要方式。 拿我遇到的 nginx cache 的问题来举个栗子。一年前,当我接手 Tubi TV 的性能较低且很难维护的 API 系统后,虽打定主意日后重写,但摆在面前的,刻不容缓的问题是提高性能。应用层可以施展的空间不大(数据已经在 redis 里),所以只能在 web 层打主意。在 HAProxy 和 nginx cache 之间,我选择了后者, 因为 nginx 已经在当时的生产环境下大量使用。我虽然没用过 nginx cache,但启用 nginx cache 并不是难事,照着文档设置好 cache 的路径和大小等参数后,在需要使用 cache 的 location 下,设置 cache key 并使能即可,我本地的简单的测试运行正常。然而,在生产环境中,本该命中的请求却一直处在 miss 的状态。我一筹莫展,尝试了网上搜到的各种方案无果。最终,我决定自己编译一个打开 DEBUG 开关的 nginx 版本( nginx cache 及 upstream 里和 cache 相关的代码量并不算多,几千行,我快速过了一下,然后就着日志上的内容寻找相关的处理流程,并在几个大的 bailout 分支猜想可能出现的情景。由于 nginx debug log 还是不够详细到满足我的需求,我在这些没有被顾及到的分支上各自加了调试代码,重新编译,运行。 这个过程中,「猜」起到了很大的作用。我记得本科时的数学老师 —— 一个可爱的小老头 —— 点名提问对方答不上来时,常常挂在嘴边的口头禅是:你猜一下嘛。他总说连蒙带猜也是解题的一种思路,伟大的数学家同时也是伟大的猜想家。 我们读代码时,猜文件名,函数名,变量名的意图,猜某个分支的意图,猜某段代码的意图,最终结合运行的结果,打印出来的调试信息来印证我们的猜测。这是读者和作者间有趣的猫鼠游戏。读得越多,猜得越多,印证得越多,形成一个有效的 feedback loop(read – guess – verify),你下次猜测成功的几率就越大。 最终,问题被我定位出来 —— 它是两三处 configuration 未正确配置的问题。stackoverflow 上的答案是部分正确的,它解决了绝大多数人的问题 —— 没有 ignore cache control 相关的 header 几乎被每个初次使用者忽略了,它也是我的配置问题之一。但之所以这个答案没能解决我的问题,是因为我们生产环境中的 nginx 有个不起眼的配置 disable 了 proxy buffer,从而导致 nginx 直接跳过 cache。 从以上的过程中,我们抽象一下,看看为了破案而阅读代码要注意什么:
还有个关键的第 5 步,我单拎出来说。很多时候我们轮回数次,终于在第三步 bingo 后快乐地像是刚刚 K.O. 了对手的春丽,夹着腿跳将起来,左右手在空中一齐比划着二,得意忘形,以至于忘记了执行第 5 步。 喜悦是短暂的,记忆也是短暂的。整个过程你的目标是如此清晰,执行力无比强大,为达到目的「不择手段」。三天后老板问你,小程啊,你很棒啊。你用了什么手段征服了这个无比难缠的八阿哥?这时你拼命追忆,却像拿筛子盛水,忙乱半天一无所获。你开始怀疑人生:三天前的我和现在的我究竟是不是一个人? 所以关键的第 5 步是:复盘。解决问题后,别着急接受同事们的致谢和女(男)神的秋波。趁着那坨记忆还热气腾腾,抄起 evernote(或者 xxx),把整个过程用最简洁的方式记录下来 —— 关键代码,关键路径,到达终点的整个猜测过程,以及那些日志验证了猜测是对的,哪些日志验证了猜测走不通(恭喜你 —— console 或者 terminal 在这个时候应该还没关),总之,你在不择手段的过程中用过的一切手段,都应该像记流水账一样记录下来。最后分析总结:
在这种「破案」般阅读代码的历程中,如果没有复盘,你 70% 的功夫白费了 —— 你花了不少时间,读了不少代码,除了一个好的结果外,并无太大的收获。可惜的是,绝大多数工作场景,我们都略过了这步。我自己也是(深刻检讨中)。写这个章节时,我搜了搜我的 evernote,翻了翻我的邮件,除了去年初有封邮件只言片语介绍了我使能了 nginx cache 外,再无其他记录。好在我当时解决完问题顺便又读了些 nginx cache 的代码,有些许印象,所以还能把它搬出来做例子。 复盘帮你把这样的信息沉淀下来,让你有机会回顾,进而组织和固化成上篇文章中所说的知识。这样的内容累积多了,慢慢你的头上就会顶起一个光环,光环上傲娇地写着:砖家。 场景二:为了明理而阅读代码场景一所述的读代码方法是被动的,为了对付问题而读,是大部分人精进代码的唯一途径。这就好比英雄无敌里你就做个守成之主,收收矿,屯屯兵,从不主动招惹野怪,只等着敌人来进攻。这样三个月下来,就打了几仗,稳则稳矣,无奈经验值增长太慢。 要想涨快点怎么办?主动出击啊!计算机领域的很多算法,基础知识,理论,在看过书,读过文章后我们都似懂非懂,这时,阅读代码就是最快地巩固和加深理解的方式:
这个过程是一个正反馈,是马太效应累积地过程。你读的书多,你脑子里的知识点就多,疑问同样也多。这些疑问促使你读相关的代码去印证和解惑,代码读多了,又感觉理论知识欠缺,于是周而复始,不断学习下去。反之,书读得少,你脑子里都没存几个问号,也就无所谓读代码去求证了。 以 REST API framework 为例 —— 两年前我还在 Juniper 做 web security 时,需要做一个坚实的 API system。我们知道做网络的,干起事来要比做互联网严谨得多(所以也慢得多),于是我花了好些时间读了 RFC 2616 及其后续的修订(7230-7235)。然后就是对 API framework 进行选型,找个合适的。当时我正好在研究 clojure,便拿了 liberator 来看。Liberator 受 erlang 下的 webmachine 启发,用简单的 macro 把 decision tree 实现得很优雅。后来我又扫了下 webmachine 的 decision tree,pattern matching + 递归,非常漂亮。可惜当时我在的团队思想比较僵化,眼里只容得下 python。无奈我退而求其次,选用了 eve,一个 python 下的 rest API framework。eve 的代码质量中规中矩,平铺直叙,明显像是你我写出的代码的模样。 详细讲讲我读 webmachine 的过程。我读 webmachine,完全是 liberator 的引荐,liberator 的作者说其 decision tree 来自于 webmachine,并附了图。这时的我就像刚练了李小龙的截拳道,听闻这功夫源自咏春,一下子就心痒痒欲探探咏春的虚实了。 webmachine 的代码很短,只有 4700 行。循着文件名很快就能找到 每个 decision 的流转见下图(https://raw.githubusercontent.com/wiki/webmachine/webmachine/images/http-headers-status-v3.png):
(此图片来源于网络,如有侵权,请联系删除! ) (不要费力打了,这图微信里没法看)
这里 atom 的命名完全跟着图走,比方说 v3b13 这个 atom,含义是 v3 版本的图,b 列 13 行的 decision node。这是第一个 decision,如果 service available,则整个 flow 继续往下走,否则返回 503 service unavailable。 明白了这一点,按图索骥,代码的执行流程非常好懂。接下来的事情就很简单了 —— 顺着流程一个一个看 decision node 的代码,RFC 2616 变得鲜活起来,在你眼前跳动。我们再看一个例子:precondition check,是 v3g11:
(此图片来源于网络,如有侵权,请联系删除! )
这段代码从 http header 里读出 这个代码解释到这里,明白 HTTP 协议中 etag 作用的人,或者对 concurrency control 方案清楚的人自然一目了然;但我相信不少人会很难理解它的应用场景。再进一步解释一下:比如小明和小红是程序人生的两个管理员,他们通过 API 同时从数据库中获取到程序人生的基本信息(名称,描述等) v1。小明把程序人生的名字改成了「序员人生」,调用 PUT API 成功修改数据为 v2。小红也同时修改这个数据,但她还是使用原有的 v1 的数据进行修改,结果提交时把小明的修改覆盖了。这是个 concurrency control 的经典场景 —— 令人谈虎色变的 race condition。怎么办(想想你平时怎么处理)?HTTP 协议的处理办法是:小明和小红拿数据的时候,同时拿到一个「版本号」(你就想象成数据的 sha1),这里管它叫 etag。小明更改后,数据的 etag 变了,小红拿旧的 etag 提交时,服务器一检查当前的 etag,不匹配,于是便 412 了。这是一个简化了的 optimistic lock。 拉拉杂杂说这么多,只想说明一件事:能够读懂代码,和理解代码的应用场景是两码事。但是当你真正理解之后,你的代码功力就大涨。日后做并发环境下的共享对象更新,你脑袋里会闪个问号:吓,这里可不可以不用 lock(pessimistic lock),而是考虑类似 If-Match 的机制呢? 言归正传。之前的整个过程,我都在理解作者的意图。心满意足后,我一般会问问:
这短短五行的代码, OK,这栗子炒的时间够长了,打住。我们对比一下三个 API framework 的代码量:liberator 1.2k,webmachine 5k,eve 12k。读 liberator 的感觉像是楚辞,优美但晦涩;读 webmachine 的感觉像是数学教材,满纸都是递归推导;读 eve 的感觉像是读本科生的论文,完成了功能而已,读完没啥印象,倒是有些反面教材:整个 framework 写得太死,一些本不该 framework 做的决策被做了,以至于要满足我们的某些需求,最终只能通过 fork 它改 framework 的代码来完成,而这是 framework 的大忌(我们当时用的是 0.4,写本文时是 0.7)。 我们总结一下 —— 为了明理而阅读代码的方法并不太难:
最后一步是个非常耗时的过程,除非你有惊人的毅力,或者好为人师,要把你的心得分享出去,否则,做的动力不大。当然,在这个场景下,我们是悠然自得地读代码,没有客户老板拿着鞭子在后面抽,所以读懂之后记忆会非常深刻,即便没有电子文档留存,回到代码翻一翻,记忆就能还原了。 再次反省:我第 6 步也做得不好,有些手稿,如果不拍成照片,就永远的丢失了。
(此图片来源于网络,如有侵权,请联系删除! ) (我对 app master 的初始化的简单总结) 这样的阅读做得越多,真正搞明白的理论,知识和算法越多,你就越是不可替代的大拿。有时候,其实我们只要认认真真花上几个月,搞明白某大型项目,基本就是人中龙凤了。 场景三:为了能级跃迁而阅读代码中学物理告诉我们:原子在光的辐射下,吸收光子,可以从低能状态跳跃到高能状态,电子的轨道,或者说能级(energy level)会发生跃迁。这和哲学上常常提到的量变到质变异曲同工。作为一个程序员,你的发展历程也是这么一个过程:在工作中缓慢爬坡,到达一个平台便停滞不前,似乎股票走势上的箱体整理。然后突然有一段时间,不知道受了什么刺激(比如说野战打出了龙王神力,或者说读了程序人生*_*),突然连拉几个涨停上了另一个平台。 打破平台期,成就能级跃迁,你需要吸收合适的「光子」。这光子可以是一个开天辟地的项目(比如说 Google 的 Google Map,docker 的 docker,阿里的淘宝等),可是这样的机会并非总能被你我赶上,大多数人都是在日复一日地做些并不起眼的,只能缓缓升级的小活 —— 就像一个七级的英雄带着一群大雕,却只能天天怼大耳怪地狱恶犬狮鹫之流的野兵。这时候,与其默默沉沦,不若学庄子口中的北冥之鱼那样,沉潜浮动,积蓄能量,等待下一次抟扶摇而上九万里。 这种积蓄能量为跃迁准备的一种方式是读代码。读什么?读那些基础地不能再基础,你认为自己一辈子都不会去写的那些代码。比如 linux kernel,比如 OTP。这种读法,你不知何时能够完成,所以,要有足够的耐心和时间。检视阅读 + 主题阅读 + 思维导图是经常用到的方法。下图是 OTP 的源码我在检视阅读后,圈定的要逐渐阅读的部分,加粗的是我已经完成粗读的部分。
(此图片来源于网络,如有侵权,请联系删除! ) OTP 的代码不算少,但是耦合度非常低,其实最终是拆分成若干个场景二去阅读。我们看其代码总量:
(此图片来源于网络,如有侵权,请联系删除! ) 1.4m loc,近乎恐怖。但除去 example 和一些无关的辅助代码后:
(此图片来源于网络,如有侵权,请联系删除! ) 930k,小了约 45%。而我圈定的第一批阅读的代码,只有区区 130k 而已,不消两天可以粗读,顶多半年,可以精读。如此这般,半年后,你的水平必然非当日的吴下阿蒙了。 需要指出的是,这种阅读有时会让人非常沮丧,因为你会遇见非常非常之多的 knowledge gap,从而不得不翻书查资料弥补这些你缺失的知识点,拉慢了整个阅读理解的步伐 —— 有时甚至数日毫无进展,让你心中被激发出来的那口气开始渐渐衰竭。这时候,稳住!这些 knowledge gap 是上天馈赠的礼物,是你弥补 you don’t know what you don’t know 的绝佳机会。慢慢来,take it easy,享受获取额外知识的喜悦。 这样数年下来不断充实自己,你写代码做项目时,离余光中描绘的,令人神往的李白的「绣口一吐,就半个盛唐」的状态不远矣! 分享一个小故事,结束这篇文章: 在 Juniper 的早年,我在写 netscreen data plane 代码的总结(就是我在其他文章中提到过的被公司同事戏谑为『葵花宝典』的那份内部文档)时,因为想更加弄清楚 IPSec phase 1 SA 建立的过程,潜进了 IKE 的后花园。彼时我对 IKEv2 不甚了解,读了很多资料,然后才开始看代码。代码我看得懵懵懂懂,catcher,thrower 等奇奇怪怪的表述让我如同黄帝陷入了蚩尤的迷雾中不得方向。后来在同事的提醒下,我才知道那一堆术语都源自棒球,在 wikipedia 上搞懂了这些棒球术语的意思后,那些代码开始变得可爱起来。 几年以后,我第一次读了『如何阅读一本书』,作者用了一大段文字通过棒球的捕手的技巧来类比阅读的技巧,用捕手和投手的关系来类比读者和作者的关系,读者读者,我不禁回到了十多年前那个满是阳光的午后:我坐在宽敞的办公桌前,使用桌上的无盘 SunRay 工作站接入了一台以格林童话 Gretel and Hansel 命名的 solaris 服务器上,然后使用 vim 打开了 IKE 的建立连接,六次(不知道记忆是否还对)握手的代码,咂着香浓的咖啡,开始观看一场精彩纷呈的棒球比赛。。。 |
免责声明:本站所有文章和图片均来自用户分享和网络收集,文章和图片版权归原作者及原出处所有,仅供学习与参考,请勿用于商业用途,如果损害了您的权利,请联系网站客服处理。
本资源来自易用通,如有侵权,请联系站长。
本站为避免不必要的纷争,分享的所有资源中一切可能有版权风险的资源将全部转载自第三方网站或平台,站长只为大家提供相关资源的介绍和跳转引导。 因可能有疏忽大意,所以如有遗漏资源侵犯了您的合法权利,请联系站长删除。
【小程序源码网资源下载使用说明】:
本站所分享的一切QQ小程序源码,thinkphp整站源码,微信小程序源码,图文教程等资源仅供用户学习参考使用,任何人不得作其他用途,违者自行承担所有责任。
【小程序源码网毫无人看的介绍】:
本站又称Z站,原名贼娘网,开站于2018年,换过三任站长,目前站长是第四任站长,本站是一个主要分享免费开源小程序源码/网站源码/免费素材/教程资源的网站,主要小程序资源有用于学习的小程序源码,也有正版原创可商用的小程序源码,是一个公益博客型网站。
【小程序源码网原创源码版权申明】:
未经小程序源码网许可,任何人不得擅自使用本站原创首发源码进行商业行为(除本站VIP用户在期限内,版权无使用限制),否则将依法承担相应赔偿责任。
【小程序源码网转载文章版权申明】:
本站所转载的QQ小程序或微信小程序源码与其他资源仅供学习,任何人不得作其他用途,违者自行承担所有责任。
【小程序源码网站长最后的屁话】:
如有您认为本站有任何侵犯您合法权益的文章,或者您有什么疑问需求,欢迎联系站长QQ,站长24小时在线,备注公司名称和源码版权问题或者需要小程序定制开发等站长业务类型可急速处理,如果您只是交流小程序的一些开发问题或源码问题可以加入QQ群讨论,就不用加站长啦,对于白嫖党,QQ群才是处理问题的天堂,当然站长也欢迎大家骚扰~
小程序源码网 » 如何阅读一份代码?