说实话,以前读源代码对我来说就是个地狱笑话。 公司业务代码都堆成山了,谁有闲心去读开源库内部到底干了啥。 但前年夏天,线上Node服务半夜报警,CPU飙到100%,重启也没用。 看进程和日志就像大海捞针,被逼无奈只能顺着调用链一层一层查下去。 最后发现是缓存库的一个过期清理模块在高并发下老是把弱引用对象往内存里塞,具体是node_modules/cache-manager的一个旧版本。 当时我拿着heap snapshot对比了几轮,每次都多了几十MB,后来在retainers里看到了内部Map的key才定位。 从那时候起,我再也不敢说“读源码没用”这种屁话了。
后来我慢慢摸到点门道:读源代码跟写代码完全是两回事。 写代码的时候你是掌握全图的设计者,读别人的代码感觉就是在考古,拿小刷子蹲在墓坑里刷碎陶片。 你只能从断点里摸线索,从调用栈里猜原因。 代码不会告诉你写的人当时在想啥,文档是假的,注释是编的,只有逻辑永远说真话。 我开始学会先跑起来再说,别学网上那些人捧着源码干瞪眼。 用node --inspect开调试器,或者往关键路径塞打印,效率翻倍。 我那次排查内存泄漏就是靠Chrome DevTools的Memory,先抓一个baseline,然后压测三十秒,再抓,连续三次,看对象分配数。 具体数字不重要,重要的是你能找出持续膨胀的那个构造函数,再去点它的分配栈,这才算扒到了代码的尸骨。
我还有一个比较反直觉的观点:别把源代码当成书来读。 书是从头看到尾,读代码得当成狼人杀来玩。 你得先判断谁可能是凶手,通常就是第三方包或框架里的某个模块。 然后用IDE的Find All Locations和Call Hierarchy顺藤摸瓜。 比如读那种大型库,从package.json主入口进去根本看不到边。 之前我调试一个Markdown解析库,转HTML特别慢,用Node自带的profiler跑一轮,98%时间耗在一个巨大正则上。 翻源码是找不到原因的,要让CPU告诉你热点在哪。 所以与其琢磨怎么“读懂源码”,不如先学着用工具定位代码里的麻烦。
至于怎么练习,我的土办法是改bug。 找一个你用着不错的库,去GitHub issues里翻别人报的bug,挑一个能复现的,然后试着修。 这比看一百篇源码分析有用。 我上次就是这么摸清楚了axios某个版本的interceptor执行顺序,修的时候发现官方注释跟实际行为不一致,顺手提了个PR。 虽然没被合并,但那个过程让我理解了状态机的实现。 还有,读代码时一定要做笔记,在空白处画调用链,用卡片记下每个模块的职责和坑。 这笔记平时看不出用,关键时刻能救命。 话说回来,不同语言的源码套路差异太大。C要关注内存,Java喜欢一堆抽象类,JS生态则狂野得很,有的库手写,有的库直接给你生成压缩过三万行的代码。 别想一招鲜,要按项目调整工具链。
说到底,阅读源码不是什么高大上的天赋,更多时候是一回生二回熟。 我也不信有人能毫无目的地读完整个开源项目。 完整的源码像时间的化石,你只能通过挖掘某一层来找答案。 我有时候会焦虑,开了个源码阅读计划,几天就忘了前面。 后来我想明白了,读代码不是用来背的,是用来“遭遇”的。 遇到问题,带着问题进场,解决问题之后自然形成记忆。 最后给个直接能上手的清单:第一,遇到bug先用工具定位,strace、inspect、perf都行,别干瞅;第二,从调用栈的入口进源码,不要尝试从main函数开始;第三,利用对象方法名推生命周期;第四,做一个最小复现用例,再把源码文件复制一份加日志。 这些都是我踩坑踩出来的土办法,希望你之后能少摔几次。