Jamie Zawinski访谈:C++之恶

Seibel:你是怎么开始学习编程的?

Zawinski:哇,很久以前的事了,都快没什么印象了。没记错的话,我第一次真正使用计算机编程大概是在八年级。当时学校里有几台TRS-80,我们边玩边学了点BASIC。我记不清是不是专门开了门课,好像只是课后摆弄过。我记得那些机器没法保存程序,只能照着杂志或手册什么的,将程序逐行敲进去。当时我看了很多书。书中讲到的计算机语言,我没办法实际运行,只好在纸上编写那门语言的程序。

Seibel:后来你是怎么开始接触Lisp的?

Zawinski:我看了许多科幻小说,觉得人工智能实在太迷人了,计算机将统治世界。为此我学了点人工智能。我高中时有个朋友叫Dan Zigmond,当时我们俩互相换书看,于是一起学习Lisp。有一次,他去参加Apple用户协会(Apple Users Group)在卡耐基•梅隆大学举办的活动,这其实就是大家聚在一起交换软件,而我朋友只是想搞点免费的东西。另外,他还找了个大学生模样的人搭话,那个大学生说:“喂,大伙来看,这里有个15岁的孩子会Lisp,真是少见。你该去找Scott Fahlman要份活干。”Dan真的照做了。而Fahlman还真给了他一份活。Dan又说:“对了,我有个朋友你也一起要了吧。”他指的就是我。Fahlman就那么雇了我们。我猜他大概是这么想的,哇哦,有两个高中生居然对这东西感兴趣,让他们在实验室里晃荡也不会有什么大碍。于是我们开始做些简单的活,比如用新版编译器重新编译整套代码。整个过程真叫人难忘。你可以想象,只有我和Dan两个是小孩,其他都是研究语言和人工智能的研究生。

Seibel:高中毕业后,你很自然地就去了CMU。

Zawinski:是的。事情是这样的,我讨厌高中,那是我生命中最糟糕的日子。快毕业时,我去找Fahlman要一份全职工作,他回答说:“不大好办,不过我有几个朋友刚开了家公司,找他们谈谈。”那家公司名叫Expert Technologies,也就是ETI。他们正在打造一个专家系统,可以自动给电话簿标页码。他们使用Lisp开发,我认识其中几个人,之前都在Fahlman的小组里待过。他们雇了我,一切顺风顺水,约莫过了一年,我开始惶恐不安:哦,天哪,得到这两份工作完全是撞大运,绝不会有下次了。一旦丢了这份工作,没有大学文凭的话,我就只能去打打零工了,看来我应该去拿张文凭。

那段时间真的很糟。上高中时,所有人都抱怨:“净是些没完没了的老掉牙的标准化测试,上了大学,一切都会好起来的。”结果上大学第一年,与高中毫无区别,“放心,等你念了研究生,都会好起来的。”大学和高中一样糟糕,换了时间而已,我可受不了。每天早上8点钟起床,就开始往脑子里塞东西。那门叫做课程介绍的课还非上不可,这门课教你怎么用鼠标。我找到他们说:“我都在这所大学里工作了一年半,我知道鼠标怎么用。”但所有人都得上,概莫能外,“这是规定”。其他也都差不多,我实在无法忍受,索性退学了事。我觉得自己做得很对。

我在ETI干了大概4年,后来公司开始走下坡路。那时我已经写了不少代码,便找了个新闻组,发帖子找工作,还顺带提到自己写过不少代码。Peter Norvig(注:Google研发总监,《十年编程无师自通》作者)看到帖子后安排了面试。

Seibel:Norvig当时在伯克利?

Zawinski:是啊。那份工作很奇特。他们有一大群研究生在做自然语言理解方面的研究,他们基本上都是语言学家,只做一些编程。因此他们打算找个人接手他们自己编写的那些零零碎碎的代码,并整合成真正能用的东西。

这活相当困难,因为我没有相关背景,无法理解他们到底在做些什么。因此常常碰到这样的情形:我盯着某样东西,但完全不知所措。我不理解那是什么意思,也不知道下一步该做什么,我读些什么才能真正理解它。于是我跑去问Peter。他很礼貌地回应我:“你现在理解不了,这很正常,周二我有时间,到时给你讲解一下。”结果我就无所事事。于是我把大块时间都用在折腾窗口系统,摆弄屏幕保护程序,以及之前出于好玩而捣鼓的那些用户界面相关程序。

就这么过了6个或8个月,我觉得自己完全是在虚掷光阴。我什么都没为他们做,只觉得自己像在度假。

