JAVA命運(轉載)

Java的命運

熱1已有 59 次閱讀  2011-03-14 22:41


文 / Peter Seibel  譯 / 郝培強

本文是Common Lisp專家Peter Seibel對Google公司首席Java架構師Joshua Bloch的訪談,談到他所遇到的最糟糕的Bug以及Java的命運。

最糟糕的Bug

Seibel:我們聊聊調試吧。你遇到的最糟糕的Bug是什麼?joshua

Bloch:提起Bug我立馬就想到了一個,這個Bug很嚴重,而且很搞笑。那是90年代初,我在匹茲堡的Transarc公司工作時。我在很緊的工期下提交了一個事務共享內存的實現。我在限期內完成了設計和實現,甚至還在過程中做出了幾個可重用的組件。但是這麼匆忙地寫了很多新代碼,我還是挺擔心的。

爲了測試這些代碼,我寫了一個叫做“亂撞”的很長的程序出來。它運行了大量的事務,每個事務又包含了嵌套的事務,嵌套到可以嵌套的最大深度。每個嵌套事務都可能會加鎖,以遞增的順序讀取共享數組裏面的幾個元素,對每個元素都加入點東西,保持數組中所有元素的和爲0,還是不變量。這些事務要麼提交,要麼取消,如90%的提交,10%的取消,其他比例也可以。多個線程同步運行於這些事務之上,長時間地訪問數組。因爲我測試的是一個共享內存機制,所以我同時運行多個有多個線程的“亂撞”程序,每個有自己的進程。

在一般的併發級別下,“亂撞”輕鬆過關。但是當我真正調高併發級別時,我發現“亂撞”偶爾,僅僅是偶爾,無法通過一致性檢查。我不知道這是怎麼搞的。這隻能是我的錯,因爲新代碼都是我一個人寫的。

我花了大約一個星期,痛苦地爲每個組件寫了徹底的單元測試,所有的單元測試都通過了。然後我爲每個內部數據結構寫了詳細的一致性檢查,這樣我就可以在每次變化後調用這些一致性檢查,直到測試失敗爲止。最後,我終於發現一個底層的一致性檢查失敗了,這個問題無法重現,但是某種程度上可以幫助我分析問題出在哪裏。最後,我得出了確實的結論——我的鎖根本不工作。兩個事務鎖定、讀寫同一個值的時候,產生了併發的讀—修改—寫回操作,而後一次寫入毀掉了第一次的寫入。

我編寫了自己的鎖管理器,所以我懷疑是它出了問題。但是鎖管理器輕鬆地通過了測試。最後,我覺得問題不在鎖管理器,而是它依賴的互斥體的實現!那時候操作系統還不支持多線程,我們需要寫自己的多線程包。原來負責互斥體代碼的工程師,不小心把我們的Solaris的線程實現中的lock和try-lock的彙編代碼的標籤弄混了。所以,每次你以爲你在調用lock的時候,其實調用的是try-lock,反之亦然。也就是說當真的有爭用發生的時候——在當年其實是很罕見的——第二個線程直接就進入了第一個線程的臨界區,因爲第一個線程也沒有鎖住。搞笑的是,這也就是說,整個公司幾個星期都在運行沒有互斥體的程序,而且誰都不知道。

Knuth有句關於測試的名言,Bentley和Mcllroy的精彩論文“Engineering a Sort Function”中曾經引用過,大概意思是說,做測試時,要不憚以最大的惡意來推測所要測試的代碼的錯誤。做這些測試的時候,我就是這麼做的。但是這樣會把所有東西糾結在一起,更難找到Bug。首先,併發的時候很難這麼做,往往完全無法復現場景。其次,到最後可能會發現你的核心假設是錯的。喜歡喊“耶,這語言出錯了”或者“系統出問題了”是新手乾的事兒。但是在這裏,我依靠的基石——互斥體,確實出問題了。

Seibel:也就是說Bug不在你的代碼中,但是同時你只能對你的代碼進行徹底的單元測試,因爲你沒有別的辦法,只能去檢查自己的代碼。你覺得這些測試是不是可以,或者說應該讓互斥體代碼的作者來寫,這樣你就不用浪費一個星期,節省了一半的測試量,而且也可以找到這個Bug。

