### 14.夢寐以求的編程語言
> 一心讓臣民行善的暴君可能是最專制的暴君。
> ——C.S.LEWIS(1898—1963,英國小說家)
我的朋友曾對一位著名的操作系統專家說他想要設計一種真正優秀的編程語言。那位專家回答,這是浪費時間,優秀的語言不一定會被市場接受,很可能無人使用,因為語言的流行不取決于它本身。至少,那位專家設計的語言就遭遇到了這種情況。
那么,語言的流行到底取決于什么因素呢?流行的語言是否真的值得流行呢?還有必要嘗試設計一種更好的語言嗎?如果有必要的話,怎樣才能做到這一點呢?
為了找到這些問題的答案,我想我們可以觀察黑客,了解他們使用什么語言。編程語言本來就是為了滿足黑客的需要而產生的,當且僅當黑客喜歡一種語言時,這種語言才能成為合格的編程語言,而不是被當作“指稱語義”(denotational semantics)或者編譯器設計。
### 流行的秘訣
沒錯,大多數人選擇某一種編程語言,不是因為這種語言有什么獨特的特點,而是因為聽說其他人使用這種語言。但是我認為,外界因素對于編程語言的流行其實沒有想象中那么大的影響力。我倒是覺得,問題出在對于什么是優秀編程語言,黑客的看法與大多數的語言設計者不一樣。
黑客的看法其實比語言設計者的更重要。編程語言不是數學定理,而是一種工具,為了便于使用,它們才被設計出來。所以,設計編程語言的時候必須考慮到人類的長處和短處,就像設計鞋子的時候必須符合人類的腳型。如果鞋子穿上去不舒服,無論它的外形多么優美,多么像一件藝術品,你也只能把它當作一雙壞鞋。
大多數程序員也許無法分辨語言的好壞。但是,這不代表優秀的編程語言會被埋沒,專家級黑客一眼就能認出它們,并且會拿來使用。雖然他們人數很少,但就是這樣一小群人寫出了人類所有優秀軟件。他們有著巨大的影響力,他們使用什么語言,其他程序員往往就會跟著使用。老實說,很多時候這種影響力更像是一種命令,對于其他程序員來說,專家級黑客就像自己的老板或導師,他們說哪種語言好用,自己就會乖乖地跟進。
專家級黑客的看法不是決定一種語言流行程度的唯一因素,某些古老的軟件(Fortran和Cobol的情況)和鋪天蓋地的廣告宣傳(Ada和Java的情況)也會起到作用。但是,我認為從長期來看,專家級黑客的看法是最重要的因素。只要有了達到“臨界數量”(critical mass)的最初用戶和足夠長的時間,一種語言可能就會達到應有的流行程度。而流行本身又會使得這種優秀的語言更加優秀,進一步拉大它與平庸語言之間的好壞差異,因為使用者的反饋總是會導致語言的改進。你可以想一下,所有流行的編程語言從誕生至今的變化有多大。Perl和Fortran是極端的例子,除它們兩個之外,甚至就連Lisp都發生了很大的變化。
所以,即使不考慮語言本身的優秀是否能帶動流行,我想單單流行本身就肯定會使得這種語言變得更好,只有流行才會讓它保持優秀。編程語言的最高境界一直在發展之中。雖然語言的核心功能就像大海的深處,很少有變化,但是函數庫和開發環境之類的東西就像大海的表面,一直在洶涌澎湃。
當然,黑客必須先知道這種語言,才可能去用它。他們怎么才能知道呢?就是從其他黑客那里。所以不管怎樣,一開始必須有一群黑客使用這種語言,然后其他人才會知道它。我不知道“一群”的最小數量是多少,多少個黑客才算達到“臨界數量”呢?如果讓我猜,我會說20人。如果一種語言有20個獨立用戶,就意味這20個人是自主決定使用這種語言的,我覺得這就說明這種語言真的有優點。
達到這一步并非易事。如果說用戶數從0到20比從20到1000更困難,我也不會感到驚訝。發展最早的20個用戶的最好方法可能就是使用特洛伊木馬:你讓人們使用一種他們需要的應用程序,這個程序偏巧就是用某種新語言開發的。
### 外部因素
我們得先承認,確實有一個外部因素會影響到語言的流行。一種語言必須是某一個流行的計算機系統的腳本語言(scripting language),才會變得流行。Fortran和Cobol是早期IBM大型機的腳本語言。C是Unix的腳本語言,后來的Perl和Python也是如此。Tel是Tk的腳本語言,Visual Basic是Windows的腳本語言,(某種形式的)Lisp是Emacs的腳本語言,PHP是網絡服務器的腳本語言,Java和JavaScript是瀏覽器的腳本語言。
編程語言不是存在于真空之中。“編程”其實是及物動詞,黑客一般都是為某個系統編程,在現實中,編程語言總是與它們依附的系統聯系在一起的。所以,如果你想設計一種流行的編程語言,就不能只是單純地設計語言本身,還必須為它找到一個依附的系統,而這個系統也必須流行。除非你只想用自己設計的語言取代那個系統現有的腳本語言。
這種情況導致的一個結果就是,無法以一種語言本身的優缺點評判這種語言。另一個結果則是,只有當一種語言是某個系統的腳本語言時,它才能真正成為編程語言。如果你對此很吃驚,覺得不公平,那么我會跟你說不必大驚小怪。這就好比大家都認為,如果一種編程語言只有語法規則,沒有一個好的實現(implementation),那么它就不能算完整的編程語言。這些都是很正常很合理的事情,編程語言本來就該如此。
當然,編程語言本來就需要一個好的實現,而且這個實現必須是免費的。商業公司愿意出錢購買軟件,但是黑客作為個人不會愿意這樣做,而你想讓一種語言成功,恰恰就是需要吸引黑客。
編程語言還需要有一本介紹它的書。這本書應該不厚,文筆流暢,而且包含大量優秀的范例。布賴恩·柯尼漢和丹尼斯·里奇合寫的《C程序設計語言》(C Programming Language)就是這方面的典范。眼下,我大槪還能再加一句,這一類書籍之中必須有一本由O'Reilly公司出版發行。這正在變成是否能吸引黑客的前提條件了。
編程語言還應該有在線文檔。事實上,在線文檔可以當作一本書來寫,但是目前它還無法取代實體書。實體書并沒有過時,它們讀起來很方便,而且出版社對書籍內容的審核是一種很有用的質量保證機制(雖然做得很不完美)。書店則是程序員發現和學習新語言的最重要的場所之一。
### 簡潔
假定你的語言已經能夠滿足上面三項條件——一種免費的實現,一本相關書籍,以及語言所依附的計算機系統——那么還需要做什么才能使得黑客喜歡上你的語言?
黑客欣賞的一個特點就是簡潔。黑客都是懶人,他們同數學家和現代主義建筑師一樣,痛恨任何冗余的東西或事情。有一個笑話說,黑客動手寫程序之前,至少會在心里盤算一下哪種語言的打字工作量最小,然后就選擇使用該語言。這個笑話其實與真實情況相差無幾。就算這真的是個笑話,語言的設計者也必須把它當真,按照它的要求設計語言。
簡潔性最重要的方面就是要使得語言更抽象。為了達到這一點,首先你設計的必須是高級語言,然后把它設計得越抽象越好。語言設計者應該總是看著代碼,問自己能不能使用更少的語法單位把它表達出來。如果你有辦法讓許多不同的程序都能更簡短地表達出來,那么這很可能意味著你發現了一種很有用的新抽象方法。
不要覺得為用戶著想就是讓他們使用像英語一樣又長又啰嗦的語法。這是不正確的做法,Cobol就是因為這個毛病而聲名狼藉。如果你讓黑客像下面這樣求和:
`add x to y giving z`
而不是寫成:
`z=x+y`
那么你就是在侮辱黑客的智商,或者自己作孽了。
簡潔性是靜態類型語言的力所不及之處。不考慮其他因素時,沒人愿意在程序的頭部寫上一大堆的聲明語句。只要計算機可以自己推斷出來的事情,都應該讓計算機自己去推斷。舉例來說,hello-world本應該是一個很簡單的程序,但是在Java語言中卻要寫上一大堆東西,這本身就差不多可以說明Java語言設計得有問題了^。
^「hello-world程序的唯一作用就是顯示出“Hello,world!”這句話。使用Java語言,你需要這樣寫:
`public class Hello {
????public static void main(String[] args) {
????????System.out.println("Hello, world!");
????}
}`
如果你從來沒有接觸過編程,看到上面的代碼可能會很奇怪,讓計算機顯示一句話為什么要鎬得這么復雜?有意思的是,資深程序員的反應與你一樣。」
單個的語法單位也應該很簡短。Perl和Common Lisp在這方面是兩個不同的極端。Perl的語法單位很短,導致它的代碼可以擁擠得讓人無法理解,而Common Lisp內置運算符的名稱則長得可笑。Common Lisp的設計者們可能覺得文本編輯器會幫助用戶自動填寫運算符的長名稱。但是這樣做的代價不僅是增加了打字的工作量,還包括提高了閱讀代碼的難度,以及占用了更多的顯示器空間。
### 可編程性(Hackability)
對黑客來說,選擇編程語言的時候,還有一個因素比簡潔更重要,那就是這種語言必須能夠幫助自己做到想做的事。在編程語言的歷史上,防止程序員做出“錯誤”舉動的措施多得驚人。這是語言設計者很自以為是的危險舉動,他們怎么知道程序員該做什么不該做什么?我認為,語言設計者應該假定他們的目標用戶是一個天才,會做出各種他們無法預知的舉動,而不是假定目標用戶是一個笨手笨腳的傻瓜,需要別人的保護才不會傷到自己。如果用戶真的是傻瓜,不管你怎么保護他,他還是會搬起石頭砸自己的腳。你也許能夠阻止他引用另一個模塊中的變量,但是你沒法防止他日日夜夜不知疲倦地寫出結構混亂的程序去解決完全錯誤的問題。
優秀程序員經常想做一些既危險又令人惱火的事情。所謂“令人惱火”,我指的是他們會突破設計者提供給用戶的外部語義層,試著控制某些高級抽象的語言內部接口。比如,黑客喜歡破解,而破解就意味著深入內部,揣測原始設計者的意圖。
你應該敞開胸懷,歡迎這種揣測,對于制造工具的人來說,總是會有用戶以違背你本意的方式使用你的工具。如果你制造的是編程語言這樣高度組合的系統,那就更是如此了。許多黑客會用你做夢也想不到的方式改動你的語法模型。我的建議就是,讓他們這樣干吧,而且應該為他們創造便利,盡可能多地把語言的內部暴露在他們面前。
其實,黑客并不會徹底顛覆你的工具,在一個大型程序中,他可能只是對語言改造一兩個地方。但是,改動多少地方并不重要,重要的是他能夠對語言進行改動。這可能不僅有助于解決一些特殊的問題,還會讓黑客覺得很好玩。黑客改造語言的樂趣就好比外科醫生擺弄病人內臟的樂趣,或者青少年喜歡用手擠破青春痘的那種感覺。至少對男生來說,某些類型的破壞非常刺激。針對青年男性讀者的Maxim雜志每年出版一本特輯,里面一半是美女照片,另一半是各種嚴重事故的現場照片。這本雜志非常清楚它的讀者想看什么^。
^「在《神經外科醫生手記》(When the Air Hits Your Brain)一書中,神經外科醫生弗托塞克講述了住院總醫生戈雷的一段話,內容關于外科醫生與內科醫生的區別。
> 戈雷和我要了一個大比薩,找了一張空桌子坐下。他點起一根香煙,說:“那些內科醫生真是令人討厭,總是喜歡談論一輩子只能遇到一次的病例。這就是他們的問題,他們只喜歡古怪的東西,討厭普通的常見病例。這就是我們和他們的區別。你看,我們喜歡腰椎間盤突出,覺得像比薩一樣又大又好吃,但是他們看到高血壓就憎恨不已……”
很難把腰椎間盤突出與又大又好吃聯系在一起,徂是,我想我知道他們指的是什么。我經常覺得某個bug非常誘人,一定要追蹤下去。不是程序員的人很難想象bug有什么好玩的。一切正常當然很好,但是不可否認,能夠抓到某些bug會讓人興奮到極點。」
一種真正優秀的編程語言應該既整潔又混亂。“整潔”的意思是設計得很清楚,內核由數量不多的運算符構成,這些運算符易于理解,每一個都有很完整的獨立用途。“混亂”的意思是它允許黑客以自己的方式使用。C語言就是這樣的例子,早期的Lisp語言也是如此。真正的黑客語言總是稍微帶一點放縱不羈、不服管敎的個性。
優秀的編程語言所具備的功能,應該會使得言必稱“軟件工程”的人感到非常不滿、頻頻搖頭。與黑客語言形成鮮明對照的就是像Pascal那樣的語言,它是井然有序的模范,非常適合教學,但是除此之外就沒有很大用處了。
### 一次性程序
為了吸引黑客,一種編程語言必須善于完成黑客想要完成的各種任務。這意味著它必須很適合開發一次性程序。這一點可能出乎很多人的意料。
所謂一次性程序,就是指為了完成某些很簡單的臨時性任務而在很短時間內寫出來的程序。比如,自動完成某些系統管理任務的程序,或者(為了某項模擬任務)自動生成測試數據的程序,以及在不同格式之間轉化數據的程序等。令人吃驚的是,一次性程序往往不是真的只用一次,就像二戰期間很多美國大學造的一大批臨時建筑后來都成了永久建筑。許多一次性程序后來也都變成了正式的程序,具備了正式的功能和外部用戶。
我有一種預感,最優秀的那些大型程序就是這樣發展起來的,而不是像胡佛水壩那樣從一開始就作為大型工裎來設計。一下子從無到有做出一個大項目是很恐怖的一件事。當人們接手一個巨型項目時,很容易被它搞得一蹶不振。最后,要么是項目陷入僵局,要么是做出來一個規模小、性能差的東西。你想造一片鬧市,卻只做出一家商場;你想建一個羅馬,卻只造出一個巴西利亞;你想發明C語言,卻只開發出Ada。
開發大型程序的另一個方法就是從一次性程序開始,然后不斷地改進。這種方法比較不會讓人望而生畏,程序在不斷的開發之中逐漸進步。一般來說,使用這種方法開發程序,一開始用什么編程語言,就會一直用到最后,因為除非有外部政治因素的干預,程序員很少會中途更換編程語言。所以,我們就有了一個看似矛盾的結論:如果你想設計一種適合開發大型項目的編程語言,就必須使得這種語言也適合開發一次性程序,因為大型項目就是從一次性程序演變而來的。
Perl就是一個鮮明的例子。它不僅僅設計成適合開發一次性程序,而且它本身就很像一次性程序。最初的Perl只是好幾個生成表格的工具收集在一起而已。后來程序員用它寫一次性程序,當那些程序逐漸發展壯大后,Perl才隨之發展成了一種正式的編程語言。到了Perl 5,這種語言才適合開發重要的程序,但是在此之前它已經廣為流行了。
什么樣的語言適合寫一次性程序?首先,它必須很容易裝備。一次性程序是你只想在一小時內寫出來的程序,所以它不應該耗費很多時間安裝和配置,最好已經安裝在你的電腦上了。它必須是想用就用的。C語言可以想用就用,因為它是操作系統的一部分;Perl可以想用就用,因為它本來就是一種系統管理工具,操作系統已經默認安裝它了。
很容易裝備不僅僅指很容易安裝或者已經安裝,還指很容易與使用者互動。一種有命令行界面、可以實時反饋的語言就具有互動性,那些必須先編譯后使用的語言就不具備互動性。受歡迎的編程語言應該是前者,具有良好的互動性,可以快速得到運行結果。
一次性程序的另一個特點就是簡潔。對黑客來說,這一點永遠有吸引力。如果考慮到你最多只打算在這個程序上耗費一個小時,這一點就更重要了。
### 函數庫
簡潔性的最高形式當然是有人已經幫你把程序寫好,你只要運行就可以了。函數庫就是別人幫你寫好的程序,所以它是編程語言的另一個重要特點,并且我認為正在變得越來越重要。Perl就贏在它具有操作字符串的巨大函數庫。這類函數庫對一次性程序特別重要,因為開發一次性程序的原始目的往往就是轉化或提取字符串。許多Perl程序的原型可能就是把幾個函數庫調用放在一起。
我認為,未來50年中,編程語言的進步很大一部分與函數庫有關。未來的函數庫將像語言內核一樣精心設計。優秀函數庫的重要性將超過語言本身。某種語言到底是靜態類型還是動態類型、是面向對象還是函數式編程,這些都不如函數庫重要。那些習慣用變量類型考慮問題的語言設計者可能會對這種趨勢感到不寒而栗。這不等于把語言設計降到開發應用程序的層次嗎?哦,真是太糟了。但是別忘了,編程語言是供程序員使用的,而函數庫就是程序員需要的東西。
設計優秀的函數庫是很難的,并不只是寫一大堆代碼而已。一且函數庫數量變得太多,找到一個你需要的函數有時候還不如自己動手寫來得快。函數庫的設計基礎與語言內核一樣,都是一個小規模的正交運算符集合。函數庫的使用應該符合程序員的直覺,讓他可以猜得出哪個函數能滿足自己的需要。
### 效率
眾所周知,好的編程語言生成的代碼有較快的運行速度。但是實際上,我覺得代碼的運行速度不是編程語言的設計者能夠控制的。高德納很久以前就指出,運行速度只取決于一些關鍵的瓶頸。而在編程實踐中,許多程序員都已經注意到自己很容易搞錯瓶頸到底在哪里。
所以,編程時提高代碼運行速度的關鍵是使用好的性能分析器(profiler),而不是使用其他方法,比如精心選擇一種靜態類型的編程語言。為了提高運行速度,并沒有必要每個函數的每個參數類型都聲明清楚,你只需要在瓶頸處聲明清楚參數類型就可以了。所以,更重要的是你需要能夠找出瓶頸到底在什么地方。
人們在使用非常高級的語言(比如Lisp)時,經常抱怨很難知道哪個部分對性能的影響比較大。可能確實如此,如果你使用一種非常抽象的語言,這也許是無法避免的。不管怎樣,我認為一個好的性能分析器會解決這個問題,雖然這方面還有很長的路要走,但是未來你可以快速知道程序每個部分的時間開銷。
這個問題一部分源于溝通不暢。語言設計者喜歡提高編譯器的速度,認為這是對自己技術水平的考驗,而最多只把性能分析器當作一個附送給使用者的贈品。但是在現實中,一個好的性能分析器對程序的幫助可能大于編譯器的作用。這里又一次反映出語言設計者與用戶之間發生了脫節,前者竭盡全力想要解決的問題其實方向不甚正確。
讓性能分析器自動運行可能是一個好主意。它自動告訴程序員每個部分的性能,而不是非要等到程序員手動運行后才能知道。比如,當程序員編輯源碼的時候,代碼編輯器能夠實時用紅色顯示瓶頸的部分。另一個方法應該是設法顯示正在運行的程序的情況,這對互聯網軟件尤其重要,因為服務器上有很多程序同時運行,它們都需要你密切關注。自動運行的性能分析器用圖形實時顯示程序運行時的內存狀況,甚至可以發出聲音,表示出現了問題。
出現問題時,聲音是很好的提示。我們在Viaweb搞了一塊很大的面板,上面有各種各樣的儀表盤,用來顯示服務器的狀況。儀表盤的指針由微型馬達驅動,每當馬達旋轉的時候,就會發出一陣輕微的噪音。在我的工位沒法看到儀表盤,但是只要我聽到聲音,就能立刻知道服務器出現了問題。
性能分析器甚至有可能自動找出不合理的算法。如果將來有人發現某種形式的內存訪問是不合理算法的信號,我不會感到很驚訝。如果有一個小人兒可以鉆進計算機看看我們的程序是怎么運行的,他可能會變成一個忙碌又悲慘的可憐蟲,就像那些為政府跑腿的小人物。我總覺得自己用處理器做了很多無用功,伹是一直沒有找到能夠看出程序是怎樣浪費運算能力的好辦法。
現在有一些語言先編譯成字節碼(byte code),然后再由解釋器執行。這樣做主要是為了讓代碼容易移植到不同的操作系統,伹是這也可以變成一項很有用的功能。讓字節碼成為語言的正式組成部分,允許程序員在瓶頸處內嵌字節碼,這可能是一個不錯的主意。然后,針對這部分字節碼的優化也就變得可以移植了。
正如許多最終用戶已經意識到的,運行速度的概念正在發生變化。隨著互聯網軟件的興起,越來越多的程序主要不是受限于計算機的運算速度,而是受限于I/O的速度。加快I/O速度將是很值得做的一件事。在這方面,編程語言也能起到作用,有些措施是顯而易見的,比如采用簡潔、快速、格式化輸出的函數,還有些措施則需要深層次的結構變化,比如采用緩存和持久化對象(persistent object)。
用戶關心的是反應時間(response time),但是軟件的另一種效率正在變得越來越重要,那就是每個處理器能夠同時支持的用戶數量。未來許多有趣的應用程序都將是運行在服務器端的互聯網軟件,所以每臺服務器能夠支持的用戶數量就成了軟件業者的關鍵問題。互聯網軟件的資本支出就取決于這個指標。
許多年以來,大多數面向最終用戶的程序都不太關心效率。軟件開發者總是假設用戶桌面電腦的運算能力會不斷增長,所以不用刻意提高軟件的效率。帕金森定律^被證明與摩爾定律一樣顛撲不破。軟件不斷膨脹,消耗光所有可以得到的資源。這一切將隨著互聯網軟件的出現發生改變,因為硬件和軟件現在捆綁在一起供應。對于那些提供互聯網軟件的公司來說,每臺服務器支持的用戶數量最大化會對降低成本產生巨大影響。
^「帕金森定律(Parkinson's Law)的一種原始表達形式是“工作總是到最后一刻才會完成”,后來引申到計算機領域就變成了“數據總是會填滿所有空間”,更一般性的總結則是“對一種資源的需求總是會消耗光這種資源的所有供應”。——譯者注」
在一些應用程序中,處理器的運算能力是瓶頸,那么最重要的優化對象就是軟件的運行速度。但是,一般情況下內存才是瓶頸,你能夠同時支持的用戶數量取決于用戶數據所消耗的內存。編程語言在這方面也能發揮作用,對線程的良好支持將使得所有用戶共享同一個內存堆(heap)。持久化對象和語言內核級別的延遲加載(lazy loading)支持也有助于減少內存需求。
### 時間
一種編程語言要想變得流行,最后一關就是要經受住時間的考驗。沒人想用一種會被淘汰的語言編程,這方面已經有很多前車之鑒了。所以,大多數黑客往往會等上幾年,看看某一種新語言的勢頭,然后才真正考慮使用它。
新事物的發明者通常對這個發現很震驚,他們沒想到人們居然這樣對待發明創造。但是,讓別人相信一種新事物是需要時間的。我有一個朋友,他的客戶第一次提出某種需求時,他很少理會。因為他知道人們有時候會想要自己并不真正需要的東西。為了避免浪費時間,只有當客戶第三次或第四次提出同樣的需求時,他才認真對待。這個時候客戶可能已經很不高興了,但是這至少保證他們提出的需求應該就是他們真正需要的東西。
大多數人接觸新事物時都學會了使用類似的過濾機制。甚至有時要聽到別人提起十遍以上他們才會留意。這樣做完全是合理的,因為大多數的熱門新商品事后被證明都是浪費時間的噱頭,沒多久就消失得無影無蹤。虛擬現實建模語言VRML剛誕生時曾經轟動一時,但是我決定等到一兩年后再去學習它,結果一兩年后已經沒有學習的必要了,因為市場已經把它遺忘了。
所以,發明新事物的人必須有耐心,要常年累月不斷地做市場推廣,直到人們開始接受這種發明。我們就耗費了好幾年才使得客戶明白ViaWeb不需要下載安裝就能使用。不過,好消息是,簡單重復同一個信息就能解決這個問題。你只需要不停地重復同一句話,最終人們將會開始傾聽。人們真正注意到你的時候,不是第一眼看到你站在那里,而是發現過了這么久你居然還在那里。
新事物的發展改進一般也需要很長時間。大多數技術在誕生后都逐漸發生了巨大的變化,編程語言更是如此。誕生頭幾年,一小批早期使用者比其他因素更能促進技術發展。早期使用者都是行家,要求也很高,能夠很快找出你的技術中存在的缺點。而且,如果你的用戶只有很少幾個人,你就能夠與他們所有人保持密切接觸。只要不斷改進你的系統,即使給用戶造成了損失,早期使用者也會對你寬容大度的。
新技術被市場接納的方式有兩種,一種是自然成長式,另一種是大爆炸式。自然成長式的一個例子就是在車庫里白手起家、自力更生的創業者。幾個好朋友埋頭工作,在外界毫不知曉的情況下開發出某種新技術。他們把它推向市場,沒有任何宣傳,最初的用戶寥寥無幾(但是熱心程度無與倫比)。創業者持續改進新技術,與此同時,通過口碑效應,用戶數量不斷增長。在創業者不經意間,他們已經壯大起來了。
大爆炸式的例子是有風險資本支持、在市場上大張旗鼓宣傳的創業公司。他們急急忙忙地開發一個產品,推向市場的時候大肆曝光,立刻就獲得了一大批使用者(至少他們希望如此)。
一般來說,車庫里的創業者會妒忌大爆炸式的創業公司。后者的主導人物個個光彩照人、自信非凡,深受風險資本商的追捧。他們什么都買得起,在公關公司配合產品推出的宣傳活動中,他們自己也附帶成為了明星人物。自然成長式的創業者坐在自家車庫里,覺得自己又窮又可憐。伹是我想他們不必難過。最終來看,自然成長式會比大爆炸式產生更好的技術,能為創始人帶來更多的財富。如果你研究一下目前的主流技術,就會發現大部分都是源于自然成長式。
這種模式不僅存在于商業公司,還存在于科研活動中。Multics操作系統和Ada語言是大爆炸式項目,現在都已經銷聲匿跡了,而它們的繼承者Unix和C語言則是自然成長式項目。
### 再設計
著名散文家E.B.懷特說過,“最好的文字來自不停的修改”。所有優秀作家都知道這一點,它對軟件開發也適用。設計一樣東西,最重要的一點就是要經常“再設計”,編程尤其如此,再多的修改都不過分。
為了寫出優秀軟件,你必須同時具備兩種互相沖突的信念。一方面,你要像初生牛犢一樣,對自己的能力信心萬丈;另一方面,你又要像歷經滄桑的老人一樣,對自己的能力抱著懷疑態度。在你的大腦中,有一個聲音說“千難萬險只等閑”,還有一個聲音卻說“早歲哪知世事艱”。
這里的難點在于你要意識到,實際上這兩種信念并不矛盾。你的樂觀主義和懷疑傾向分別針對兩個不同的對象。你必須對解決難題的可能性保持樂觀,同時對當前解法的合理性保持懷疑。
做出優秀成果的人,在做的過程中常常覺得自己做得不夠好。其他人看到他們的成果覺得棒極了,而創造者本人看到的都是自己作品的缺陷。這種視角的差異并非偶然,因為只有對現狀不滿,才會造就杰出的成果。
如果你能平衡好希望和擔憂,它們就會推動項目前進,就像自行車在保持平衡中前進一樣。在創新活動的第一階段,你不知疲倦地猛攻某個難題,自信一定能夠解決它。到了第二階段,你在清晨的寒風中看到自己已經完成的部分,清楚地意識到存在各種各樣的缺陷。此時,只要你對自己的懷疑沒有超過你對自己的信心,就能夠坦然接受這個半成品,心想不管多難我還是可以把剩下的部分做完。
讓這兩股相反的力量保持平衡是很難的。初出茅廬的年輕黑客都很樂觀,自以為做出了偉大的產品,從不反思和改進。上了年紀的黑客又太不自信,甚至故意回避一些挑戰性很強的項目。
任何措施,只要能讓“再設計”周而復始地進行下去,就都是可取的。文章可以修改到你滿意為止,但是軟件的修改通常來說可以無休止地進行下去。文章的讀者不可能抱怨修改后新增加的內容讓他們前后的思想產生了不協調,但是軟件的使用者就會抱怨修改后的版本有不兼容問題。
用戶是一把雙刃劍。他們推動語言的發展,但也使得你不敢對語言進行大規模改造。所以,一開始的時候要精心選擇用戶,避免使用者過快增長。發展用戶就像一種優化過程,明智的做法就是放慢速度。一般情況下,用戶比較少意味著你任何時候都可以加大修改的力度。這時,對語言規格做出改變就像撕繃帶,當你感到痛苦的一瞬間,痛苦就已經成為了回憶。如果用戶數量龐大,修改語言帶來的痛苦就將持續很長時間。
大家都知道,讓一個委員會負責設計語言是非常糟糕的主意。委員會只會做出惡劣的設計。但是我覺得,委員會最大的問題在于他們妨礙了“再設計”。在委員會的主持下,修改一種語言是非常麻煩的事,沒有人愿意自討苦吃。而且,即使大多數成員不喜歡某種做法,委員會最后的決定往往還是維持現狀。
就算委員會只有兩個人,還是會妨礙“再設計”,典型例子就是軟件內部的各個接口由不同的人負責。這時除非兩個人都同意改變接口,否則接口就無法改變。因此現實中,盡管軟件功能越來越強大,內部接口卻往往一成不變,成為整個系統中拖后腿的部分。
一種可能的解決方法是,將軟件內部的接口設計成垂直接口而不是水平接口。這意味著軟件內部的模塊是一個個垂直堆積起來的抽象層,層與層之間的接口完全由其中的一層控制。如果較高的一層使用了較低的一層定義的語言,那么接口就由較低的一層控制;如果較低的一層從屬于較高的一層,那么接口就由較高的一層控制。
### 夢寐以求的編程語言
讓我們試著描述黑客心目中夢寐以求的語言來為以上內容做個小結。這種語言干凈簡練,具有最高層次的抽象和互動性,而且很容易裝備,可以只用很少的代碼就解決常見的問題。不管是什么程序,你真正要寫的代碼幾乎都與你自己的特定設置有關,其他具有普遍性的問題都有現成的函數庫可以調用。
這種語言的句法短到令人生疑。你輸入的命令中,沒有任何一個字母是多余的,甚至用到Shift鍵的杌會也很少。
這種語言的抽象程度很高,使得你可以快速寫出一個程序的原型。然后,等到你開始優化的時候,它還提供一個真正出色的性能分析器,告訴你應該重點關注什么地方。你能讓多重循環快得難以置信,并且在需要的地方還能直接嵌入字節碼。
這種語言有大量優秀的范例可供學習,而且非常符合直覺,你只需花幾分鐘閱讀范例就能領會應該如何使用此種語言。你偶爾才需要查閱操作手冊,它本身很薄,里面關于限定條件和例外情況的警告寥寥無幾。這種語言的內核很小,但很強大。各個函數庫高度獨立,而且和內核一樣經過精心設計,它們都能很好地協同工作。語言的每個部分就像精密照相機的各種零件一樣完美契合,不需要為了兼容性問題放棄或者保留某些功能。所有函數庫的源碼都很容易得到。這種語言能夠很輕松地與操作系統和用其他語言開發的應用裎序對話。
這種語言以層的方式構建。較高的抽象層透明地構建在較低的抽象層之上。如果需要的話,你可以直接使用較低的抽象層。
除了一些絕對必要隱藏的東西,這種語言的所有細節對使用者都是透明的。它提供的抽象能力只是為了方便你的開發,而不是為了強迫你按照它的方式行事。事實上,它鼓勵你參與它的設計,給你提供與語言創造者平等的權力。你能夠對它的任何部分加以改變,甚至包括它的語法。它盡可能讓你自己定義的部分與它本身定義的部分處于同等地位。這種夢幻般的編程語言不僅開放源碼,更開放自身的設計。