《Java編程思想》作者:C++不垃圾,只是Java很傲慢

摘要:《Thinking in C++》及《Thinking in Java》的作者Bruce Eckel向來是個“擁C++反Java”派,他曾經不止一次的提到,C++語言特性的添加有多麼的深思熟慮,而Java又是如何的把一些奇怪的東西不停的加進去。Bruce認爲,理解語言特性爲什麼會存在是非常有幫助的。他將其稱之爲“語言考古學”。

本文節選自《Thinking in C++》及《Thinking in Java》作者Bruce Eckel的博文,文章寫在一次C++規範委員會例常會議之後,Bruce受C++設計師(常被稱爲C++之父)Bjarne Stroustrup邀請而參與了這次會議,並寫下了參會感想如下(節選):

在C++委員會會議上我所能找到的,是C++社區裏最聰明的一羣人,羣英薈萃,爲我答疑解惑。我很快意識到,這種方式之好,遠超我在任何一門研究生課程中之所得。如果考慮到研究生的機會成本,這還是一筆在財務上要划算得多的生意。

我被深深吸引住了,堅持出席了有大約8年的時間。在我走後,委員會仍繼續前行;雖標準仍未制定完成,但彼時Java已經出現了,還有一些其他(語言)的草案也問世了(這是技術刺激成癮者的毛病——我的確鑽研某一門語言,但我也一直在尋找更有生產力的手段:那些前景看起來很光明的語言特性可以毫不費力地分散我的注意力)。

每次大家見面的時候,我都會拋出一列清單,這是我累積下來的有關C++的棘手問題列表。通常我會請他們在幾日內予以澄清。出席委員會能看到的最有價值的東西就是這個,當然,還包括得以早早接觸到即將公佈的新特性。

從長遠來看,把語言特性添加進C++的謎團裏面並觀察它,是一門深奧的學問。現在說三道四是一件很簡單的事情,說什麼C++太爛了,設計太糟糕了等等。在對C++設計時所受的約束都沒有任何理解時,很多人就這樣脫口而出了。Stroustrup(51CTO編者注:這個Stroustrup也就是邀請作者參會的Stroustrup,也就是C++語言的設計師Stroustrup)的約束是,C程序應該稍作改動,或者最好不做改動,就能在C++下編譯。且不管這是不是完全合乎邏輯,但它給C程序員提供了一個很好的演進路徑。不過這存在較大的侷限性,需要把每一項大家抱怨不已的困難特性都一一虛擬化。由於這些特性難以理解,許多人就直接得出結論說C++設計糟糕,而這遠非事實。

在語言設計上,Java用傲慢的態度對待這一認識。關於這一點,我在《Java編程思想》及許多博文上都寫過了。因此我的長期追隨者都知道,由於Gosling(Java語言之父)和Java語言設計者對C++的否定態度,Java一開始就把我擰到了錯誤的方向。說實話,我與Gosling 的首次“邂逅”印象糟糕——那是很多年以前的事了,當時我剛進入第一家公司,第一次開始使用UNIX (Fluke,生產電子測試設備;我在裏面做嵌入式系統編程)。有一位軟件工程師輔導我,教我使用emacs。不過當時公司裏唯一的工具只有Gosling Emacs的商用版

(Unipress)。如果你做錯了什麼,程序會侮辱你,把你叫做火雞,並把屏幕填滿垃圾。這樣的東西出現在了一個商用產品上,而我們公司可是花了相當一筆錢的。不消說,等到Gnu emacs變得穩定起來後,公司馬上就換到了Gnu emacs上(我見過Richard Stallman。當然,他是個瘋狂的傢伙。不過他也是絕頂聰明的:他知道當遇到麻煩的時候,你需要的是幫助,而不是侮辱)。(51CTO編者注:Richard Stallman即Gnu emacs的開發人員,美國一位著名黑客。他曾在05年坐客新浪,與洪峯大談黑客道培訓。)

我不知道對Gosling印象的這段形成經歷在多大程度上影響了我後面對他工作的看法,但事實上,“我們看見它太差勁了,就決定拿出自己的語言”,對C++的這種態度於事無補。尤其是當我開始在《Java編程思想》的寫作過程中把它弄清楚,並屢次發現,那些草率決定的語言特性與庫,都不得不予以修訂——確實如此,其中的大部分都必須要修訂,有些修訂還是在程序員已經忍受了多年之後才落實。在許多場合下,Gosling坦誠他們必須快馬加鞭,否則就要被互聯網革命超越了。