最后我去了Lucid,当时仅存的两家Lisp环境开发商之一。我决定离开伯克利的主要原因是我觉得自己一事无成,那种感觉很糟。我周围的都不是程序员。当然他们都不赖,我仍和其中几个人保持着友谊。与解决实际问题相比,他们对抽象的事物感兴趣得多。而我想有所成,有一天能指着某样东西说:“瞧,这个漂亮的活是我干的。”

Seibel:你在Lucid的工作成果是XEmacs,不过,你去那里一开始就做Lisp方面的开发?

Zawinski:当然,我一开始做的一个项目,我都记不起是什么机器了,不过我确定那是台16个处理器的并行计算机,我们使用的Lucid Common Lisp变体提供了几个控制结构,可以创建进程,分别部署到不同的处理器上。

我做了一部分后端优化工作,减少创建线程的开销,以便完成一些有用的计算,比如实现并行Fibonacci算法,从而不再只忙于为每个线程新建栈组(stack group)。我全身心投入其中。那是我第一次有机会使用那么奇特的机器。

这之前我还负责把Lisp迁移到新机器上。大致过程是,有人已经针对新架构写好了编译器后端,并且已编译好自举代码。首先我拿到一个二进制文件,据称是在这台机器上可执行的代码;接着我必须剖析它们的装载器格式,以便写个简单的C程序,装载拿到的文件,将页面置为可执行,并跳转到那个页面。幸运的话,你就会看到Lisp提示符,之后即可开始手工装载其他东西。

这对任何架构而言都是件难事,因为装载器几乎没有真正的文档说明。你只能找个C程序编译一把,然后逐个字节分析,用Emacs编辑字节。看看把这个字节改成零有什么结果,它会不会停止运行。

厌恶C++

Seibel:这么说来你一开始就从事Lisp方面的工作。不过,显然你的整个职业生涯并不是只有Lisp,下一门语言是什么?

Zawinski:嗯,在Lisp之后,我正经用来编程的下一门语言是C,感觉像是回到了在Apple II上编程时用过的汇编语言。PDP-11汇编器才会认为它是门语言。总之C相当令人不快。一直以来,我总是尽可能避开C。至于C++,除了叫人反感之外,一无是处。因此我总是尽可能不用C++,在Netscape时,我用C语言搞定一切。理由很简单,我们的目标平台都是性能不高的机器,没法很好地运行C++程序,一旦开始采用其他库,C++程序就会变得很大。此外,各个C++编译器千差万别,相互之间存在许多不兼容问题。于是我们一开始就敲定使用ANSI C,它很好地满足了我们的要求。C之后用的是Java,感觉又像回到了Lisp,因为Java不存在你拼命要避开的概念,这下又自在了。

Seibel:比如?

Zawinski:内存管理。函数也更像函数而非子程序。另外,对模块化的要求也高很多。用C写代码,很容易不自觉地写个goto语句,用起来实在太顺手。

Seibel:现在你好像主要使用C和Perl。

Zawinski:嗯,其实我已经不怎么写程序了。通常我只写些凌乱简短的Perl脚本,用来维持服务器的正常运转。另外,我还写些简单的代码,为自用的MP3获取唱片封面,等等。这都是些短小急就、即用即抛的程序。

Seibel:你是喜欢Perl,还是由于它方便才用的?

Zawinski:哦,我可瞧不上Perl,它太可怕了。不过,它几乎无处不在。随便找台电脑坐下来,你用不着找人安装Perl即可运行自己的脚本,Perl早装上了。这是Perl值得推荐的唯一理由。

Perl拥有大而全的库。基本上你想做的都有库能帮你实现。尽管通常实现得不是很好,但至少有现成的。如果用Java写了些代码,然后试着运行,结果发现在自己的电脑上安装Java遇到问题,这种体验实在令人不快,我就碰到过。在我看来,Perl是门卑鄙的语言。如果只用Perl很小一部分,你可以让它变得像C那样,或者更像JavaScript。Perl的语法太过古怪,数据结构一团糟。Perl的好处实在不太多。

Seibel:但至少没C++那么糟?

Zawinski:是的,绝对没有。Perl的应用场合不太一样。对于某些活,用Perl或类似的语言比用C编写要容易得多,前者是面向文本的语言,即所谓的“脚本语言”(Scripting Language)。我个人并不赞同“程序”和“脚本”的分法,这么分毫无意义。不过,如果主要操作就是处理文本或启动程序,比如运行wget下载HTML页面,并对其做模式匹配,那么用Perl实现显然更便捷。

Seibel:后来怎么离开了Lucid?

