我常常問(wèn)面試者,“你最喜歡的編程語(yǔ)言是什么?” 答案幾乎如出一轍,“工作中我只選擇正確的編程語(yǔ)言?!?廢話(huà),誰(shuí)會(huì)故意選擇錯(cuò)誤的語(yǔ)言呢?這顯然是為了逃避選擇一種具體的編程語(yǔ)言,以免選擇了一種我不喜歡的。
如果面試者這樣回答“我最熟悉某一種編程語(yǔ)言”,這同樣也沒(méi)有回答我的問(wèn)題。
當(dāng)時(shí)要是我的話(huà),我會(huì)這樣回答,“我最喜歡 Python,因?yàn)槭褂盟幊套屛腋械娇鞓?lè),但我只在某某情況下使用它。其余時(shí)間,我使用 XYZ...”
然而,大約一年之前,我產(chǎn)生了一個(gè)奇怪的想法:Java 適合所有的編程工作。(在你吐槽之前,我暫停一下,)這個(gè)想法根植于你感覺(jué)正確但卻與現(xiàn)實(shí)不符的一些觀點(diǎn),而且這個(gè)想法從來(lái)都沒(méi)有流行起來(lái),但不管怎樣,請(qǐng)讓我先來(lái)解釋一下。
Python 的確是我喜愛(ài)的編程語(yǔ)言,用它編程真的讓我感到快樂(lè)。它讓我的大腦感到快樂(lè),它和偽代碼是如此契合,以至于用它來(lái)工作能讓人真正感到愉悅。
多年以前,我曾經(jīng)讀過(guò) Bruce Eckel 一本頗具影響力的書(shū)《強(qiáng)類(lèi)型與強(qiáng)測(cè)試》(Strong Type vs. Strong Testing)。在書(shū)中,他聲稱(chēng)靜態(tài)類(lèi)型(他稱(chēng)為強(qiáng)類(lèi)型)是保證程序正確性的多種方式之一,如果你用單元測(cè)試去檢查其它方面(例如算法和邏輯),那么類(lèi)型也將得到檢查,因此你不妨采用動(dòng)態(tài)類(lèi)型編程語(yǔ)言,并從中獲得動(dòng)態(tài)類(lèi)型編程語(yǔ)言的優(yōu)勢(shì)。
Bruce 使用了 Python 來(lái)說(shuō)明他的代碼,其成效非常顯著:我決定從今往后寫(xiě)什么都用 Python。不幸的是,工作中一個(gè)大型 Java 項(xiàng)目進(jìn)展到中途時(shí),我和同事一致認(rèn)為這個(gè)程序應(yīng)該用 Python 來(lái)寫(xiě),也許有一天,我們會(huì)找到一個(gè)很好的借口來(lái)重寫(xiě)這個(gè)程序。
不到一年時(shí)間,幾件事情讓我的想法來(lái)了一個(gè)180度的大轉(zhuǎn)彎:
- 在一家公司里,我寫(xiě)了一個(gè)模擬器,這樣就可以讓我的 Java 服務(wù)獨(dú)立運(yùn)行而無(wú)需一個(gè)全功能的網(wǎng)站。在這個(gè)模擬器中,我運(yùn)行一些腳本測(cè)試包括失敗在內(nèi)不同的情景。由于 Java 6 已經(jīng)內(nèi)置了 JavaScript 的解析引擎以及很多人都了解這門(mén)腳本語(yǔ)言的緣故,我決定使用 JavaScript 來(lái)編寫(xiě)這些腳本。我認(rèn)為使用腳本語(yǔ)言可以讓我們和測(cè)試人員很容易地編寫(xiě)測(cè)試。一位名叫 Justin Lebar 的實(shí)習(xí)生認(rèn)為我們應(yīng)該只使用 Java。模擬器是用 Java 寫(xiě)的,測(cè)試腳本為什么不用 Java 呢?它已經(jīng)在那里了,這是大家都知道的。我們?nèi)耘f堅(jiān)持使用 JavaScript。在整個(gè)過(guò)程中,我不得不寫(xiě)各種各樣的的代碼讓 Java 和 JavaScript 相互溝通。你知道,因?yàn)闊o(wú)法定位到具體腳本執(zhí)行的所在行數(shù),這意味著不同語(yǔ)言堆棧的足跡已經(jīng)變得難以跟蹤了。測(cè)試人員無(wú)法完成任何測(cè)試。到最后,我們從 JavaScript 中一無(wú)所獲,Justin 的觀點(diǎn)無(wú)疑是正確的。
- 還是在那家公司里,我們使用 JSON 格式(順便說(shuō)一下,這是個(gè)很好的想法)來(lái)存儲(chǔ)我們的日志文件,一位同事寫(xiě)了一個(gè)名為 logcat 的 Python 程序,用來(lái)解析日志文件和輸出標(biāo)準(zhǔn)的柱狀圖報(bào)告,這個(gè)程序有許多不錯(cuò)的功能特性(包括一個(gè)二分查詢(xún)時(shí)間戳)。在我的一個(gè)私人項(xiàng)目中,也需要類(lèi)似的東西,我再次建議使用 Python。但我的搭檔 Dan Collens 則認(rèn)為應(yīng)該使用 Java,因?yàn)樗呀?jīng)在那里了,我們都了解它,而且它夠快。他最終用 Java 實(shí)現(xiàn)了它,我的搭檔是對(duì)的:它的速度極快。我對(duì) Python logcat 和 logcat 的另一個(gè) Java 實(shí)現(xiàn)作了一番對(duì)比,后者的速度大約快十倍。 使用 Python 去實(shí)現(xiàn),無(wú)論程序員節(jié)省了多少時(shí)間都無(wú)法挽回失去用戶(hù)的損失,因?yàn)楹芏嘤脩?hù)每次不得不等上十倍的時(shí)間才可以拿到分析日志報(bào)告。
- 最后一個(gè)例子,我編寫(xiě)了一個(gè)簡(jiǎn)單的程序用于搭建一個(gè) Web 界面。我覺(jué)得應(yīng)該使用 Python,但是這樣做的話(huà),我需要找出如何利用 Python 的類(lèi)庫(kù)來(lái)為 Web 頁(yè)面提供服務(wù)的辦法。我在此之前已經(jīng)在 Java(采用 Jetty)中實(shí)現(xiàn)過(guò)這個(gè)功能了,所以使用 Java 的話(huà),我可以在更短的時(shí)間內(nèi)將其做好并且運(yùn)行起來(lái)。這個(gè)時(shí)候,我開(kāi)始意識(shí)到,隨著我在第三方 Java 庫(kù)上面的知識(shí)積累以及在實(shí)用工具方面的不斷成長(zhǎng),使用其它語(yǔ)言的成本已經(jīng)變得越來(lái)越高了。我需要把這些事情搞清楚再寫(xiě)一遍,而不是從已有的項(xiàng)目中復(fù)制和粘貼。注意,這不僅適用于 Java,它也適用于任何使用單一語(yǔ)言的場(chǎng)景。
對(duì)于 Java,最大的爭(zhēng)論在于它的語(yǔ)法繁瑣??赡馨桑怯衷鯓幽??我認(rèn)為真正的爭(zhēng)論是寫(xiě) Java 代碼需要更長(zhǎng)的時(shí)間。我懷疑在最初的10分鐘之后這是很真實(shí)的。當(dāng)然你不得不寫(xiě) public static void main,但那又能花多少時(shí)間呢?當(dāng)然你也必須這樣寫(xiě):
Map<String,User> userIdMap = new HashMap<String,User>();
而不是:
userIdMap = {}
如果從一個(gè)更大的情景來(lái)看,這算長(zhǎng)嗎?一天之中能多花多少分鐘,2天呢?而在 Python 更加接近實(shí)際的代碼看起來(lái)是這樣:
# Map from user ID to User object.
userIdMap = {}
(如果沒(méi)按照上述方式編寫(xiě) Python 代碼,那么你就可能面臨更大的問(wèn)題。缺乏文檔的 Python 程序是很難維護(hù)的。)我們面臨的問(wèn)題是,程序員認(rèn)為不用動(dòng)腦筋的工作是痛苦且浪費(fèi)時(shí)間。我在這里引用某個(gè)論壇中的一條帖子,很好地證明了這一點(diǎn):
當(dāng)你不得不為極其明顯的對(duì)象增加類(lèi)型聲明時(shí),沒(méi)有比這更無(wú)聊的了,比如 Foo x = new Foo()。– @pazsxn
實(shí)際上不是這樣,額外鍵入 Foo 并非“很無(wú)聊”。只是三個(gè)字符而已。由于這項(xiàng)工作無(wú)需動(dòng)腦,致使其副作用被嚴(yán)重夸大了,但這只是小事一樁。程序員真正不愿意做的事,是為了處理列表事項(xiàng)而編寫(xiě)某種類(lèi)型的命令:
if command = "up":
up()
elif command = "status":
status()
elif command = "revert":
revert()
...
因此,他們將會(huì)編寫(xiě)一些自認(rèn)為聰明的自動(dòng)調(diào)度代碼,但這樣做需要花費(fèi)更長(zhǎng)的時(shí)間,而且肯定會(huì)讓后來(lái)的程序員產(chǎn)生迷惑:revert() 方法是怎樣調(diào)用的呢。即便如此,程序員卻錯(cuò)誤地覺(jué)得好像這樣做會(huì)是在節(jié)約時(shí)間。這其實(shí)是一個(gè)動(dòng)態(tài)語(yǔ)言的陷阱。它讓你自我感覺(jué)更有效率,但除了編寫(xiě)一個(gè)新程序的前10分鐘之外,其他時(shí)間并非如此。你只是通過(guò)手工編寫(xiě)了一些愚蠢的調(diào)度代碼,到最后,你還得在那些真正的工作上花費(fèi)精力。