我發現,理解語言特性爲什麼會存在是非常有幫助的。如果是由大學教授一下子和盤托出,把它們端到你面前,你勢必就會構想出這門語言的一個神話,說“這種語言特性之所以存在,肯定有一些真正重要的原因,這些原因只有創建這門語言的聰明人才能理解,我是理解不了的,我信賴它就是了”。從某方面來說,對語言特性這種基於信仰的接受是一種負擔;它阻止你對所發生的事情進行分析和理解。在我的主旨演講中(Bruce將在未來幾天參與一個主旨演講),我會關注一些特性,並檢查一下它們在不同語言中是如何被實現的,以及爲什麼被實現。

這裏就有個例子。對象創建。在C語言中,聲明瞭變量之後編譯器就會爲你創建堆棧空間(未經初始化,除非你初始化,否則會有垃圾數據)。但是如果你想要動態地做這件事情,你就得使用 malloc() 和 free()這兩個標準庫函數,還要小心翼翼地手工執行完所有的初始化及清理工作。如果你忘了,就會出現內存泄漏等類似災難,這是常有的事。

有關動態對象創建:一般來說,編譯器將內存分爲三部分:靜態存儲區域、棧、堆。靜態存儲區主要保存全局變量和靜態變量,棧存儲調用函數相關的變量、地址等,堆存儲動態生成的變量,在c中是指由malloc,free運算產生釋放的存儲空間,在c++中就是指new和delete運算符作用的存儲區域。(來源:51CTO樹洞的技術博客

因爲malloc() 和 free() “僅僅”是庫函數,在基本編程課上,應有的相關知識通常沒有被傳授,令人既疑惑不解又膽顫心驚。當程序員需要分配大量的內存空間時,他們就不去學如何來使用這些函數進行處理,取而代之的是常常就分配一個巨型數組的全局變量了事(不是開玩笑),數組之大,遠遠超過他們曾自認爲所需的空間。程序似乎工作了,再說了,好像誰都不會用到產生越界——因此,當多年之後它的確發生的時候,程序中斷了,而某個可憐的傢伙就得一頭鑽進去,把錯誤在哪裏這個謎底給找出來。

Stroustrup認爲動態分配需要更簡單、更安全——這一塊得放到語言核心中,而不是降格爲庫函數。還必須要與初始化和清理一起協同工作,初始化和清理必須由構造函數和析構函數分別提供,以便爲所有對象提供相同的保證。

這個問題是影響了全部C++決策的一塊里程碑:對C的向後兼容性。理想情況下,對象的堆棧(heap)分配可只需忽略即可。但C的兼容性要求進行堆棧(stack)分配,因此必須對heap對象和stack對象進行區分。爲了解決這個問題,C++從SmallTalk挪用了new 這個關鍵字。創建 stack 對象只需聲明即可,像這樣: Cat x;或者帶參數的情況下, Cat x("mittens");。而創建heap 對象時,就使用new,像這樣: new Cat x; 或者 new Cat x("mittens");。利用這個約束,我們得到一個優雅而一致的解決方案。

自從判定C++的一切都做得不好且過於複雜之後,Java就產生了。具有諷刺意味的是,Java 決定把 stack 徹底拋棄了(特別是忽略了基本類型上的失敗,這點我已經在別的地方指出過了)。真好啊,既然所有對象都是在heap上分配的,區分stack和heap的分配就沒有必要了。他們可以輕易說 Cat x = Cat() 或者 Cat x = Cat("mittens")。或者甚至更好地,用聯合的類型引用來消除重複(不過那樣——還有像閉包(closure)之類的其他特性——就顯得“太長”了。因此我們反而離不開Java的平凡版;類型推導已經討論過了,但我敢打賭那不會發生,也不該發生。因爲這會在給Java增加特性的同時帶來問題)。

Guido Van Rossum (Python的創建者)採用了一個最小化的方案——經常爲人所痛斥的空白的使用,正說明了他對語言簡潔性的追求。既然 new 關鍵字不再必要,他就省去了,好像這樣: x = Cat("mittens")。Ruby 也可能用了這種方法,不過Ruby其中一個主要的約束是儘可能追隨Smalltalk,因此在Ruby是這樣的:x = Cat.new("mittens") 。但Java以貶低C++做事的方式爲準則,以至於用了new 這個關鍵字成了一個迷了。自研究了該語言在其他地方所做的決策後,我的猜測是,他們是不是從來就沒有意識到,這東西根本就是可有可無的?

因此這就是我所說的語言考古學的意思。我希望人們能用一個更好的視角來看待語言設計,並在學習一門編程語言時,能有更多的批判性思維過程。

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