Zawinski:Lucid完蛋了。当时裁了不少人。我发邮件给自己相识的几个人:“嘿,看样子我也得快点找份新工作了。”Marc Andreessen刚好是其中一个,他回信说:“太巧了,我们上周刚开了家公司,来我们这儿吧。”大概就是这么回事。

Netscape经历

Seibel:于是你去了Netscape。你主要做什么工作?

Zawinski:我一进公司就去开发浏览器的Unix平台部分。我去之前,其他人好像已经写了几天代码。其中Windows和Mac平台的进展稍快。总的原则是后端代码尽可能多,针对三个平台的前端代码尽可能少。

Seibel:所有代码全是从头写起的?

Zawinski:都是全新的代码。绝大部分Netscape创始人之前都是NCSA/Mosaic开发人员,他们写过不同平台的NCSA/Mosaic,实际上是三个独立的程序。那六个人都在Netscape。他们没有重用任何代码,当然他们之前写过这个程序。

Seibel:你做过的哪件事是最引以为傲的?

Zawinski:那就是我们发布了Netscape浏览器,其他不值一提。我主要专注于自己负责的部分,即Unix前端的用户界面。不过,最关键的是我们发布了Netscape,而且大家喜欢用。人们立即抛开NCSA Mosaic转而使用我们的产品,并感叹:“哇噢,这是我用过的最棒的软件。”Netscape工具栏提供了“精彩站点”按钮,可以展示人们推荐的那些精彩站点。大概有近200个!我倒不太为我们写的代码感到骄傲,关键在于它发布了。从许多方面来看,Netscape的代码不算太好,因为时间紧,写得太快。不过它的确实现了产品功能。我们发布了产品,这才是关键。

推出96 Beta版的第一个晚上,我们都围坐在房间里,盯着不断增加的下载量,每次下载都会发出响声,妙不可言。一个月后,两百万人用上了我们编写的软件。真是不可思议。毫无疑问,我们为Netscape作出的付出都是值得的,我们影响了人们的生活,他们的生活因我们的工作而变得更有意思、更快乐,也更轻松。

Seibel:另外,你还开发过邮件阅读器,对吗?

Zawinski:开发2.0版本时,Marc走到我的隔间,对我说:“我们需要一个邮件阅读器。”我回答道:“好啊,听起来不错。我之前做过邮件阅读器。”我当时住在伯克利,大概有两个星期没去办公室。那两个星期我就坐在咖啡馆里,乱涂乱画,试图勾勒出邮件阅读器要实现的功能。我列出功能列表后,又一项项划掉,确定要用多长时间实现,用户界面该如何设计才好。

随后我回到公司,开始写代码。Marc又找到我说:“对了,我们又雇了个人,他之前也做过邮件相关开发。你们俩一起开发吧。”那个人就是Terry Weissman,那家伙太强了,我们合作很愉快。跟早期浏览器开发团队其他人共事相比,这次合作是完全不同的体验。

我们俩不会互相对着嚷嚷。真想不到我们之间的分工方式也行得通,换了别人不知道会怎样。我已做好初步设计,写了些代码,每天或每两天,我们会对一下功能清单,我会说:“哦,我来做这块。”他会说:“好的,我做那块。”然后我们又各自忙开来。

检入代码后,我们会碰一次头,他会说:“我这边都搞定了,你的怎样了?”“唔,我在做这块。”“那好,我就开始做那块了。”我们就以这种方式分配任务。最后看来效果非常不错。

我们也会有分歧,我认为应该把过滤功能加到文件夹里,因为时间不够,没法做好。他说:“不行,我认为我们应该搞定那个。”而我则答道:“时间不够用了!”结果他当天晚上就写好了。

Seibel:你们会发很多电子邮件吗?

Zawinski:是的,邮件不断。那时还没有即时消息,搁现在的话,估计都会用即时消息,我们发的邮件往往只有一行内容。我们还通过电话交流。

我们发布的2.0版本集成了邮件阅读器,反响不错。接着我们着手开发2.1版,我认为这一版才是完整的,它将实现第一次发布时没有的功能。Terry和我刚做到一半,Marc进来对我说:“我们刚买了家公司。他们做的邮件阅读器与你们做的差不多。”我答道:“哦,好的。不过我们已经有了。”他说:“对,是的,不过公司发展太快,要雇到好员工太难,有时直接收购一家公司是条捷径,那些有经验的员工都能为我所用。”“那是,这些人准备做哪块?”“他们会接手你们现在做的项目。”“哦,真糟糕,那我得另外找点事做。”

