//z 2012-09-26 22:24:35 [email protected][T51,L1560,R32,V428]
Zawinski:那终究只是理论。
Seibel:是的,不过有时这个理论也能成真,只要主事者有良好的判断力,框架也不是太过精致,的确能节省时间。你能讲讲自己属于哪一类吗?
Zawinski:虽然是陈词滥调,不过我还是要重提:更差就是更好(worse is better)。假定你花时间构建了完美的框架,满足了你的全部需求,从1.0版一直用到5.0版,一切都很棒;猜猜结局如何:1.0版发布用了三年时间,而你的竞争对手只用六个月就发布了他们的1.0版,结果就是你出局了。
你的竞争对手六个月就推出的1.0版,代码质量低劣,他们可能得花上两年时间重写代码,那又怎样?他们有机会重写,而你早就丢了工作。
Seibel:很多时候,也许是期限逼近,时间紧迫,你扔掉了大块代码,因为你认为另起炉灶反而更快。
Zawinski:是的,一定会碰到,那时你得赶紧脱手,避免更多损失。
Seibel:就是这一点导致开源软件开发中你深感遗憾的无休止的重写?
Zawinski:是的。但撇开效率不谈,从另一角度来看,写自己的代码远比弄清楚别人的代码来得有意思。这一切就那么自然而然地发生了。其中一点就是所有东西总是在不停重写,结果一样都没完成。如果你是那些开发人员之一,那也不错,因为总有东西折腾,当然前提是你更热衷于捣鼓计算机,而不是将其作为达成目的的手段。
以编程为乐
Seibel:说到捣鼓计算机本身,你现在是否仍以编程为乐?
Zawinski:有时。我现在净干些系统管理员的脏活,我很受不了,也从没喜欢过。我喜欢做XScreenSaver的相关开发,从某些角度来看,屏幕保护程序(即实际的显示方式而非XScreenSaver框架本身)堪称完美程序,因为它们基本上都是从头写起,养眼好看,绝无所谓的2.0版本。
Seibel:你喜欢做数学计算、求解几何和图形之类的谜题?
Zawinski:是的。以这种方式显示,这个抽象的小方程式会是什么样?或者,怎样才能让这些方块移动时更生动,不那么生硬,就像是计算机在正常搬移东西?诸如此类的问题。还有,怎么处理这些正弦波,才能让它看上去更像是在弹跳?此外,我还会写些简单蹩脚的shell脚本,聊以自用。
Seibel:除了有两百万人用上你的软件,你还以编程的哪些方面为乐?
Zawinski:这个问题有点难。我想大概是解决问题的过程。
Seibel:你感觉到代码的美吗?美感是否在可维护性之上?
Zawinski:是,当然是。任何东西只要表达恰当,不论精炼,抑或平淡,都是具有美感的。
Seibel:你认为编程和写作是类似的智力活动?
Zawinski:从某些角度看,我认为是这样。当然,编程要严格得多。但就表达思维的整体能力而言,两者非常相似。不要不着边际,说出口之前先想一下准备说什么,然后尽量言简意赅。我认为这正是编程和写文章的共同点。
优秀和差劲程序员的区别
Seibel:接下来我们聊点编程的细节。你怎么设计自己的代码?如何组织代码的结构?不妨以你最近OS X上的XScreenSaver移植工作为例讲讲。
Zawinski:好,首先我会随意鼓捣一番,写些短小的演示程序,最后不会再用。
接着,为每个X11调用创建一个空函数(stub),然后再开始慢慢逐个实现,弄清楚自己准备如何实现这个,怎么实现那个。
另外,在Mac平台上,需要编写启动代码。这有点像粗线条绘制,搭建基础框架。剩下的就是一个接一个地移植屏保护程序。
Seibel:这么说来,针对每个X11调用,你都要编写相应的实现。你有没有发现自己累积了大量非常相似的代码?
Zawinski:有,当然有。通常当你第二或第三次剪切和粘贴相近代码时,就不能再剪切和粘贴了,而应抽取成子程序。
//z 2012-09-26 22:24:35 [email protected][T51,L1560,R32,V428]
Seibel:假定再次开发规模与邮件阅读器相当的软件,你前面提到开始会写下几段文字,列出一组功能特性,这是你着手编写代码之前所做的最细粒度的准备吗?
Zawinski:是的。也许还会简略描述库和前端的差别。不过也可能不会。如果是独自一人开发,我不会关心这些,毕竟那部分我再清楚不过了。随后,我采取的第一步是自顶向下或自底向上开始。无论采用哪种方式,我都会先在屏幕上显示一个窗口,包含若干按钮,然后逐步深入,开始构建那些按钮的功能。
我发现尽早在屏幕上显示一些东西,有助于集中注意力去解决问题。这能帮我决定下一步做什么。
Seibel:你是否经常重构以保证代码内部结构的一致性?或者你一开始就很清楚如何将代码各部分整合在一起?
Zawinski:通常我一开始就很清楚。编写程序的第一个版本时,我一般会把所有代码写在一个文件里。随后,我开始分析那个文件的结构,比如有几段代码非常相似。那些代码已有上千行,何不把它们挪到另一个文件里。另外,只有真正开始编写代码,发现情况不受自己掌控,你才能体会到这点。
Seibel:我注意到优秀程序员和差劲程序员的区别之一是,优秀程序员在不同抽象层之间切换自如,游刃有余,修改时仍能保证各层独立,并且选择最合适的那一层进行修改。
Zawinski:显然决定在哪里进行修改很有讲究,而且可能关系非常大。
对我而言,我认为最重要的一点是在构建全新的程序时,应当想办法尽快写好自己能用的程序,哪怕只实现一点功能。这样你就能真正知道下一步做什么。
调试经验谈
Seibel:下面,我们再谈谈调试相关的话题。对初学者而言,你推荐哪个调试工具?打印语句?符号调试器?形式证法证明程序正确性?
Zawinski:过去这些年里变化太大。当初使用Lisp机器时,我做了一些修改,基本上Lisp侦听器(listener)就成了检查器,只要它打印出对象,就会出现一个上下文菜单,你可以点击菜单项,返回指定的值。这么一来,很容易跟踪一组相关对象和类似的东西。这就是我早期思考问题的方式。深入代码内部,来回修改,不断试验。
后来,我开始编写Emacs内部的C代码,使用GDB时,我试图沿用同样的方式。不过一直没有取得很好的效果。随着时间的推移,我渐渐地不再使用这类工具,而是直接插入打印语句,重新运行程序。如此反复,直到搞定为止。如今,人们似乎不清楚何谓调试器,大多数人都用打印语句。
Seibel:这其中有多少是因Lisp和C语言的区别而非工具的区别所致?一处区别是在Lisp里你可以测试一小部分,调用你不确定工作正常与否的小函数,然后中断函数执行,检查运行状态。而C代码呢,复杂之极,必须运行整个程序,然后在某个地方设置断点。
Zawinski:与C语言相比,Lisp这类语言本身更适合调试。另外Perl、Python和类似语言在这方面也更具Lisp的特质,不过我还没看到多少人真的按照Lisp的方式去做。
Seibel:你们是否使用断言,或其他比较正式的文档编写方式或真正检查不变量的方式?
Zawinski:显然,插入断言语句对调试而言是个好主意,如你所说,还有利于文档化。但随之而来的问题是,在产品代码中,断言失败时会怎么样?你会怎么做?我们商定的做法是返回零,好让它继续运行下去。
许多程序员都有种本能:“我必须呈现错误消息。”不,根本不用。没人会在意那个。
Seibel:你会为了调试而逐句查看程序,或者,如有些人建议的那样,写好程序之后,把逐句查看作为检查代码的方法?
Zawinski:不会。只有调试程序时,我才单步查看代码。有时是为了确认代码写得没问题。但很少这么做。
Seibel:那你是怎么调试代码的?
Zawinski:我会先审读代码。然后插入一些代码,尝试解决存在的问题。总之视情况而定,很难一概而论。
Seibel:与调试相关的,是测试。在Netscape,你们有专门的QA组,还是你们自己测试所有项目?
Zawinski:两个都有。我们会一直运行软件,那是最好的一线QA。另外我们有个QA组,他们会从头到尾做详细的正式测试。
Seibel:那么开发人员那一级的测试呢,比如单元测试?
Zawinski:没有,我们从来不做那类测试。
软件维护
Seibel:说到这个,正好聊聊软件维护相关的话题。你怎样设法理解别人的代码?
Zawinski:我会直接一头扎进去,开始阅读代码。
Seibel:那么你会从哪里开始呢?从第一页开始,按顺序读下去?
Zawinski:有时会这么做。更常见的是学习如何使用某个新的库或工具包。幸运的话,你能找到一些文档,还有API。最后弄清楚自己可能会用到的那部分,或者弄懂它是怎么实现的。按这个思路一直做。或者,对于诸如Emacs的程序,有可能从底层着手。基本上就是不断抽取,直到现出骨架。
Seibel:我们讨论过重写为何比修正更有趣,但重写并不总是好主意。我想知道你怎么拿捏两者之间的界限?
Zawinski:一开始我只是修正缺陷,并试着做些优化,结果原有代码不见踪影,后来几乎变成了重写。
当然,我在Lucid Emacs里添加的许多东西不像重写字节码编译器那样有充足的理由。实际上,我做许多东西的动机在于让它更像Lisp机器,更像我熟悉的Emacs,而那其实是我熟悉的Lisp环境。于是我添加了大量东西,设法让Emacs在许多方面不再是半吊子的Lisp,比如应该用事件对象取代包含数字的链表。现在回想起来,那些修改当属最大的问题。那类修改导致与第三方库的兼容性问题。
Seibel:当然,那时你不知道会出现两个Emacs。
Zawinski:的确。不过即使没有XEmacs,也不会只有一个Emacs,也无法避免兼容性问题。事后看来,如果我早意识到那些修改有那么大的影响,我也许会采取不同的做法。或者多花些时间,让原来的方式也能工作。
Seibel:代码可读性事关维护,前面你谈到一些编写易读代码的做法,有哪些特性可以让代码更易读?
Zawinski:嗯,显然是注释。写下期望是什么,实际又做了什么。如果是创建数据结构,那就描述其成员布局。
Seibel:那结构呢,你是以自顶而下还是自底向上的方式组织自己的代码?
Zawinski:通常我会把叶节点放在文件顶部,尽力保持那种基本结构。然后,通常是在顶部之上,编写API注释说明。
Seibel:那么假定为了进一步论证,你准备重新出来工作,组建一个开发团队。你会怎么组织安排?
Zawinski:在我看来,最好的安排是一个团队不超过三个或四个人,成员之间紧密合作,每天一起共事。这种做法可以按比例放大很多倍。
//z 2012-09-26 22:24:35 [email protected][T51,L1560,R32,V428]
Seibel:怎么协调这些团队呢?
Zawinski:我们会约定好各模块之间的接口。就我的理解,要让模块之间交互自如,最佳做法就是保持模块本身真正简单,减少可能出错的途径。
至于怎么划分则完全视项目而定。
Seibel:这么说来,在Netscape,你们会把事情分割开来,每个人负责软件不同的模块。有的人认为那么做很重要,其他人则认为团队共同拥有所有代码的做法更好,你怎么看?
Zawinski:两种方法我都用过,各有优势。让每个人都拥有全部代码,我认为不切实际,因为代码实在太多。
Seibel:当还是资历尚浅的程序员时,你导师做了哪些对你有帮助的事情?
Zawinski:我觉得关键在于他们能意识到什么时候该提升员工的等级。
Seibel:你阅读代码主要是因为你正在开发相关功能,抑或你只是想探究它是怎么工作的?
Zawinski:只是到处看看,我想知道那是怎么工作的。把东西拆开的冲动是将人们带入这一行当的一大原因。
对自学的程序员的建议
Seibel:你觉得自己自学了计算机科学,抑或只是学习编程?
Zawinski:嗯,这些年我的确学了些计算机科学,但目标是学习编程。让机器做事情是目标,计算机科学则是达成目标的手段。
Seibel:你是否认为那是种缺失,有没有想过但愿自己曾以更系统的方式学习过计算机科学?
Zawinski:有时的确会有那种想法,特别是早期,我会想:“天哪,我什么都不懂。”这只会叫人窘迫,还会让人缺乏安全感。要是多花点时间在求学上,我的生活的确会全然不同,不过那时我做了我该做的。
Seibel:你有过相反的感觉吗,觉得身边的计算机科学家并不像自己那样懂得编程的真谛?
Zawinski:我经常有那种感觉,不过,“哇,你们这些家伙搞错对象了。”这种想法其实并不多,更多是觉得:“哇,我们只是兴趣不同。”
Seibel:你主要是靠自学的。对那些自学的程序员,你有什么建议?
Zawinski:我不知怎么就跌跌撞撞做了程序员。我当时做的一些决定,导致了其他决定,最终成了现在的我。
我不时收到邮件,内容大体是“我想成为程序员,我该怎么做?”或是“我该不该上大学?”我怎么回答得上?要是在1986年问我,我也许还有不错的答案。现在人们已不可能循着我当年的路走,因为那条路已经无迹可寻。
Seibel:计算机方面的书呢?哪些计算机科学或编程书籍是必读的?
Zawinski:老实说,计算机书我读得并不多。我一直都推荐的书是《计算机程序的构造和解释》(Structure and Interpretation of Computer Programs),许多人都怕读这本书,因为Lisp味太浓。不过,这本书在不教语言的前提下教授编程,做得非常到位。
还有一本书,是关于调试的,微软员工写的【译者注:疑为微软出版社1993年出版,Stephen A. Maguire的Writing Solid Code - Microsoft Techniques for Developing Bug-free C Programs,图灵影印版《编程精粹:编写高质量C语言代码》】,主要介绍如何有效使用断言。还有本书叫《设计模式》,人人追捧,称其为当时最好的作品,不过在我看来,这本书一派胡言。
捣鼓折腾很重要
Seibel:有程序员必须具备的关键技能吗?
Zawinski:嗯,好奇心,把东西大卸八块的好奇心。好奇心是获取知识的主要途径。把东西拆开,用心研习,才能做好自己的东西。至少我是这么做的。计算机的书我读得很少。我的经验主要来自不断挖掘源代码和参考手册。
Seibel:你读过Knuth的《计算机程序设计艺术》【编者注:即将由人民邮电出版社出版】(The Art Of Computer Programming)吗?
Zawinski:没读过。这也许是我真正该读的一本书。
Seibel:这本书很难读,需要很好的数学功底才能真正读懂。
Zawinski:数学我可不在行。
Seibel:很多程序员都有数学背景,大量计算机科学理论都离不开数学。这么看来,数学也并非不可或缺,你就是很好的例子。想成为优秀程序员得具备多少数学知识或数学化的思维?
Zawinski:嗯,那要看你怎么划分,什么算是数学的,什么不算。
不过,我还不至于认为做程序员不需要数学。但是,我总觉得比起数学,编程与写文章有更多共通之处。这就如同你正在写故事,尝试向非常愚钝的人——词汇有限的计算机——表述观念。你已经掌握自己想表达的观念,以及用于表达的那些工具,你会使用哪些词,序论和总结陈述会写成什么样?编程也大抵如此。
谈到文章,口味问题就凸显出来了。用一段文字描述某样东西,可以描述得体,也可以描述出彩,很有特色。这些同样适用于程序。程序可以只是完成任务,或者,只要组合得好,还能做到容易理解。
Seibel:为什么口味很重要?只是为了让自己满意,还是从实践角度来说优美的代码更好?
Zawinski:很大程度上,优美和易维护是相似的,或者说息息相关。
//z 2012-09-26 22:24:35 [email protected][T51,L1560,R32,V428]
Seibel:你是否认为,如今在编程上能获得成功的人已不同往常?
Zawinski:当然,现在已不太可能从无到有编写没有任何依赖的程序。
Seibel:要是你现在只有13岁,看到现在编程的方式,你还会被吸引吗?
Zawinski:这太难回答了。我不认识13岁大的孩子,也不知道当下的世界变成什么样了。
我觉得正是折腾捣鼓,比如拆开磁带仓背部,查看齿轮是怎么啮合在一起的,这种探索吸引我走上了编程之路。除了乐高机器人(LEGO Mindstorms),在我看来,现在人们已经没什么机会循着我当年那条路成长。不过,也许我是错的。
Seibel:另一方面,编程本身也变得更容易。只是让计算机做些常规任务的话,你根本不必一开始就掌握晦涩复杂的汇编语言。
Zawinski:没错。我觉得今天的孩子想要编程可以从搭建Web应用或编写Facebook插件等开始。搭建LiveJournal的Brad Fitzpatrick是我朋友。当初写LiveJournal时,他只是随便玩玩,写了个Perl脚本,以便他和朋友可以用它留言:“我准备吃午饭去了。”他开始的方式就是写个简短的Perl脚本,然后放在Web服务器上。这种现象也许会愈演愈烈。
//z 2012-09-26 22:24:35 [email protected][T51,L1560,R32,V428]