文 / Peter Seibel 譯 / 郝培強
本文是Common Lisp專家Peter Seibel對Google公司首席Java架構(gòu)師Joshua Bloch的訪談,談到API對設計流程的影響和Google的Java觀,以及數(shù)學、散文與程序員的關(guān)系。
數(shù)學與程序員的關(guān)系
Bloch:我覺得是思想方式,學不學數(shù)學都能擁有這種思想。但是學一下確實有好處。我曾有個同事叫madbot,Mike McCloskey。他很懂數(shù)學,但是沒有學過數(shù)論。他重寫了BigInteger的實現(xiàn)。原來的實現(xiàn)是C語言函數(shù)包的封裝,他發(fā)誓用Java重寫,要達到基于C語言版本的速度。后來他做到了。為此他學了大量的數(shù)論知識。如果他的數(shù)學不行,他肯定搞不定這個項目,而如果他本來就精通數(shù)論,就無需費力去學習了。
Seibel:但是,這本來就是個數(shù)學問題啊。
Bloch:對,這個例子不恰當。但是,我相信即使是跟數(shù)學無關(guān)的問題,學習數(shù)學培養(yǎng)出的思維方式對編程來說也是必不可少的。例如,歸納證明法和遞歸編程的關(guān)系非常緊密,你不理解其中一個,就不可能真正理解另外一個。你可能不知道術(shù)語基本情況和歸納假設,但是如果你不能理解這些概念,你就沒有辦法寫出正確的遞歸程序。所以,即使是在與數(shù)學無關(guān)的領域內(nèi),不理解這些數(shù)學概念的程序員也會遇到很多困難。
你剛才提到了微積分,我覺得它不那么重要??尚Φ氖沁@么多年來似乎已經(jīng)成為了一種思維定勢了,只要你受過大學教育,那么人們就認為你應該懂微積分。微積分中有很多美妙的思想,可以讓人展開無窮的想象。
但是,你可以以連續(xù)或者離散這兩種不同的方式思維。我覺得對程序員來說,精通離散思維更為重要。例如我剛提到的歸納證明法。你可以證明一種假設對所有整數(shù)都成立。證明過程就像施魔法一樣。首先證明它對一個整數(shù)成立,然后證明針對這個整數(shù)成立意味著針對下一個整數(shù)也成立,這樣就能證明它適用于全部整數(shù)。我認為對程序員來說這比理解極限的概念要重要得多。
好在我們無需選擇。大學課程里這兩樣都教得不少。所以即使你用微積分用得沒離散數(shù)學那么多,學校里還是會教授微積分的。但是我認為離散的東西比連續(xù)的東西更重要。
散文與程序的關(guān)系
Seibel:前面你提到寫程序和寫散文有許多相似之處。盡管數(shù)學和計算機、編程的聯(lián)系一直很緊密,但是不是可以認為,寫Web框架或者基于Web框架的Web應用程序所需要的技能跟寫作的關(guān)系更為緊密呢?
Bloch:是啊。前面你提到Java程序員有兩個不同的社群。編寫庫、編譯器和底層框架的社群,更需要數(shù)學知識。而如果你是在底層框架之上編寫Web應用程序,那么必須了解如何進行溝通,言語上的、視覺上的溝通都需要了解。遇到那些令我操作失誤的網(wǎng)站我就很惱火。顯然有些人完全沒有考慮過用戶怎么使用他們的產(chǎn)品。所以實質(zhì)上,編程能力是一系列不同技能的結(jié)合。你擅長哪些技能,決定了你擅長編寫什么樣的程序。但是,即使是庫、編譯器以及底層框架也需要代碼可讀、可維護。如果你不擅長寫作,你就很難達到你的目標。
API對設計流程的影響
Seibel:你設計軟件的流程是什么樣的?打開Emacs就開始寫代碼,然后改來改去直到程序?qū)懞茫窟€是坐到沙發(fā)上拿著一打紙先列個提綱?
Bloch:很多年前,我在OOPSLA(譯者注:面向?qū)ο缶幊獭⑾到y(tǒng)、語言和應用國際研討會。)上作了一個演講,題目是“如何設計一個好的API,以及這為什么很重要”。網(wǎng)上可以找到這個演講的幾個版本。它很好地解釋了我的設計流程。
最重要的是了解你到底要設計什么,也就是你要解決的是什么問題。需求分析的重要性怎么強調(diào)也不過分。有人認為:“噢,需求分析呀。跑到顧客那邊問問他需要什么。得到客戶的答案不就成了嘛。”
事實絕非如此。這不僅是一個協(xié)商的過程,而且是一個理解的過程。許多顧客不會告訴你問題,而會告訴你一個解決方案。例如,顧客可能會說:“我需要你給這個系統(tǒng)加上以下17個特性。”那么你必須問:“為什么?你想用這個系統(tǒng)做什么?你期望它怎么發(fā)展?”等等。你要來來回回好幾次,直到弄明白顧客真正需要軟件去做的所有事情。這些就是用例。
這個階段最重要的事情就是提出好的用例。一旦有了用例,你就有了用來比較所有備選解決方案優(yōu)劣的基準。你可以花大量的時間去改進用例,因為一旦用例錯了,你就徹底失敗了,所有后續(xù)的流程都會徒勞無功。
我見過這樣的事。有人找來一幫聰明人,還沒搞清到底要做個什么樣的系統(tǒng),就開工了。辛苦地工作了6個月,寫出來247頁的系統(tǒng)規(guī)范文件。這是最糟糕的情況。因為6個月后他們精確制定出來的系統(tǒng)可能毫無用處。他們往往會說:“我們已經(jīng)投資了那么多,制定出來規(guī)范文件,我們必須把這個系統(tǒng)做出來。”所以他們創(chuàng)造了一個沒有任何用處的系統(tǒng),這個系統(tǒng)也從未投入使用過。多恐怖啊。如果沒有用例就做好了軟件,那么當你試圖做點非常簡單的操作時就會發(fā)現(xiàn):“哦,我的天,像選擇一個XML文檔并打印這么簡單的事情,需要這么多的代碼啊。”這是很恐怖的。
所以先獲取這些用例,然后編寫骨架API。骨架API應該很短很短,也就一頁紙的內(nèi)容吧,一般正好是一頁,無需非常精確。你要聲明包、類和方法,如果還不清楚他們應該是什么樣的話,可以放一句話的描述。不過這不是產(chǎn)品發(fā)布要求的那種質(zhì)量文檔。
中心思想就是在這個階段保持敏捷,逐步完善API,使其滿足用例,為原始的API添加代碼,看是否可以滿足需求。真是不可思議,很多事情事后看真是太淺顯了,但設計API的時候,甚至是構(gòu)思用例時,你還是會犯各種錯誤。用代碼實現(xiàn)用例時你會說:“哦,我的天,全都錯了。類太多了。這些可以合并,這些需要拆開。”或者類似這樣的話。好在API文檔只有一頁長,改起來也很容易。
你對API越來越有信心,代碼也就越寫越長。但是,核心原則是,先寫使用API的代碼,然后再寫實現(xiàn)它們的代碼。因為,如果實現(xiàn)代碼被廢棄,之前的工作就都白做了。事實上,應該在給出設計規(guī)范前寫API的代碼,否則你可能把時間浪費在給最后完全不需要的東西設計規(guī)范上。這就是我設計軟件的方法。
Seibel:設計Java集合類這樣的,一個具體的自包含的API,設計規(guī)范需要有多具體?
Bloch:我敢說比你想的要粗略多了。任何復雜的編程都需要API設計,因為大程序都需要模塊化,你必須設計模塊之間的接口。
優(yōu)秀的程序員把問題分塊,孤立地去看他們。這樣做的理由有幾條。比如,你可能會在無意中創(chuàng)造出好用的、可重用的模塊。如果你寫一個單一的系統(tǒng),它越來越大,等你想分塊的時候,就無法找到清晰的邊界,最后系統(tǒng)就變成了一個無法維護的垃圾。所以我斷言,無論你是否把自己看成API設計者,把問題分塊都是最好的編程方法。
這就是說,編程的世界非常廣闊。如果對你來說編程就是寫HTML代碼,那么這也許不是最好的編程方法。但是,我認為對于大多數(shù)編程來說,這就是最好的方法。
Seibel:所以你希望系統(tǒng)由不同的模塊松散地耦合在一起。要達到這樣的目標,現(xiàn)在有兩種不同的看法。一種是坐下來實現(xiàn)設計模塊間的API,像你前面提到的那樣。另外一種是,“構(gòu)建可運行的最簡系統(tǒng),然后毫不留情地重構(gòu)”。
Bloch:我不認為這兩種方法有什么沖突。某種程度上,我談的就是測試先行編程,以及對API的重構(gòu)。如何測試你的API呢?在實現(xiàn)API之前編寫它的測試用例。雖然我還不能運行用例,但我在進行測試先行的編程:實現(xiàn)用例后看API是否能完成任務,我用這樣的方法測試API的質(zhì)量。
Seibel:也就是說你寫好使用API的用戶代碼,然后評審代碼:“這就是我要的代碼嗎?”
Bloch:對!有時候你都不用走到評審用戶代碼的這個階段。寫代碼的時候可能就會有感悟,“寫不出來,我忘了這部分API的功能了。”或者“這代碼寫起來太乏味了,一定哪里出錯了。”
這跟你多么優(yōu)秀無關(guān)。不用API寫代碼,就不可能看出API有什么問題。設計了一個東西,使用了才知道:“哦,錯的這么離譜。”如果這是在你浪費大量時間基于這個API寫了無數(shù)代碼之前的話,那么這就是一個重大的勝利。所以,我談得更多的是測試先行編程和對API的重構(gòu),而不是重構(gòu)API的實現(xiàn)代碼。
說到能夠運行的最簡程序,我完全贊同這種提法。API設計有一條基本原則:疑則不用。它必須是完全滿足你關(guān)心的所有用例的最簡系統(tǒng)。而不是說“把亂七八糟的代碼堆在一起”。有很多格言警句說明了這點。我最喜歡的一條是:“簡單沒那么容易做到。”坊間認為就是Thelonious Monk說的,實際不是,是誤傳。
沒人喜歡爛軟件。人們提倡“構(gòu)建可運行的最簡系統(tǒng),然后毫不留情地重構(gòu)”,而不提倡“寫垃圾代碼”,更不會說“不要做前期設計”。我曾跟Martin Fowler討論過這個問題。他堅信,只有仔細推敲要做的東西,系統(tǒng)才會有合理的形狀和結(jié)構(gòu)。他說過,“不要在寫代碼前先寫下247頁的設計規(guī)范。”我很贊同。
我不贊同Martin的一點是:我認為測試不能用來取代文檔。只要你寫了別人編程時可以利用的代碼,你就需要做出精確的說明,而測試確保這些代碼符合你給出的說明。
所以兩大陣營確實有些不同意見,但是我認為他們之間的鴻溝沒有某些人想象的那么大。
Seibel:既然你提到了Fowler,咱們就聊聊他。他寫了很多關(guān)于UML的書,你把UML當設計工具用過嗎?
Bloch:沒有。我覺得用UML做些圖表讓其他人理解起來可能更容易。但是說實話,我根本記不住那些組件應該是方的還是圓的。
Google是否可以多用點Java
Seibel:作為Google公司里面的Java程序員,你有沒有想過Google是否可以多用點Java?如果不考慮現(xiàn)實因素,假如輕揮一下魔棒就可以把Google所有的C++代碼用Java代替,這樣行嗎?
Bloch:某種程度上是可以的。系統(tǒng)的大部分都可以用Java編寫,而且現(xiàn)狀,也是逐漸往這個方向發(fā)展的。但是對系統(tǒng)的絕對核心,例如索引服務器的內(nèi)循環(huán)來說,性能上的一丁點兒提升都有巨大的價值。當這段代碼運行在很多機器上的時候,你讓它稍微快一點,那么無論是在經(jīng)濟上,還是從環(huán)保角度看,都會獲得很大的收益。所以有些代碼你恨不得用匯編來寫,匯編就比C語言更好嗎?
我不是對某件事物特別虔誠的那種人。能用就好。我寫了20年的C語言代碼。從消耗多少程序員的時間的角度來看,使用更現(xiàn)代的編程語言更有效率,而且更現(xiàn)代化的編程語言更安全、更便利,表達能力更強。在大多數(shù)情況下,程序員的時間比計算機的時間更寶貴。但是當你的程序運行在成千上萬臺機器上的時候,就完全不同了。所以我們寫的有些程序,使用那些可能不那么安全的語言,榨出每一點值得榨出的性能?,F(xiàn)在程序員們使用的現(xiàn)代語言效率都差不多,如果有人說他們的語言效率高十倍,那么多半是在騙你。
但是從工程師寫程序耗時的角度去看,差異很大。首先,更現(xiàn)代的語言已經(jīng)排除了大量的錯誤實踐。其次,它們包含了大量的工具,可以提高工程師的工作效率。可以說這是一種文化,是人們在學校學的語言。但是它也是工作中的基礎工程問題。例如,假如一種語言有宏處理器,那么就很難給它寫出好的工具。解析C++比解析Java要難多了。
現(xiàn)在,Google用Java寫的代碼比以前多多了。我不知道具體的數(shù)量,但就算還沒有達到臨界點,估計也快了。不過,各種語言都有多少行代碼和在各種語言下執(zhí)行多少個循環(huán)是有很大區(qū)別的。試圖把索引服務器的內(nèi)循環(huán)用Java改寫很愚蠢,不值得稱道。如果你是初創(chuàng)一個公司要做類似的事情,可以用Java或者其他現(xiàn)代的安全的語言來寫大部分代碼,但是在不需要它們的時候,不要用它們。我們有自己的工程基礎架構(gòu)。代碼庫、監(jiān)控工具等所有的東西都維系著它。就算Java最終不能獲得同等的地位,也會在這些系統(tǒng)中有很多用處,這就不錯。我剛到Google的時候,還不是這樣的。
如果很早就著手建立公司的DNA,就能夠獲得巨大的成功,但是這也令他們很難換掉那些早期應用良好、現(xiàn)在已經(jīng)過時的技術(shù)。我記得1982年左右,我在約克鎮(zhèn)高地的IBM研究中心實習的時候,那里的主流還是批處理系統(tǒng)。甚至當他們已經(jīng)開始做分時系統(tǒng)的時候,他們還用虛擬讀卡機(編程卡片)、虛擬打孔器這樣的術(shù)語交流。什么東西都用80列的記錄。而DEC一直將思維禁錮在分時系統(tǒng)上。我估計微軟也面對這樣的問題,就是他們的思維能否超越桌面PC系統(tǒng)。
Seibel:20年內(nèi),人們將會談論Google為何只能在互聯(lián)網(wǎng)上賣廣告。
Bloch:沒錯。畢竟,在Google還有一部分人認為Java太慢而且不可靠。有這種看法的原因很顯然,那就是1999年左右發(fā)布的用于Linux的Blackdown Java(譯者注:一個非官方移植的虛擬機),它確實又慢又不可靠。既有的看法總是很頑固的,很難改變。事實上Google在很多核心功能上使用Java,甚至包括廣告。
所以某種程度上,他們知道Java既快又可靠。但是在實際的搜索流程中,對機器循環(huán)最敏感的領域,所有的東西都基于C++,這么做很明顯的一個原因就是公司的“基因”。這將在很長一段時間里影響著我們。
Seibel:你實際編程中用哪些工具?
Bloch:我就知道你遲早要問這個問題,我是老幫菜了,提這個都覺得丟人。Emacs的鍵盤快捷方式在我的腦子里面已經(jīng)根深蒂固了。而且我喜歡寫小的程序,代碼庫之類的。所以,我寫代碼的時候幾乎不用現(xiàn)代的工具。但是我知道,很多現(xiàn)代的工具可以提高效率。
寫大程序的時候我確實使用IntelliJ,因為我們整個團隊都在用,但是我不是這方面的專家。這個工具給我留下了深刻印象,我喜歡這些工具對代碼做的靜態(tài)分析。我找用Eclipse、NetBean以及FindBug的人來幫我審閱《Java解惑》,書中的很多錯誤陷阱都可以被這些工具自動檢測到,太了不起了。