Bloch:給互斥體代碼加一個好的自動化單元測試肯定可以避免讓我遭受那些痛苦,不過注意那可是90年代初。我想都沒想過要抱怨那個工程師沒寫個好的單元測試。即使是今天,爲併發工具寫單元測試還是一種藝術形式。

Java的命運

當你改進一個成熟語言的時候,你必須更加仔細地考慮能力和複雜度之間的平衡。

Seibel:既然你說到這裏,Java是否逃脫了滅亡的命運了?它變複雜的速度是不是比變好的速度更快呢?

Bloch:這個問題不好回答。具體說來,Java5加入了比我們設想的更多的複雜度。將泛型特別是通配符加到語言中到底有多複雜我也說不好。我得爲有功勞的人說句話,GrahamHamilton真是了不起,那時候他就想明白了一切,而我不明白。

有趣的是,他抗爭多年,希望阻止泛型進入Java語言中。但是在泛型被成功地阻擋在Java外的這些年裏變體的概念,也就是通配符的隱含意義流行了起來。如果它們來得更早,沒有變體,也許我們現在可以有一個更簡單的、更容易跟蹤的語言。

引入通配符有實際的好處。子類化和泛型之間根本就是阻抗不匹配的,通配符盡力在彌合這種不匹配。但是這麼做又顯著地增加了複雜度。有些人認爲在聲明空間,而不是用戶空間,變體是更好的解決方案,但我不太相信這一點。

這仍舊懸而未決,因爲它們都還沒經過在真實世界裏海量的程序員們的測試呢。一些語言經常只在小範圍內獲得成功,人們會說:“噢,這些語言很不錯,只是可惜沒有成爲世界範圍成功的語言。”但是這往往是有原因的。希望使用Scala或者C#4.0,這樣的聲明空間變體的語言可以徹底解決這一疑問。

Seibel:那麼是什麼推動Java引入泛型呢?

Bloch:沒看起來那麼精彩啦,我們的新聞報道是可信的。我的思維模式是,“嗨,集合多半都應該是同質的——一組字符串,一個從字符串到數字的映射,等等。而現在默認情況下集合是異質的:它們都是對象的集合,取出時都需要類型轉換,這簡直是胡鬧。”如果我可以告訴系統,這是一個從字符串到數字的映射,它會幫我做類型轉換,而且會在編譯期間幫我盯着,防止我做錯什麼,那不是挺好的嗎?它可以抓到更多的錯誤——它可以包含高層的類型信息,看起來是件好事兒。

我認爲泛型和其他加入到Java5的語言特性一樣,我們只是讓語言去做以前我們要手工去做的事情而已。某些情況下我堅信:foreach就是好。它所做的就是對你隱藏遍歷器和索引變量帶來的複雜性。代碼更短,概念也不復雜。從某種意義上說,它的概念更簡單,因爲我們爲數組和其他的集合創建了這種僞多態機制,你可以遍歷一個ArrayList或者一個數組,而無需關心你遍歷的是什麼類型。

這種思想不能適用於泛型的主要原因是,它是對已經很複雜的類型系統的大擴展。類型系統是很微妙的,修改它們可能對語言帶來深遠的、難以預期的影響。

我認爲得到的教訓是,當你改進一個成熟語言的時候,你必須更加仔細地考慮能力和複雜度之間的平衡。而且,實際上,複雜度跟語言的功能數量間至少是平方級關係。爲一門老語言加上了一個新的功能,通常就意味着爲它加入了一大堆複雜度。當一種語言已經達到或接近程序員理解能力的極限時,那麼你加入任何複雜性進來都會加劇理解的難度。

語言更復雜後就會消失嗎?不會。我認爲C++早已超越了它的複雜度極限,但還是有很多人用它編程。可這實際上是逼人們只使用其中一個子集。所以我認識的每個用C++的公司都說:“對,我們用C++,但是用的是多繼承,不用操作符重載。”有很多功能你完全不用,因爲使用它們會造成代碼太複雜。即使不得不用那些功能,我認爲也實在沒什麼好處。那樣的話,程序員就讀不懂別人的代碼,也就不存在“程序員的可移植性”了。

