挺多人问过我“如何阅读已有代码”这个问题,希望我能有一个好的方法。有些人希望通过阅读“优质项目”(比如 Linux 内核)得到启发,改进自己的代码质量。对于这个问题,我一般都不好回答,因为我很少从阅读别人的代码得到提升。每一次阅读别人的代码,对我来说都是一种折磨,而且每一次都不是别人的代码提升了我,而是我提升了别人的代码。
比起阅读代码,我更喜欢看别人的文章或者书。我喜欢他们跟我面对面的交流,用简单的自然语言或者画图解释他们的思想。有了思想,我自然知道如何把它变成代码,而且是优雅的代码。每一次参加学术会议,我都发现自己几乎无法理解会议上的 talk 或者 paper。我会在 talk 结束之后的喝茶时间走到演讲者面前,对他说:“你的 talk 很有意思,你能在三句话之内总结一下你说了什么吗?” 这样交流之后,我忽然就懂了。
如果有同事请我帮他改进代码,我不会拿起代码埋头看,因为我知道看代码往往是没用的。我会让他们先在白板上给我解释那些代码是什么意思。我的同事们都发现,把我讲明白是很困难的。因为我的要求非常高,只要有一点不明白,我就会让他们重新讲。还得画图,我会让他们反复改进画出来的图,直到我能一眼看明白为止。如果图形是 3D 的,我会让他们给我压缩成 2D 的,理解了之后再“推广”到 3D。我无法理解复杂的,高维度的概念,他们必须把它给我变得很简单。
所以跟我讲代码总是需要费很多时间,但这是值得的,因为我明白了之后,往往能挖出其他人都难以看清楚的要点。给我讲解事情,也能提升他们自己的思维和语言能力,帮助他们简化思想,甚至在忽然间发现改进他们自己代码的方法。很多时候我根本没看代码,通过给我讲解,后来他们自己就把代码给简化了。节省了我的脑力和视力,他们也得到了提高。
我最近一次看别人的代码是在 Intel,我们改了 PyTorch 的代码。那不是一次愉悦的经历,因为虽然很多人觉得 PyTorch “好用”,它内部的代码却是非常晦涩,难以理解的。PyTorch 不是 Intel 自己的东西,所以没有人可以给我讲。修改 PyTorch 代码,增加新功能的时候,我发现很难从代码本身看明白应该改哪里。后来我发现,原因在于 PyTorch 的编译构架里自动生成了很多代码,导致你无法理解那些代码是怎么来的。
比如他们有好几个自己设计的文件格式,里面有一些特殊的文本,决定了如何在编译时生成代码。你得理解这些文件里面的内容在说什么,而那不是任何已知的语言。这些文本文件被一些 Python 脚本读进去,吐出来一些奇怪的 C++,CUDA,或者 Python 代码。这其实是一种 DSL。我已经在之前的文章中解释过 DSL 带来的问题。所以要往 PyTorch 里面加功能,你就得理解这些脚本是如何处理这些文本文件,生成代码。而这些脚本写得也比较混乱和草率,所以就是头痛2,头痛的平方。
最后我发现,根本没有办法完全依靠这些代码本身来理解它。那么怎么解决这个问题呢?幸好,网络上有 PyTorch 的内部工程师写了篇 blog,解释 PyTorch 如何组织代码。Blog 的作者 E. Z. Yang 我见过一面,是在一次 PL 学术会议上。他当时在 MIT 读书,貌似一个挺聪明的小伙子。不过看了这 blog 也只能初步知道它做了什么,应该碰大概哪些文件,而这些每天都可能变化。
这篇 blog 还提到,某几个目录里面是历史遗留代码,如果你不知道那是什么,那么请不要碰!看看那几个目录,里面都是一些利用 C 语言的宏处理生成代码的模板,而它使用 C 语言宏的方式还跟普通的用法不一样。在我看来,所谓“宏”(macro)和 metaprogramming 本身就是一个巨大的误区,而 PyTorch 对宏的用法还如此奇怪,自作聪明。
你以为看了这篇 blog 就能理解 PyTorch 代码了吗?不,仍然是每天各种碰壁。大量的经验都来自折腾,碰壁,在黑暗中摸索。多个人同时在进行这些事情,然后分享自己的经验。讨论会内容经常是:“我发现要做这个,得在这个文件里加这个,然后在那个文件里加那个…… 然后好像就行了。” 下次开会又有人说:“我发现不是像你说的那样,还得改这里和这里,而那里不是关键……” 许多的知其然不知其所以然,盲人摸象,因为“所以然”已经被 PyTorch 原来的作者们掩盖在一堆堆混乱的 DSL 下面了。
所以我从 PyTorch 的代码里面学到了什么呢?什么都没有。我只看到各种软件开发的误区在反复上演。如果他们在早期得到我的建议,根本不可能把代码组织成这种样子,不可能有这么多的宏处理,代码生成,DSL。PyTorch 之类的深度学习框架,本质上是某种简单编程语言的解释器,只不过这些语言写出来的函数可以求导而已。而写解释器是我最在行的事情。
那么我是怎么成为现在这个样子的呢?肯定有某种方法,对吧。我的方法很简单,写最短最精巧的代码,从最薄最精悍的书或者课程里面学,从真正的大师那里学,而不是大型开源项目。
造就我今天的编程能力和洞察力的,不是几百万行的大型项目,而是小到几行,几十行之短的练习。不要小看了这些短小的代码,它们就是编程最精髓的东西。反反复复琢磨这些短小的代码,不断改进和提炼里面的结构,磨砺自己的思维。逐渐的,你的认识水平就超越了所有这些几百万行,让人头痛的项目。
很多人都不知道,有一天我用不到一百行 Scheme 代码就写出了一个“深度学习框架”,它其实是一个小的编程语言。虽然没有性能可言,没有任何 GPU 加速,功能也不完善,但它抓住了 PyTorch 等大型框架的本质——用这个语言写出来的函数能自动求导。这种洞察力才是最关键的东西,只要抓住了关键,细节都可以在需要的时候琢磨出来。几十行代码反复琢磨,往往能帮助你看透上百万行的项目里隐藏的秘密。
所以我如何阅读别人的代码呢?Don’t。除非我真的要使用那个项目的代码,我才会去折腾它。