我在最近两个工作过的公司(Etsy和Twitter)成立过代码阅读小组,一些人向我征询关于阅读代码以及运作代码阅读小组的建议。想说的太多,一言以蔽之:不要成立代码阅读小组。你应该去成立小组但不是我稍后提到的那样,而在此之前,我要解释一下我是如何得出目前观点的。
作为一位曾读英语专业的业余作者,我以往总是被这样的想法所吸引:代码就像文学作品,我们应该像通过阅读优秀范文学写英文那样来学写代码。当然,持这种观点的不止我一个人。Donald Knuth(著名计算机科学家,译者注),除了致力开发TeX软件和编著《计算机程序设计艺术》,一直以来是被他称作“文学编程”的提倡者,并已经把他的几个大型程序出版成书。
另一方面,早在我加入Etsy并成立第一个代码阅读小组之前,手头上就已经有一些迹象本就表明:这是看待代码的一个错误方式。
首先,当我在编写关于与程序员访谈的《编程人生》(书中记录与15位世界级编程大师的对话,译者注)一书时,我几乎问了每个人关于阅读代码的问题。大部分人说阅读代码是重要的且程序员应该多读,然而当我问到他们最近读了哪些代码时,很少有人有好的答案。在他们还是年轻黑客时,其中有些人认真地读过一些代码,然而几乎没人保有阅读代码的习惯。Knuth作为伟大的计算机科学集大成者,阅读了大量的代码;Brad Fitzpatrick谈到出于恶作剧而阅读过一些开源代码片段。但他们俩是例外。
如果这还不够,那么在完成《编程人生》之后,我获得一次采访Hal Abelson的机会。Hal Abelson是麻省理工大学的著名教授,《计算机程序的构造和解释》的合著者。当我首次跟他谈论,问及之前那样的阅读代码问题时,他给出了普遍的答案:阅读代码是重要的且程序员应该多读。但他也没有说出任何最近读过的代码,除了不得不阅读的代码之外,包括在谷歌休公假时审查同事的代码以及给麻省理工学生评分时的代码。后来我问他关于这种矛盾:
引用
Seibel: 我对人们所说的和实际所做的之间的这种分裂很好奇。每个人说“程序员应该阅读代码”,但似乎少数人真正去做了。我会感到惊讶:如果我采访小说家们并问他们最近读了哪本小说,他们回答说“哦,自从读研究生后,我就没真正地去读一本小说了”。作家们实际上会读其他作者的书,但程序员不会真正得去读其他人的代码,尽管他们说应该去读。
Abelson: 是的。你是对的。但记住,很多时候,你增删查改一个程序最终使之能够运行并完成你需要它做的所有事情,所以就有很多与核心思想无关紧要的东西。
Seibel: 所以基本上你最终是说,大多数代码不值得一读?
Abelson: 或者说程序从一个初始计划或某种伪代码构建起来。书中的许多代码,是一些净化过的版本,不具备使程序运行起来的所有东西。
Seibel: 我想起了《计算机程序的构造和解释》的前言,里面写道“程序应该是为了供人读才写的,然后顺便让机器执行了一下。”然而正好你所描述的事实在实际中却是,大多数程序都是让机器来执行才写的,如果有的话,只顺便供人阅读。
Abelson: 嗯,我认为他们一开始是为了供人阅读的,因为里面包含着一些想法。你解释的东西,仅有一小部分存在我们的书中。书中有一些相当重要的程序。而且部分原因是我们认为解释它做什么的最简单方式就是表达在代码中。
然而即使明显知道大部分真正的代码实际上不是采用人容易读懂的方式去编写的,但这不足以让我在Etsy成立小组时放弃这种文学研讨班模式。由于大多数Etsy开发者熟悉Javascript以及我知道Jeremy Ashkenas对可读性代码的编写有浓厚兴趣,所以在第一次代码阅读小组会时,我选取了Jeremy的backbone.js。我仍计划了一些像文学研讨会一样的流程,但我发现许多人不会预习代码(嗯,这点跟文学研讨班倒是一样)。所以我决定在小组讨论环节之前演示要讨论的代码。
当准备我的演示文档时,我发现自己掉进了惯常模式:每当试图真正理解或心领神会一段代码时,我不得不从根本上重写它。为了更好理解,我将开始重命名一些名称,接着顺着我组织代码思路去移动一些语句。很快,我就已经开始深入代码抽象化(或具体化),并开始对更大程度地重组代码结构。一旦完成代码重写,我通常已很好地领悟了,甚至可以溯源并理解初始版本。我经常觉得这种阅读代码的方式不好,但它是迄今为止我理解代码的唯一方式。
在代码阅读小组演示时,我以初始的backbone.js作为开始,然后在自己想法的指引下,一步一步演变使之更易懂。当我问大家是否应该转向小组讨论环节时,但似乎没有人很感兴趣。好在看到我的重构让组员对原始代码的底层结构有了与之前我通过重构获得的相同见解。
Etsy的第二次代码阅读小组会由Avi Bryant主持,展示了如何利用SmallTalk的代码浏览功能来查看一些代码。因为在Etsy少数工程师跟Smalltalk打过交道,所以我们对组员会预习代码这件事不抱任何希望。但这次演示,对组员来说是一次领略SmallTalk魅力的绝佳机会,也让有我有机会发难Avi关于Smalltalk和Lisp的区别。
当我来到Twitter,文学研讨会模式仍莫名其妙地盘旋在脑海中,尽管在Etsy的两次看起来备受青睐的小组会几乎没有遵循这种模式。当我发邮件邀请twitter的工程师参加代码阅读小组时,回应相当热情。再次,第一次小组会由Marius Eriksen演示了一段代码。在这个示例中,展示了Scala语言实现的Future包的内部构件。这个构件被用于Twitter的很多服务,其大部分由Eriksen本人编写。
在演示了一段时间后,我终于明白一个浅显的道理:代码不是文学作品。我们不去阅读代码,而是解译、审查它。一段代码不是文学片段,而是样本。当我问Knuth关于他自己阅读代码时,他回答的其实早就给我指明了这个方向:
引用
Knuth: 但建立在大脑的东西是真正有价值的。那么我怎么利用它的呢?曾经有台型号为Bunker Ramo 300的机器,有人告诉我这台机器的Fortran编译器真的是不可思议的快,但没有人知道它是怎么做到的。我得到了编译器源代码的副本。因为我没有机器的说明书,所以甚至连机器语言是什么都不知道。
但是我把它当成是一项有趣的挑战。我能搞清楚BEGIN,然后我就开始解码。这些指令码对应着双字指令助记符,所以我开始合计着“这个可能是装载指令,而这个可能是分支”。接着我知道它是Fortran编译器,所以到一定程度后,看着卡片的第七列就能分辨是不是注释。
三个小时后,我已经弄懂机器的一小部分。接着我碰到了重大问题——跳转表。因此这是一个难题,之后我坚持画了些图表,就像我在某安全局试图解译一个密码一样。但我知道它是个运行很快的Fortran编译器,在某种意义来说这不是加密而是有意地掩盖;因为弄不到机器的说明书,所以答案就藏在代码中。
最终我弄明白了为什么这个编译器如此快。不幸的是,它并不是因为使用了卓越的算法,仅仅是因为他们采用非结构化编程并且最大限度地优化了代码。
画图表、获取更多一点信息并作假设仅是你基本上解决某种难题的方法。通常,当我阅读技术文件时,是同样的挑战。我试图理解作者的思想,尝试搞懂这个概念是什么。我认为,你试着去读别人的东西越多,未来你更能创造属于自己的东西。
他没有在描述文学作品的阅读,而在描述一个科学性调查。所以现在我对人们应该如何一起从代码中获得深刻见解这个问题有了新答案,正如我向Twitter代码阅读小组解释的一样:
引用
在准备与一起编程的女儿们即将进行的对话时,我开始思考告诉她们一些关于代码阅读以及应该读什么代码的事情。我再次突然想到了那些所有口惠而实不至的代码阅读想法,绝大部分程序员不会真正地阅读大量代码,至少不是纯粹为了阅读代码而读。这有一个简单检验方法:说出一段你读过的代码并且肯定大部分优秀程序员会去读或者至少听说过。不多,对不对?可能一行都没有。
但我想到代码不是文学作品,我们不是读者。更确切地说,有趣的代码片段是样本,我们是博物学家。所以我认为更好的方式是:我们其中的一位扮演着一个刚从异国回来的19世纪博物学家在国内科学界引发关于他们所发现的一只新型昆虫的议论:“看这怪物的触须!这些触须看起来非常笨拙,然而该物种的雄性可利用它们杀死小青蛙,然后雌性将卵产在其尸体中”,而不是像一群学比较文学的研究生那样挑出一段代码并去阅读、讨论它。
这种演示的关键在于介绍者要选一段自己深刻理解的代码,并通过从进化碎屑层(亦为临时补救方案–指快速有效但丑陋的方案,译者注)中指出核心思想,有助于听众理解。一种合理的方式应该是展示真正的源码并剥离、重写关键部分,就像一个生物学家去染色样本,使不同特征更容易辨别一样。
典型的演示应面向所有程序员,或男或女,或聪明或一般,但必须对与代码出处有关的任何特定知识不作要求。为了让组员理解代码,演示文档应该提供足够的上下文,解释对一般水平程序员而言比较晦涩的实现语言的任何细节。
由于我的顿悟,我们采用新模式,已成功举行了几次代码阅读小组会议,现在被称为Twitter的旨在提高编码知识的英国皇家学会。我们仍在摸索讲演代码的最佳方法,尽管目前方法感觉很不错。另外,我不再觉得我这种解剖式阅读代码的方式是糟糕的。
至今最大的教训是,代码是非常稠密的。半小时的报告只够用来呈现大约十二行耐人寻味的代码和一个主要思想。几乎可以肯定的是:报告人必须真正深入某段代码,要比任何人有更深的理解。除此之外,一个优秀的演示至少可让组员接触到的核心思想,对于决定自己去阅读代码的人们来说,可能是一个好的开端。
原文链接: Peter Seibel 翻译: 伯乐在线 - heloowird
译文链接: http://blog.jobbole.com/64548/