Seibel:如果去掉泛型,現在Java會變得更好用嗎?

Bloch:我不知道。我還是喜歡泛型。泛型能幫我找到代碼中的Bug。泛型可以讓編譯器強制做一些限制,之前這些限制我只能放在註釋中。另一方面來說,當我看到那些瘋狂的參數類型相關的錯誤信息,當我看到像classEnum<EextendsEnum<E>>這樣的泛型類型聲明時,我就會想,顯然泛型的設計還沒成熟到可以放到Java中的水平。

我們總是太樂觀,然後搬起石頭砸自己的腳。所以我們說:“耶!我們當然可以把泛型放到Java中。在CLU的時候我們就知道泛型了。這技術25年前就有了。”最近我聽到關於閉包的類似言論,不過那是50年前的技術了。“噢,閉包很簡單,不會給語言加入任何新的複雜性。”

嗯,沒錯。但是我覺得我們從泛型這件事兒得到了教訓。在你懂得這個改動會對概念層面帶來什麼影響之前,在你可以確保軟件行業從業人員可以高效地使用新特性,而且這一新特性會讓他們活得更好之前,你不應該給語言加入這一特性。

如果早知道程序員們對泛型是這個反應,我們肯定不會把它加到Java裏。這是不是說我們就完全不會搞泛型?不,我不這麼認爲。我認爲泛型確實很好。主要是因爲大多數集合是同質的,而不是異質的,同質的集合處理起來是比較方便的。多數情況下類型轉換都不合適。轉換可能會失敗,而且讓你的程序不再優雅。我想你知道這是什麼集合,它應該自動符合你的這些需求。但是,是不是這就意味着你應該承受我們現在承受的這種複雜度?不,我想我們只是沒有處理好泛型。

Seibel:關於泛型有來自於用戶的壓力嗎?有人抱怨泛型的缺點干擾他們寫程序了嗎?

Bloch:有沒有工程師大罵泛型的缺點?不,沒有,他們沒有抱怨過。如果因爲泛型簡潔就把它們加進來,那我會內疚的。因爲當時我們以爲這麼做是對的。

有人說,很多工程都是扯淡。有人要求我們加入foreach嗎?沒有。他們沒有要求我加入。但是我就知道這是應該做的。我對了——每個人都喜歡它。但是我覺得我們行業內的一大問題就是,在工程領域,做一個東西,僅僅因爲它簡潔,僅僅因爲它是一個好的工程項目,等等。如果你不能解決真實用戶——在這裏就是Java程序員——的真實問題,那麼你就不應該加入新的特性。

James Gosling曾做過一個非常了不起的演講——“Java的感覺”。他說,給Java加入任何東西之前,都需要三個真實的用戶。不應該因爲一個東西簡潔就把它放進來。

但是人們就是想把什麼東西都放進去。工程師是做什麼的?他們就是寫代碼的。而當他們寫一個庫,或者一個語言的時候,他們就是想放各種東西進去。你需要他人的參與,需要指導的聲音,需要這些東西來幫助你完成產品,幫你在放與不放之間做出最好的權衡。因爲你可以放進去的東西總比你應該放進去的東西多。那麼是不是說所有的這些東西都不好呢?那也不是。只是你需要做出決定,某些東西是不應該放進去的。

思考Java帶來的編程經驗

Seibel:思考Java的設計並實現它,是否讓你學到了什麼跟編程有關係的東西?

Bloch:我學到的東西太多了。比如我知道了即使是想把一個很小的程序寫對也是非常難的。我把這個想法發表在了博客裏,題目是“幾乎所有的二分搜索和歸併排序都是錯的”。認爲自己程序是對的就是在愚弄自己。程序裏有大量沒解決的Bug,當然是不對的。多數情況下,程序裏的Bug都不少,它們只能免費完成任務。

我知道,既然寫正確的程序那麼難,我們就應該盡力去幫助大家。所以能減少Bug的所有東西都是好的。這就是我是靜態類型和靜態分析的信徒的原因,任何可以減少某個特定類別Bug的東西都是非常好的,任何可以讓程序員的工作更輕鬆的東西都是好的。

