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期)


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章