(題外話(huà):關(guān)于使用 vim 編輯器編寫(xiě)代碼這一問(wèn)題,我對(duì)不同選擇的理解有誤。我感覺(jué)使用 vim 非常有效率,似乎代碼在終端上飛舞,而使用 Eclipse 讓我感覺(jué)遲鈍,這證明了我的選擇更傾向于效率。但顯然我的收益是建立在一定損失的基礎(chǔ)之上的,我不得不查看誰(shuí)調(diào)用了一個(gè)特定函數(shù),或者不得不手動(dòng)查看一個(gè)對(duì)象的方法。動(dòng)態(tài)語(yǔ)言的支持者們有他們自己的選擇,我承認(rèn)在這一問(wèn)題上我同樣是錯(cuò)的。)
那么到底為什么要選擇動(dòng)態(tài)語(yǔ)言呢?如果你和我比賽去寫(xiě)一個(gè)簡(jiǎn)單的博客系統(tǒng),你用 Python 的話(huà),使用 pickling 和 whatnot 你在30分鐘內(nèi)就會(huì)讓事情變得有趣,而我用 MySQL 構(gòu)建的話(huà)要花2天時(shí)間。許多語(yǔ)言方面的選擇是基于類(lèi)似這樣的微不足道的競(jìng)賽。但在大約兩周開(kāi)發(fā)之后,當(dāng)我們都需要增加一個(gè)功能時(shí),我花的時(shí)間最多和你一樣,而且我不需要在如何讓我的系統(tǒng)應(yīng)對(duì)大量用戶(hù)上花費(fèi)任何時(shí)間,或者追蹤那些令人困惑的無(wú)效語(yǔ)句,其原因只是你的一個(gè)函數(shù)名拼寫(xiě)錯(cuò)誤導(dǎo)致語(yǔ)句執(zhí)行中斷,或者想弄明白這個(gè)請(qǐng)求參數(shù)到底包含什么東西,悲催呀。
黑客級(jí)程序員蔑視“規(guī)范和要求嚴(yán)格的語(yǔ)言”是目光短淺的表現(xiàn)。大型、長(zhǎng)期使用且由多名程序員參與的項(xiàng)目和自用的快速原型項(xiàng)目是完全不同的東西。– Source
而且,你覺(jué)得你可能不會(huì)很快碰到程序的可伸縮性問(wèn)題?NaNoWriMo 網(wǎng)站在每年的10月31號(hào)都會(huì)當(dāng)機(jī),停止響應(yīng)好多天。在這一期間的幾個(gè)小時(shí)內(nèi),大約有60,000用戶(hù)訪問(wèn),每秒可能有4個(gè)請(qǐng)求。這個(gè)網(wǎng)站是用 PHP 寫(xiě)的。OurGroceries 的后端是用 Java 寫(xiě)的,它能處理(當(dāng)前)大約每秒50個(gè)復(fù)雜請(qǐng)求,而對(duì)于 Java 線程來(lái)說(shuō) CPU 很少超過(guò)1%。
Twitter 最近通過(guò)把搜索引擎從 Ruby 切換到 Java,將查詢(xún)速度提升了三倍。
一年之前,Joel Spolsky 發(fā)表推文:
Digg: 200MM 頁(yè)面瀏覽,500臺(tái)服務(wù)器。Stack Overflow: 60MM 頁(yè)面瀏覽,5臺(tái)服務(wù)器。我漏掉什么了嗎?
— Joel Spolsky (@spolsky) October 13, 2010
@GregB 的反饋是:
@spolsky: Digg: 200MM 頁(yè)面瀏覽,500臺(tái)服務(wù)器。Stack Overflow: 60MM 頁(yè)面瀏覽,5臺(tái)服務(wù)器。我漏掉什么了嗎?<< 這就是 PHP 的原因。
— Greg Brant (@GregB) October 13, 2010
StackOverflow 使用的是 ASP.NET。因此你會(huì)整天抱怨 public static void main,但需要搭建500臺(tái)服務(wù)器這一事實(shí)也太可笑了。動(dòng)態(tài)語(yǔ)言的缺點(diǎn)是真實(shí)存在的,代價(jià)也很昂貴,而且會(huì)一直延續(xù)下去。
那么單元測(cè)試這個(gè)觀點(diǎn)又怎么樣呢?如果你必須對(duì)你的代碼進(jìn)行單元測(cè)試,靜態(tài)類(lèi)型能為你帶來(lái)什么呢?好吧,它能加快速度,而且是大幅度的。但是編寫(xiě)和維護(hù)單元測(cè)試也需要耗費(fèi)時(shí)間。最重要的是,最常出現(xiàn)的 bug 并非單元測(cè)試都能完全覆蓋。除了少數(shù)例外情況(例如解析器),單元測(cè)試是在浪費(fèi)時(shí)間。引用我哥們的一段話(huà),“單元測(cè)試是一種冗長(zhǎng)且易于出錯(cuò)的方法,它試圖挽回由于缺乏靜態(tài)類(lèi)型注解而失去的價(jià)值,但卻以一種笨手笨腳的形式出現(xiàn),因?yàn)樗蛯?shí)際業(yè)務(wù)代碼本身是完全分離的。”
因此,我的新思路就是:做任何事都用 Java。不要試圖使用 Python 寫(xiě)一些可以快速實(shí)現(xiàn)的黑客代碼,因?yàn)椋?/p>
- 你無(wú)法從使用主要編程語(yǔ)言開(kāi)發(fā)的項(xiàng)目中復(fù)制和黏貼代碼。//實(shí)現(xiàn)代碼重用
- 開(kāi)發(fā)起來(lái)可能感覺(jué)會(huì)快一些,但這是假象。實(shí)際節(jié)省的時(shí)間非常有限,盡管有些語(yǔ)法特征的確讓人討厭。
- 我和我的同事不得不學(xué)習(xí)和掌握另一門(mén)語(yǔ)言、平臺(tái)以及一系列類(lèi)庫(kù)。
- 還有一個(gè)重要原因:很可能,這個(gè)快速實(shí)現(xiàn)的黑客代碼將會(huì)成長(zhǎng)為一個(gè)重要的工具,我沒(méi)有時(shí)間去重寫(xiě)它,因而每次使用它我都要忍受由于性能不佳和難于維護(hù)而導(dǎo)致的懲罰。
使用 Python 開(kāi)發(fā)是快樂(lè)的,我同意這個(gè)觀點(diǎn)。我熱愛(ài) Python。當(dāng)我寫(xiě)一個(gè)數(shù)獨(dú)求解程序時(shí),我會(huì)使用 Python。但是對(duì)于更大些的項(xiàng)目,它是個(gè)錯(cuò)誤的工具,而且對(duì)于和支付有關(guān)的任何規(guī)模的項(xiàng)目來(lái)說(shuō),它也是個(gè)錯(cuò)誤的工具,因?yàn)檫@可能會(huì)給你的老板帶來(lái)一定損失。
我甚至想把這個(gè)思路發(fā)揮到極致 - 使用 Java 編寫(xiě) shell 腳本。除了一個(gè)簡(jiǎn)單的包裝器之外,我發(fā)現(xiàn) shell 腳本最終都會(huì)發(fā)展到一種情景,即僅僅為了從 bash 中的一個(gè)數(shù)組中移除一些中間元素,需要我在晦澀難懂的語(yǔ)法中反復(fù)尋找方法。這是多么蹩腳的語(yǔ)言啊!錯(cuò)誤的工具呀!還是使用 Java 吧。如果你覺(jué)得在 shell 上運(yùn)行命令顯得很愚蠢,編寫(xiě)一個(gè)工具函數(shù)就可以解決這個(gè)問(wèn)題。
我已經(jīng)編寫(xiě)了一個(gè) Java 啟動(dòng)器腳本,這樣我就可以將其寫(xiě)在 Java 程序的頭部:
#!/usr/bin/env java_launcher
# vim:ft=java
# lib:/home/lk/lib/teamten.jar
我可以讓這個(gè) Java 程序執(zhí)行,并且同時(shí)去掉 .java 擴(kuò)展名。腳本提取頭部?jī)?nèi)容,編譯并緩存 class 文件,然后使用指定的 jar 包去運(yùn)行。這原本是 Python 的特有優(yōu)勢(shì):對(duì)于簡(jiǎn)單的一次性程序,就無(wú)需構(gòu)建腳本啦。
專(zhuān)注于單一的語(yǔ)言將會(huì)產(chǎn)生一個(gè)有趣的影響:激勵(lì)我完善我的個(gè)人工具函數(shù)庫(kù)(上面的 teamten.jar),因?yàn)槲业呐Σ辉傩枰稚⒌綆讉€(gè)語(yǔ)言之中了。舉個(gè)例子,我寫(xiě)了一個(gè)圖片處理的類(lèi)庫(kù)。和你在 Java 和 Python 中能找到的任何類(lèi)庫(kù)相比,這個(gè)類(lèi)庫(kù)不僅速度快而且質(zhì)量更高。這花費(fèi)了我的一些時(shí)間,但我認(rèn)為這是值得的,因?yàn)槲野l(fā)現(xiàn),我無(wú)法使用 Python 腳本更好地更好地實(shí)現(xiàn)裁剪一張圖片的功能。我現(xiàn)在可以充滿(mǎn)自信地把對(duì) Java 的投資作為我未來(lái)職業(yè)和個(gè)人技術(shù)的一個(gè)重要組成部分。
最后還有一個(gè)在眾多編譯型靜態(tài)類(lèi)型語(yǔ)言中,我為什么特別選擇 Java 的問(wèn)題。C 和 C++ 的優(yōu)勢(shì)(輕微的性能優(yōu)勢(shì),可嵌入性,適合編寫(xiě)圖形化庫(kù))不適用于我的工作。C# 挺不錯(cuò),但不是跨平臺(tái)的。Scala 太復(fù)雜了。其他語(yǔ)言像 D 和 Go 都太新了,因此我不能把工作賭在它們上面。
每當(dāng)我告訴人們我現(xiàn)在寫(xiě)什么都用 Java 時(shí),他們看起來(lái)都很恐懼的樣子。甚至有一位朋友明顯面帶厭惡的表情。但是你知道嗎,Java 是一門(mén)相當(dāng)好的語(yǔ)言,當(dāng)我進(jìn)行代碼編譯時(shí),往往在第一時(shí)間,它通常會(huì)正確地運(yùn)行。任何其它語(yǔ)言都沒(méi)有像 Java 那樣給予我心靈上的寧?kù)o。Java 就像一匹兢兢業(yè)業(yè)的寶馬良駒,對(duì)于各種類(lèi)型的應(yīng)用程序來(lái)說(shuō),它都適用。
作者:Lawrence Kesteloot,是一名自由程序員,住在舊金山,喜歡林迪舞、法語(yǔ)、旅行、以及結(jié)對(duì)編程。
感謝: Jodoo 幫助審閱并完成校對(duì)。
P.S. 如果您喜歡這篇文章并且希望學(xué)習(xí)編程技術(shù)的話(huà),請(qǐng)關(guān)注一下 復(fù)唧唧。