我更加確信有好的API文檔是很重要的。人們很少提及Javadoc對這個平臺的成功所起的作用。好的API文檔永遠都是Java文化的一部分,也許是因爲Javadoc從一開始就存在吧(譯者注:所以人們低估了Javadoc的作用)。

我一直信奉“簡單就是美”這句話,現在更是如此。我不斷看到更復雜的東西最終被證實是有害的,只是有的時間長點兒,有的時間短點兒。我設計的時候,會仔細看着我的“複雜度計”,一旦複雜度要到紅線了,就需要重新設計了。

偶爾我會遇到不相信這些的人們,他們會說:“Josh你太傻了,你怎麼就是不明白;這纔是應該做的,可惜你就是搞不懂。”我就是不信這些。我覺得事情一旦複雜起來,那麼一定有什麼地方錯了,也許到了尋找更簡單的方法的時候了。

Tony Hoare的圖靈獎獲獎感言中有一句充滿了大智慧的話,講的是設計一個系統的兩種方式:“一種是儘量簡單,這樣顯然不會有什麼問題;另外一種是,儘量複雜,這樣沒什麼問題會很顯然。”

後面的內容同樣飽含智慧,但是知道的人不多:“第一種方法其實更難。它需要從複雜的自然現象發現簡單物理規律的那種技能、投入、洞察力,甚至是那種靈感,同時還需要你能接受你的目標受限於物理、邏輯和科技的約束,以及在目標間有衝突的時候可以妥協。委員會無法做到這些,除非已經完全來不及了。”

Seibel:你是否想過在職業生涯中再次更換你的主要語言,還是準備退休前一直做Java?

Bloch:我自己也不知道。我從C語言轉向Java有點突然。從研究生畢業時起,到1996年,我主要使用C語言編程,然後一直使用Java直到現在。我已經預見到我可能要更換到其他語言了。但是我不知道是什麼語言。也許它還不存在。我覺得產生一個新編程語言的時機已經成熟,但是同時我又覺得平臺的慣性也比以前更大了。現代的平臺不僅僅是一個語言和一些庫,它包括很多工具,是一個虛擬機,一個龐然大物。創建一個完整的新平臺的前景比以前更不樂觀。

我不知道將出現什麼。但是我認爲如果改變我的主要語言是對的,那麼我就會這麼做。我想盡力保持開放的心態。我想嘗試更多的語言。我最近沒時間做,但是以後還是會做的。

Seibel:列出幾個你想嘗試的語言吧?

Bloch:我想試試Scala,雖然我懷疑它是否能成爲未來的新寵。我很崇敬MartinOdersky。我覺得他寫的語言中有很多精妙的想法。但是我同時也認爲他加入了太多複雜的東西,太學術化了,所以很難取得世界範圍內的大成功。當然我還沒權利去評價,因爲我還沒學習過。

我還想用用Python。Scheme不是新生物,不過我也想試試。我想花幾個月,跟我兒子一起過一遍《Structure and Interpretation of Computer Programs》一定很有意思。每個人都說這是一本偉大的書。我已經買了,算是開了個頭。看完它需要一些時間。我想這就是我目前想學的。

Seibel:現在很多人在討論我們寫程序的時候,如何能把未來的多核CPU的優勢利用起來。Java顯然是第一個內建多線程機制的主流語言。你覺得Java的邏輯在多核的世界是否仍然可用?

Bloch:我想說得更深入一些。我認爲Java是現有語言中最好的。但有趣的是,現在很流行談Java是否即將死去。我覺得這基本上是扯淡。我認爲現在最好的多線程構件就在Java裏。我認爲Java將迎來複興。我不是說它是未來20年內最先進的,也不是說它是處理多核的最好方式。但是我認爲從現有的東西來看,我們是足以傲視同儕的。

(本文來源於《程序員》雜誌11年01期,內容節選自人民郵電出版社北京圖靈文化發展有限公司出版的《編程人生》一書。特此感謝圖靈公司授權。)

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