大体情况是他们收购了Collabra,保留了整套管理层架构,在我和Terry之上。Collabra发布过一款产品,许多方面与我们的产品类似,只不过他们的产品只支持Windows,而且根本没什么市场。

然后他们赢得了创始股,并被Netscape收购。实际上是Netscape把公司的控制权交给了这家公司。结果他们不仅接管了邮件阅读器,最后收编了整个客户端部门。收购Collabra之际,Terry和我还在开发Netscape 2.1,收购之后,他们开始重写。不用说,他们的Netscape 3.0拖了很久,我们的2.1反而成了3.0,因为是时候发布个版本了,我们需要出个主要版本。

最终他们主导开发的3.0变成了4.0,你知道,那是Netscape遭遇的最严重的软件灾难,几乎毁了整个公司。尽管此后公司还撑了很长时间,不过总的来说,我们收购的这家公司,从未取得过什么成就,却无视我们的所有劳动和成功,他们主导的软件重写,直接陷入第二系统综合症,把我们搞垮了。

他们以为,只要身在Netscape,按原来那套做法,注定成功。但是,在之前的公司,他们那套做法就没成功过。结果当取得过成功的人告诫他们“注意,千万别用C++,也不要用线程”时,他们则答道:“你胡扯些什么?真是什么都不懂。”

好吧,正是诸如不用C++、不用线程的决定,我们才得以准时发布产品。另外还有一点非常重要,我们从来都是同一时间发布所有平台的版本,对此他们根本不以为然:“哦,百分之九十的人使用Windows,我们还是专注于Windows平台,然后再移植到其他平台。”那恰好是其他许多失败公司的做法。如果你打算发布跨平台产品,历史会告诉你这么做是大忌。真想做到跨平台的话,就必须同时开发。所谓的移植只会令产品在第二平台上蹩脚不堪。

Seibel:4.0版是从零开始重写的?

Zawinski:他们没有从零开始,不过最后也替换了每一行代码。他们一开始就用C++。对此我极力反对,该死的是,结果证明我是对的。使用C++,一切变得臃肿不堪。另外还引入了大量兼容性问题,用C++编程时,没人能断定C++哪部分可以安全使用。有个家伙说他要用模板,结果你会发现,没有哪两个编译器实现模板的机制是一样的。

当编写的代码支持的多平台只是Windows 3.1和Windows 95时,你根本就意识不到问题究竟有多严重。他们的做法令Unix平台版本成了灾难,谢天谢地,那时我已经不负责那块工作。同样,Mac平台版本也好不到哪儿去。这意味着产品无法再在Win16等低端Windows机器上运行。我们不得不开始消减支持的平台。或许也是时候那么做了,不过这个理由太过蹩脚。本来不用那么做。

出于个人怨恨、自私,我觉得自己和Terry搭建了这么棒的产品,却因成功而受罚,受罚的方式就是产品被交给一群白痴。那段时间我在Netscape非常郁闷。由此我也开始了在那儿干等着的日子。

Seibel:你在Netscape待了五年?

Zawinski:是的。一直待到Netscape被收购的第二年,被收购前一天,mozilla.org项目启动,一切又开始变得有意思,为此我又待了一阵子。

Seibel:你们最后也因使用C++而陷入困境?

Zawinski:没有,是在Java上出了问题。有一次,我们打算用Java重写浏览器。当时我们的想法是:“没问题!4.0代码库会毁了公司,我们要丢弃它,只有这么做才能成功,我们清楚自己在做什么!”不过最后还是失败了。

Seibel:是因为Java还不够成熟?

Zawinski:不是。我们这部分人又拆分成分工明确的小组,其中三个人负责邮件阅读器。最后我们做好了。邮件阅读器相当不错,速度快,还有大量很棒的特性,除了妥善保存你的数据,写大文件时几乎没有停顿。我们还充分利用了Java的多线程,比我预想的好用。整个项目开发感觉很开心。从已设计好的API来看,各方面进展顺利。

只有一块没做好,邮件阅读器没法显示消息。显示消息时邮件阅读器生成HTML,而显示HTML需要一个HTML显示层,结果这个显示层没搞定,到最后也没做好。页面渲染组完全误入歧途,乱套了,它们成了项目失败的主要原因。

本文节选自《Coders  At  Work》中文版。该书是当今15位大师级计算机程序员的访谈录,重点介绍了他们的编程感悟。感谢人民邮电出版社北京图灵文化发展有限公司授权。

(本文来自《程序员》杂志10年09期)


你可能感兴趣的:(C++,windows,工作,perl,语言,lisp)