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

