文 / Peter Seibel 譯 / 郝培強
本文是Common Lisp專(zhuān)家Peter Seibel對Google公司首席Java架構師Joshua Bloch的訪(fǎng)談,談到API對設計流程的影響和Google的Java觀(guān),以及數學(xué)、散文與程序員的關(guān)系。
數學(xué)與程序員的關(guān)系

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