書名:《代碼的未來(lái)》
作者:松本行弘(Yukihiro Matsumoto)
譯者:**周自恒 **
本試讀章節(jié)摘自:『第3章 編程語(yǔ)言的新潮流』
接下來(lái)野蝇,我們從語(yǔ)言設(shè)計(jì)的角度悦昵,來(lái)比較一下 Java折柠、JavaScript、Ruby 和 Go 這 4 種語(yǔ)言癌瘾。這幾種語(yǔ)言看起來(lái)彼此完全不同瓮下,但如果選擇一個(gè)合適的標(biāo)準(zhǔn)翰铡,就可以將它們非常清楚地進(jìn)行分類,如圖所示唱捣。
JavaScript 是客戶端語(yǔ)言的代表两蟀,Java 其實(shí)也在其黎明期作為客戶端語(yǔ)言活躍過(guò)一段時(shí)間,應(yīng)該有很多人還記得 Java Applet 這個(gè)名詞震缭。之后赂毯,Java 轉(zhuǎn)型為服務(wù)器端語(yǔ)言的代表,地位也扶搖直上拣宰,但考慮到它的出身党涕,這里還是將其分類為客戶端語(yǔ)言。
另一個(gè)分類標(biāo)準(zhǔn)巡社,就是靜態(tài)和動(dòng)態(tài)膛堤。所謂靜態(tài),就是不實(shí)際運(yùn)行程序晌该,僅通過(guò)程序代碼的字面來(lái)確定結(jié)果的意思;而所謂動(dòng)態(tài)肥荔,就是只有當(dāng)運(yùn)行時(shí)才確定結(jié)果的意思。靜態(tài)朝群、動(dòng)態(tài)具體所指的內(nèi)容有很多種燕耿,大體上來(lái)分的話就是運(yùn)行模式和類型。這 4 種語(yǔ)言全都具備面向?qū)ο蟮男再|(zhì)姜胖,而面向?qū)ο蟊旧砭褪且环N包含動(dòng)態(tài)概念的性質(zhì)誉帅。不過(guò),在這幾種語(yǔ)言之中,Java 和 Go 是比較偏重靜態(tài)一側(cè)的語(yǔ)言蚜锨,而 Ruby 和 JavaScript 則是比較偏重動(dòng)態(tài)一側(cè)的語(yǔ)言档插。
客戶端與服務(wù)器端
首先,我們先將這些語(yǔ)言按照客戶端和服務(wù)器端來(lái)進(jìn)行分類亚再。如前面所說(shuō)郭膛,這種分類是以該語(yǔ)言剛剛出現(xiàn)時(shí)所使用的方式為基準(zhǔn)的。
現(xiàn)在 Java 更多地被用作服務(wù)器端語(yǔ)言氛悬,而我們卻將它分類到客戶端語(yǔ)言中饲鄙,很多人可能感到有點(diǎn)莫名其妙。Java 確實(shí)現(xiàn)在已經(jīng)很少被用作客戶端語(yǔ)言了圆雁,但是我們不能忘記,誕生于 1995 年的 Java帆谍,正是伴隨嵌入在瀏覽器中的 Applet 技術(shù)而出現(xiàn)的伪朽。
Java 將虛擬機(jī)(VM)作為插件集成到瀏覽器中,將編譯后的 Java 程序(Applet)在虛擬機(jī)上運(yùn)行汛蝙,這種技術(shù)當(dāng)初是為了增強(qiáng)瀏覽器的功能烈涮。再往前追溯的話,Java 原本名叫 Oak窖剑,是作為面向嵌入式設(shè)備的編程語(yǔ)言而誕生的坚洽。因此,從出身來(lái)看的話西土,Java 還是一種面向客戶端的編程語(yǔ)言讶舰。
Java 所具備的 VM 和平臺(tái)無(wú)關(guān)性字節(jié)碼等特性,本來(lái)就是以在客戶端運(yùn)行 Applet 為目的的需了。在各種不同的環(huán)境下都能夠產(chǎn)生相同的行為跳昼,這樣的特性對(duì)于服務(wù)器端來(lái)說(shuō)雖然也不能說(shuō)是毫無(wú)價(jià)值,但是服務(wù)器環(huán)境是可以由服務(wù)提供者來(lái)自由支配的肋乍,因此至少可以說(shuō)鹅颊,這樣的特性無(wú)法帶來(lái)關(guān)鍵性的好處吧。另一方面墓造,在客戶端環(huán)境中堪伍,操作系統(tǒng)和瀏覽器都是千差萬(wàn)別,因此對(duì)平臺(tái)無(wú)關(guān)性的要求一直很高觅闽。
Java 誕生于互聯(lián)網(wǎng)的黎明時(shí)期帝雇,那個(gè)時(shí)候?yàn)g覽器還不是電腦上必備的軟件。當(dāng)時(shí)主流的瀏覽器有 Mosaic 和 Netscape Navigator 等谱煤,除此之外還有一些其他類似的軟件摊求,而 Internet Explorer 也是剛剛才嶄露頭角。
在那個(gè)充滿夢(mèng)想的時(shí)代,如果能開發(fā)出一種功能上有亮點(diǎn)的瀏覽器就有可能稱霸業(yè)界室叉。原 Sun Microsystems 公司曾推出了一個(gè)用 Java 編寫的瀏覽器 HotJava睹栖,向世界展示了 Applet 的可能性。然而茧痕,隨著瀏覽器市場(chǎng)格局的逐步固定野来,他們轉(zhuǎn)變了策略,改為向主流瀏覽器提供插件來(lái)集成 Java踪旷,從而對(duì) Applet 的運(yùn)行提供支持曼氛。
向服務(wù)器端華麗轉(zhuǎn)身
然而,Java 自誕生之后令野,并未在客戶端方面取得多大的成功舀患,于是便開始著手進(jìn)入服務(wù)器端領(lǐng)域。造成這種局面有很多原因气破,我認(rèn)為其中最主要的原因應(yīng)該是在 Applet 這個(gè)平臺(tái)上遲遲沒有出現(xiàn)一款殺手級(jí)應(yīng)用(killer app)聊浅。
處于剛剛誕生之際的 Java 遭到了很多批判,如體積臃腫现使、運(yùn)行緩慢等低匙,不同瀏覽器上的 Java 插件之間也存在一些兼容性方面的問題,使得 Applet 應(yīng)用并沒有真正流行起來(lái)碳锈。在這個(gè)過(guò)程中焕数,JavaScript 作為客戶端編程語(yǔ)言則更加實(shí)用棍矛,并獲得了越來(lái)越多的關(guān)注。當(dāng)然,在那個(gè)時(shí)候 Java 已經(jīng)完全確立了自己作為服務(wù)器端編程語(yǔ)言的地位悉稠,因此喪失客戶端這塊領(lǐng)地也不至于感到特別肉痛兔毙。
Java 從客戶端向服務(wù)器端的轉(zhuǎn)身可以說(shuō)是相當(dāng)成功的颓影。與此同時(shí)枫虏,Sun Microsystems 和 IBM 等公司著手對(duì) JVM(Java VM)進(jìn)行改良,使得其性能得到了改善灸姊,在某些情況下性能甚至超越了 C++拱燃。想想之前對(duì) Java 性能惡評(píng)如潮的情形,現(xiàn)在 Java 能有這樣的性能和人氣簡(jiǎn)直就像做夢(mèng)一樣力惯。
在服務(wù)器端獲得成功的四大理由
由于我本人沒有大規(guī)模實(shí)踐過(guò) Java 編程碗誉,因此對(duì)于 Java 在服務(wù)器端取得成功的來(lái)龍去脈,說(shuō)真的并不是很了解父晶。不過(guò)哮缺,如果讓我想象一下的話,大概有下面幾個(gè)主要的因素甲喝。
1. 可移植性
雖然服務(wù)器環(huán)境比客戶端環(huán)境更加可控尝苇,但服務(wù)器環(huán)境中所使用的系統(tǒng)平臺(tái)種類也相當(dāng)多,如 Linux、Solaris糠溜、FreeBSD淳玩、Windows 等,根據(jù)需要非竿,可能還會(huì)在系統(tǒng)上線之后更換系統(tǒng)平臺(tái)蜕着。在這樣的情況下,Java 所具備的 “一次編寫红柱,到處運(yùn)行” 特性就顯得魅力十足了承匣。
2. 功能強(qiáng)大
Java 在服務(wù)器端嶄露頭角是在 20 世紀(jì) 90 年代末,那個(gè)時(shí)候的狀況對(duì) Java 比較有利锤悄。和 Java 在定位上比較相似的語(yǔ)言韧骗,即靜態(tài)類型、編譯型零聚、面向?qū)ο蟮木幊陶Z(yǔ)言宽闲,屬于主流的也就只有 C++ 而已了。
在 Java 誕生的 20 世紀(jì) 90 年代中期握牧,正好是我作為 C++ 程序員開發(fā) CAD 相關(guān)系統(tǒng)的時(shí)候。但當(dāng)時(shí) C++ 也還處于發(fā)展過(guò)程中娩梨,在實(shí)際的開發(fā)中沿腰,模板、異常等功能還無(wú)法真正得到運(yùn)用狈定。
相比之下颂龙,Java 從一開始就具備了垃圾回收(GC)機(jī)制,并在語(yǔ)言中內(nèi)置了異常處理纽什,其標(biāo)準(zhǔn)庫(kù)也是完全運(yùn)用了異常處理來(lái)設(shè)計(jì)的措嵌,這對(duì)程序員來(lái)說(shuō)簡(jiǎn)直是天堂。毫無(wú)疑問芦缰,Java 語(yǔ)言 的這些優(yōu)秀特性企巢,是幫助其確立服務(wù)器端編程語(yǔ)言地位的功臣之一。
3. 高性能
Java 為了實(shí)現(xiàn)其 “一次編寫让蕾,到處運(yùn)行” 的宣傳口號(hào)浪规,并不是將程序直接轉(zhuǎn)換為系統(tǒng)平臺(tái)所對(duì)應(yīng)的機(jī)器語(yǔ)言,而是轉(zhuǎn)換為虛擬 CPU 的機(jī)器語(yǔ)言 “字節(jié)碼” (Bytecode)探孝,并通過(guò)搭載虛擬 CPU 的模擬器 JVM 來(lái)運(yùn)行笋婿。JVM 歸根到底其實(shí)是在運(yùn)行時(shí)用來(lái)解釋字節(jié)碼的解釋器,理論上說(shuō)運(yùn)行速度應(yīng)該無(wú)法與直接生成機(jī)器語(yǔ)言的原生編譯器相媲美顿颅。
事實(shí)上缸濒,在 Java 誕生初期,確實(shí)沒有達(dá)到編譯型語(yǔ)言應(yīng)有的運(yùn)行速度,當(dāng)時(shí)的用戶經(jīng)常抱怨 Java 太慢了庇配,這樣的惡評(píng)令人印象深刻斩跌。
然而,技術(shù)的革新是偉大的讨永。隨著各種技術(shù)的進(jìn)步滔驶,現(xiàn)在 Java 的性能已經(jīng)能夠堪稱頂級(jí)。
例如卿闹,有一種叫做 JIT(Just In Time)編譯的技術(shù)揭糕,可以在運(yùn)行時(shí)將字節(jié)碼轉(zhuǎn)換成機(jī)器語(yǔ)言,經(jīng)過(guò)轉(zhuǎn)換之后就可以獲得和原生編譯一樣快的運(yùn)行速度锻霎。在運(yùn)行時(shí)進(jìn)行編譯著角,就意味著編譯時(shí)間也會(huì)包含在運(yùn)行時(shí)間里面。因此旋恼,優(yōu)秀的 JIT 編譯器會(huì)通過(guò)偵測(cè)運(yùn)行信息吏口,僅將需要頻繁運(yùn)行的瓶頸部分進(jìn)行編譯,從而大大削減編譯所需的時(shí)間冰更。而且产徊,利用運(yùn)行時(shí)編譯,可以不用考慮連接的問題而積極運(yùn)用內(nèi)聯(lián)擴(kuò)展蜀细,因此在某些情況下舟铜,運(yùn)行速度甚至可以超過(guò) C++。
在 Java 中奠衔,其性能提高的另一個(gè)障礙就是 GC谆刨。GC 需要對(duì)對(duì)象進(jìn)行掃描,將不用的對(duì)象進(jìn)行回收归斤,這個(gè)過(guò)程和程序本身要進(jìn)行的操作是無(wú)關(guān)的痊夭,換句話說(shuō),就是做無(wú)用功脏里,因此而消耗的時(shí)間拖累了 Java 程序的性能她我。作為對(duì)策,在最新的 JVM 中迫横,采用了并行回收鸦难、分代回收等技術(shù)。
4. 豐富的庫(kù)
隨著 Java 的人氣直升员淫,應(yīng)用逐漸廣泛合蔽,Java 能夠使用的庫(kù)也越來(lái)越多。庫(kù)的增加提高了開發(fā)效率介返,從而又反過(guò)來(lái)拉高了 Java 的人氣拴事,形成了一個(gè)良性循環(huán)∥纸铮現(xiàn)在 Java 的人氣已經(jīng)無(wú)可撼動(dòng)了。
客戶端的 JavaScript
Applet 在客戶端對(duì)擴(kuò)展瀏覽器功能做出了嘗試刃宵,然而它并不太成功衡瓶。在瀏覽器畫面中的一個(gè)矩形區(qū)域中運(yùn)行應(yīng)用程序的 Applet,并沒有作為應(yīng)用程序的發(fā)布手段而流行起來(lái)牲证。
幾乎是在同一時(shí)期出現(xiàn)的 JavaScript哮针,也是一種集成在瀏覽器中的語(yǔ)言,但是它可以在一般的網(wǎng)頁(yè)中嵌入程序邏輯坦袍,這一點(diǎn)是和 Java Applet 完全不同的方式十厢,卻最終獲得了成功。
JavaScript 是由原 Netscape Communications 公司開發(fā)的捂齐,通過(guò) JavaScript蛮放,用戶點(diǎn)擊網(wǎng)頁(yè)上的鏈接和按鈕時(shí),不光可以進(jìn)行頁(yè)面的跳轉(zhuǎn)奠宜,還可以改寫頁(yè)面的內(nèi)容包颁。這樣的功能十分便利,因此 Netscape Navigator 之外的很多瀏覽器都集成了 JavaScript压真。
隨著瀏覽器的不斷競(jìng)爭(zhēng)和淘汰娩嚼,當(dāng)主流瀏覽器全部支持 JavaScript 時(shí),情況便發(fā)生了變化滴肿。像 Google 地圖這樣的產(chǎn)品岳悟,整體的框架是由 HTML 組成的,但實(shí)際顯示的部分卻是通過(guò) JavaScript 來(lái)從服務(wù)器獲取數(shù)據(jù)并顯示出來(lái)嘴高,這樣的手法從此開始流行起來(lái)。
在 JavaScript 中與服務(wù)器進(jìn)行異步通信的 API 叫做 XMLHttpRequest和屎,因此從它所衍生出的手法便被稱為 Ajax(Asynchronous JavaScript and XML拴驮,異步 JavaScript 與 XML)。在美國(guó)有一種叫做 Ajax 的廚房清潔劑柴信,說(shuō)不定是從那個(gè)名字模仿而來(lái)的套啤。
性能顯著提升
目前,客戶端編程語(yǔ)言中 JavaScript 已成為一個(gè)強(qiáng)有力的競(jìng)爭(zhēng)者随常,伴隨著 JavaScript 重要性的不斷提高潜沦,對(duì) JavaScript 引擎的投資也不斷增加,使 JavaScript 的性能得到了顯著改善绪氛。改善 JavaScript 性能的主要技術(shù)唆鸡,除了和 Java 相同的 JIT 和 GC 之外,還有特殊化(Specialization)技術(shù)枣察。
與 Java 不同争占,JavaScript 是一種動(dòng)態(tài)語(yǔ)言燃逻,不帶有變量和表達(dá)式的類型信息,針對(duì)類型進(jìn)行優(yōu)化是非常困難的臂痕,因此性能和靜態(tài)語(yǔ)言相比有著先天的劣勢(shì)伯襟,而特殊化就是提高動(dòng)態(tài)語(yǔ)言性能的技術(shù)之一。
JavaScript 函數(shù):
function fact(n) {
if (n == 1) return 1;
return n * fact(n-1);
}
我們?cè)O(shè)想如上所示的這樣一個(gè) JavaScript 函數(shù)握童。這個(gè)函數(shù)是用于階乘計(jì)算的姆怪,大多數(shù)情況下,其參數(shù) n 應(yīng)該都是整數(shù)澡绩。由于 JIT 需要統(tǒng)計(jì)運(yùn)行時(shí)信息稽揭,因此 JavaScript 解釋器也知道參數(shù) n 大多數(shù)情況下是整數(shù)。
于是英古,當(dāng)解釋器對(duì) fact 函數(shù)進(jìn)行 JIT 編譯時(shí)淀衣,會(huì)生成兩個(gè)版本的函數(shù):一個(gè)是 n 為任意對(duì)象的通用版本,另一個(gè)是假設(shè) n 為整數(shù)的高速版本召调。當(dāng)參數(shù) n 為整數(shù)時(shí)(即大多數(shù)情況下)膨桥,就會(huì)運(yùn)行那個(gè)高速版本的函數(shù),便實(shí)現(xiàn)了與靜態(tài)語(yǔ)言幾乎相同的運(yùn)行性能唠叛。
除此之外只嚣,最新的 JavaScript 引擎中還進(jìn)行了其他大量的優(yōu)化,說(shuō) JavaScript 是目前最快的動(dòng)態(tài)語(yǔ)言應(yīng)該并不為過(guò)艺沼。
JavaScript 在客戶端稱霸之后册舞,又開始準(zhǔn)備向服務(wù)器端進(jìn)軍了。JavaScript 的存在感在將來(lái)應(yīng)該會(huì)越來(lái)越強(qiáng)吧障般。
服務(wù)器端的 Ruby
客戶端編程的最大問題调鲸,就是必須要求每一臺(tái)客戶端都安裝相應(yīng)的軟件環(huán)境。在 Java 和 JavaScript 誕生的 20 世紀(jì) 90 年代后半挽荡,互聯(lián)網(wǎng)用戶還只局限于一部分先進(jìn)的用戶藐石,然而現(xiàn)在互聯(lián)網(wǎng)已經(jīng)大大普及,用戶的水平構(gòu)成也跟著變得復(fù)雜起來(lái)定拟,讓每一臺(tái)客戶端都安裝相應(yīng)的軟件環(huán)境于微,就會(huì)大大提高軟件部署的門檻。
而相對(duì)的青自,在服務(wù)器端就沒有這樣的制約株依,可以選擇最適合自己的編程語(yǔ)言。
在 Ruby 誕生的 1993 年延窜,互聯(lián)網(wǎng)還沒有現(xiàn)在這樣普及恋腕,因此 Ruby 也不是一開始就面向 Web 服務(wù)器端來(lái)設(shè)計(jì)的。然而逆瑞,從 WWW 黎明期開始吗坚,為了實(shí)現(xiàn)動(dòng)態(tài)頁(yè)面而出現(xiàn)了通用網(wǎng)關(guān)接口(Common Gateway Interface祈远,CGI)技術(shù),而 Ruby 則逐漸在這種技術(shù)中得到了應(yīng)用商源。
所謂 CGI车份,是通過(guò) Web 服務(wù)器的標(biāo)準(zhǔn)輸入輸出與程序進(jìn)行交互,從而生成動(dòng)態(tài) HTML 頁(yè)面 的接口牡彻。只要可以對(duì)標(biāo)準(zhǔn)輸入輸出進(jìn)行操作扫沼,那么無(wú)論任何語(yǔ)言都可以編寫 CGI 程序,這不得不歸功于 WWW 設(shè)計(jì)的靈活性庄吼,使得動(dòng)態(tài)頁(yè)面可以很容易地編寫出來(lái)缎除,也正是因?yàn)槿绱耍沟?WWW 逐漸風(fēng)靡全世界总寻。
在 WWW 中器罐,來(lái)自 Web 服務(wù)器的請(qǐng)求信息是以文本的方式傳遞的,反過(guò)來(lái)渐行,返回給 Web 服務(wù)器的響應(yīng)信息也是以文本(HTML)方式傳遞的轰坊,因此擅長(zhǎng)文本處理的編程語(yǔ)言就具有得天獨(dú)厚的優(yōu)勢(shì)。于是祟印,腳本語(yǔ)言的時(shí)代到來(lái)了肴沫。以往只是用于文本處理的腳本語(yǔ)言,其應(yīng)用范圍便一下子擴(kuò)大了蕴忆。
早期應(yīng)用 CGI 的 Web 頁(yè)面大多是用 Perl 來(lái)編寫的颤芬,而作為 “Better Perl” 的 Ruby 也隨之逐步得到越來(lái)越多的應(yīng)用。
Ruby on Rails 帶來(lái)的飛躍
2004 年套鹅,隨著 Ruby on Rails 的出現(xiàn)站蝠,使得 Web 應(yīng)用程序的開發(fā)效率大幅提升,也引發(fā)了廣泛的關(guān)注卓鹿。當(dāng)時(shí)菱魔,已經(jīng)出現(xiàn)了很多 Web 應(yīng)用程序框架,而 Ruby on Rails 可以說(shuō)是后發(fā)制人的减牺。 Ruby on Rails 的特性包括:
- ?完全的 MVC 架構(gòu)?
- 不使用配置文件(尤其是 XML)?
- 堅(jiān)持簡(jiǎn)潔的表達(dá)
- 積極運(yùn)用元編程
- 對(duì) Ruby 核心的大膽擴(kuò)展
基于這些特性豌习,Ruby on Rails 實(shí)現(xiàn)了很高的開發(fā)效率和靈活性存谎,得到了廣泛的應(yīng)用拔疚。可以說(shuō)既荚,Ruby 能擁有現(xiàn)在的人氣稚失,基本上都是 Ruby on Rails 所作出的貢獻(xiàn)。
目前恰聘,作為服務(wù)器端編程語(yǔ)言句各,Ruby 的人氣可謂無(wú)可撼動(dòng)吸占。有一種說(shuō)法稱,以硅谷為中心的 Web 系創(chuàng)業(yè)公司中凿宾,超過(guò)一半都采用了 Ruby矾屯。
但這也并不是說(shuō),只要是服務(wù)器端環(huán)境初厚,Ruby 就一定可以所向披靡件蚕。在規(guī)模較大的企業(yè)中,向網(wǎng)站運(yùn)營(yíng)部門管理的服務(wù)器群安裝軟件也并不容易产禾。實(shí)際上排作,在某個(gè)大企業(yè)中,曾經(jīng)用 Ruby on Rails 開發(fā)了一個(gè)面向技術(shù)人員的 SNS亚情,只用很短的時(shí)間就完成搭建了妄痪,但是等到要正式上線的時(shí)候,運(yùn)營(yíng)部門就會(huì)以 “這種不知道哪個(gè)的家伙開發(fā)的楞件,也沒經(jīng)過(guò)第三方安全認(rèn)證的 Ruby 解釋器之類的軟件衫生,不可以安裝在我們數(shù)據(jù)中心的主機(jī)上面” 這樣的理由來(lái)拒絕安裝,這真是相當(dāng)頭疼履因。
不過(guò)障簿,開發(fā)部門的工程師們并沒有氣餒,而是用 Java 編寫的 Ruby 解釋器 JRuby栅迄,將開發(fā)好的 SNS 轉(zhuǎn)換為 jar 文件站故,從而使其可以在原 Sun Microsystems 公司的應(yīng)用程序服務(wù)器 GlassFish 上運(yùn)行。當(dāng)然毅舆,JVM 和 GlassFish 都已經(jīng)在服務(wù)器上安裝好了西篓,這樣一來(lái)運(yùn)營(yíng)方面也就沒有理由拒絕了。多虧了 JRuby憋活,結(jié)局皆大歡喜岂津。
JRuby 還真是在關(guān)鍵時(shí)刻大顯身手呢。
服務(wù)器端的 Go
Go 是一種新興的編程語(yǔ)言悦即,但它出身名門吮成,是由著名 UNIX 開發(fā)者羅勃 · 派克和肯 · 湯普遜開發(fā)的,因此受到了廣泛的關(guān)注辜梳。
Go 的誕生背景源于 Google 公司中關(guān)于編程語(yǔ)言的一些問題粱甫。在 Google 公司中,作為優(yōu)化編程環(huán)境的一環(huán)作瞄,在公司產(chǎn)品開發(fā)中所使用的編程語(yǔ)言茶宵,僅限于 C/C++、Java宗挥、Python 和JavaScript乌庶。實(shí)際上也有人私底下在用 Ruby种蝶,不過(guò)正式產(chǎn)品中所使用的語(yǔ)言僅限上述 4 種。
這 4 種語(yǔ)言在使用上遵循著一定的分工:客戶端語(yǔ)言用 JavaScript瞒大,服務(wù)器端語(yǔ)言用腳本系的 Python螃征,追求大規(guī)模或高性能時(shí)用 Java透敌,文件系統(tǒng)等面向平臺(tái)的系統(tǒng)編程用 C/C++会傲。在這些語(yǔ)言中,Google 公司最不滿意的就是 C/C++ 了拙泽。
和其他一些編程語(yǔ)言相比淌山,C/C++ 的歷史比較久,因此不具備像垃圾回收等最近的語(yǔ)言所提供的編程輔助功能顾瞻。因此泼疑,由于開發(fā)效率一直無(wú)法得到提高,便產(chǎn)生了設(shè)計(jì)一種 “更好的” 系統(tǒng)編程語(yǔ)言的需求荷荤。而能夠勝任這一位置的退渗,正是全新設(shè)計(jì)的編程語(yǔ)言 Go。
Go 具有很多特性蕴纳,(從我的觀點(diǎn)來(lái)看)比較重要的有下列幾點(diǎn):
- ?垃圾回收
- 支持并行處理的 Goroutine
- Structural Subtyping(結(jié)構(gòu)子類型)
關(guān)于最后一點(diǎn) Structural Subtyping会油,我們會(huì)在后面對(duì)類型系統(tǒng)的講解中進(jìn)行說(shuō)明。
靜態(tài)與動(dòng)態(tài)
剛才我們已經(jīng)將這 4 種語(yǔ)言古毛,從客戶端翻翩、服務(wù)器端的角度進(jìn)行了分類。接下來(lái)我們?cè)購(gòu)膭?dòng)態(tài)稻薇、靜態(tài)的角度來(lái)看一看這幾種語(yǔ)言嫂冻。
正如剛才所講過(guò)的,所謂靜態(tài)塞椎,就是無(wú)需實(shí)際運(yùn)行桨仿,僅根據(jù)程序代碼就能確定結(jié)果的意思;而所謂動(dòng)態(tài)案狠,則是只有到了運(yùn)行時(shí)才能確定結(jié)果的意思服傍。
不過(guò),無(wú)論任何程序骂铁,或多或少都包含了動(dòng)態(tài)的特性吹零。如果一個(gè)程序完全是靜態(tài)的話,那就意味著只需要對(duì)代碼進(jìn)行字面上的分析从铲,就可以得到所有的結(jié)果瘪校,這樣一來(lái)程序的運(yùn)行就沒有任何意義了澄暮。例如名段,編程計(jì)算 6 的階乘阱扬,如果按照完全靜態(tài)的方式來(lái)編寫的話,應(yīng)該是下面這樣的:
puts "720"
不過(guò)伸辟,除非是個(gè)玩具一樣的演示程序麻惶,否則不會(huì)開發(fā)出這樣的程序來(lái)。在實(shí)際中信夫,由于有了輸入的數(shù)據(jù)窃蹋,或者和用戶之間的交互,程序才能在每次運(yùn)行時(shí)都能得到不同的要素静稻。
因此警没,作為程序的實(shí)現(xiàn)者,編程語(yǔ)言也多多少少都具備動(dòng)態(tài)的性質(zhì)振湾。所謂動(dòng)態(tài)還是靜態(tài)杀迹,指的是這種語(yǔ)言對(duì)于動(dòng)態(tài)的功能進(jìn)行了多少限制,或者反過(guò)來(lái)說(shuō)押搪,對(duì)動(dòng)態(tài)功能進(jìn)行了多少積極的強(qiáng)化树酪,我們所探討的其實(shí)是語(yǔ)言的這種設(shè)計(jì)方針。
例如大州,在這里所列舉的 4 種編程語(yǔ)言都是面向?qū)ο蟮恼Z(yǔ)言,而面向?qū)ο蟮恼Z(yǔ)言都會(huì)具備被稱為多態(tài)(Polymorphism)或者動(dòng)態(tài)綁定的動(dòng)態(tài)性質(zhì)。即型宙,根據(jù)存放在變量中的對(duì)象的實(shí)際性質(zhì)擎浴,自動(dòng)選擇一種合適的處理方式(方法)。這樣的功能可以說(shuō)是面向?qū)ο缶幊痰谋举|(zhì)根暑。
屬于動(dòng)態(tài)的編程語(yǔ)言娃豹,其動(dòng)態(tài)的部分,主要是指運(yùn)行模式和類型购裙。這兩者是相互獨(dú)立的概念懂版,但采用動(dòng)態(tài)類型的語(yǔ)言,其運(yùn)行模式也具有動(dòng)態(tài)的傾向;反之也是一樣躏率,在靜態(tài)語(yǔ)言中躯畴,運(yùn)行模式在運(yùn)行時(shí)的靈活性也會(huì)受到一定的限制。
動(dòng)態(tài)運(yùn)行模式
所謂動(dòng)態(tài)運(yùn)行模式薇芝,簡(jiǎn)單來(lái)說(shuō)蓬抄,就是運(yùn)行中的程序能夠識(shí)別自身,并對(duì)自身進(jìn)行操作夯到。對(duì)程序自身進(jìn)行操作的編程嚷缭,也被稱為元編程(Metaprogramming)。
在 Ruby 和 JavaScript 中,元編程是十分自然的阅爽,比如查詢某個(gè)對(duì)象擁有哪些方法路幸,或者在運(yùn)行時(shí)對(duì)類和方法進(jìn)行定義等等,這些都是理所當(dāng)然的事付翁。
另一方面简肴,在 Java 中,類似元編程的手法百侧,是通過(guò) “反射 API” 來(lái)實(shí)現(xiàn)的砰识。雖然對(duì)類進(jìn)行取出、操作等功能都是可以做到的佣渴,但并非像 Ruby 和 JavaScript 那樣讓人感到自由自在辫狼,而是 “雖然能做到,但一般也不會(huì)去用” 這樣的感覺吧辛润。
Go 也是一樣予借。在 Go 中,通過(guò)利用 reflect 包可以獲取程序的運(yùn)行時(shí)信息(主要是類型)频蛔,但是(在我所理解的范圍內(nèi))無(wú)法實(shí)現(xiàn)進(jìn)一步的元編程功能灵迫。而之所以沒有采用比 Java 更進(jìn)一步的動(dòng)態(tài)運(yùn)行模式,恐怕是因?yàn)檫@(可能)在系統(tǒng)編程領(lǐng)域中必要性不大晦溪,或者是擔(dān)心對(duì)運(yùn)行速度產(chǎn)生負(fù)面影響之類的原因吧瀑粥。
何謂類型
從一般性的層面來(lái)看,類型2指的是對(duì)某個(gè)數(shù)據(jù)所具有的性質(zhì)所進(jìn)行的描述三圆。例如狞换,它的結(jié)構(gòu)是怎樣的,它可以進(jìn)行哪些操作舟肉,等等修噪。動(dòng)態(tài)類型的立場(chǎng)是數(shù)據(jù)擁有類型,且只有數(shù)據(jù)才擁有類型;而靜態(tài)類型的立場(chǎng)是數(shù)據(jù)擁有類型路媚,而存放數(shù)據(jù)的變量黄琼、表達(dá)式也擁有類型,且類型是在編譯時(shí)就固定的整慎。
然而脏款,即便是靜態(tài)類型,由于面向?qū)ο笳Z(yǔ)言中的多態(tài)特性裤园,也必須具備動(dòng)態(tài)的性質(zhì)撤师,因此需要再追加一條規(guī)則,即實(shí)際的數(shù)據(jù)(類型)拧揽,是靜態(tài)指定的類型的子類型剃盾。所謂子類型(Subtype)腺占,是指具有繼承關(guān)系,或者擁有同一接口痒谴,即靜態(tài)類型與數(shù)據(jù)的類型在系統(tǒng)上 “擁有同一性質(zhì)” 衰伯。
靜態(tài)類型的優(yōu)點(diǎn)
動(dòng)態(tài)類型比較簡(jiǎn)潔,且靈活性高闰歪,但靜態(tài)類型也有它的優(yōu)點(diǎn)。由于在編譯時(shí)就已經(jīng)確定了類型蓖墅,因此比較容易發(fā)現(xiàn) bug库倘。當(dāng)然,程序中的 bug 大多數(shù)都是與邏輯有關(guān)的论矾,而單純是類型錯(cuò)誤而導(dǎo)致的 bug 只是少數(shù)派教翩。不過(guò),邏輯上的錯(cuò)誤通常也伴隨著編譯時(shí)可以檢測(cè)到的類型不匹配贪壳,也就是說(shuō)饱亿,通過(guò)類型錯(cuò)誤可以讓其他的 bug 顯露出來(lái)。
除此之外闰靴,程序中對(duì)類型的描述彪笼,可以幫助對(duì)程序的閱讀和理解,或者可以成為關(guān)于程序行為的參考文檔蚂且,這可以說(shuō)是一個(gè)很大的優(yōu)點(diǎn)配猫。
此外,通過(guò)靜態(tài)類型杏死,可以在編譯時(shí)獲得更多可以利用的調(diào)優(yōu)信息泵肄,編譯器便可以生成更優(yōu)質(zhì)的代碼,從而提高程序的性能淑翼。然而腐巢,通過(guò) JIT 等技術(shù),動(dòng)態(tài)語(yǔ)言也可以獲得與原生編譯 的語(yǔ)言相近的性能玄括,這也說(shuō)明冯丙,在今后靜態(tài)語(yǔ)言和動(dòng)態(tài)語(yǔ)言之間的性能差距會(huì)繼續(xù)縮小。
動(dòng)態(tài)類型的優(yōu)點(diǎn)
相對(duì)而言遭京,動(dòng)態(tài)類型的優(yōu)點(diǎn)银还,就在于其簡(jiǎn)潔性和靈活性了。
說(shuō)得極端一點(diǎn)的話洁墙,類型信息其實(shí)和程序運(yùn)行的本質(zhì)是無(wú)關(guān)的蛹疯。就拿階乘計(jì)算的程序來(lái)說(shuō),無(wú)論是用顯式聲明類型的 Java 來(lái)編寫热监,還是用非顯式聲明類型的 Ruby 來(lái)編寫捺弦, 其算法都是毫無(wú)區(qū)別的。然而,由于多了關(guān)于類型的描述列吼,因此在 Java 版中幽崩,與算法本質(zhì)無(wú)關(guān)的代碼的分量也就增加了。
Java 編寫的階乘程序 :
class Sample {
private static int fact(int n) {
if (n == 1) return 1;
return n * fact(n - 1);
}
public static void main(String[] argv) {
System.out.println("6!="+fact(6));
}
}
Ruby 編寫的階乘程序:
def fact(n)
if n == 1
1
else
n * fact(n - 1)
end
end
print "6!="寞钥, fact(6)慌申, "\n"
---
而且,類型也帶來(lái)了更多的制約理郑。如上所示的程序?qū)?6 的階乘進(jìn)行了計(jì)算蹄溉,但如果這個(gè)數(shù)字繼續(xù)增大,Java 版對(duì)超過(guò) 13 的數(shù)求階乘的話您炉,就無(wú)法正確運(yùn)行了柒爵。Java 的程序中,fact
方法所接受的參數(shù)類型顯式聲明為 int
型赚爵,而 Java 的 int 為 32 位棉胀,即可以表示到接近 20 億的整數(shù)。如果階乘的計(jì)算結(jié)果超出這個(gè)范圍冀膝,就會(huì)導(dǎo)致溢出唁奢。
當(dāng)然,由于 Java 擁有豐富的庫(kù)資源窝剖,用 BigInteger
類就可以實(shí)現(xiàn)無(wú)上限的大整數(shù)計(jì)算驮瞧,但這就需要對(duì)上面的程序做較大幅度的改動(dòng)。而由于計(jì)算機(jī)存在 “int 的幅度為 32 位” 這一限制枯芬,就使得階乘計(jì)算的靈活性大大降低了论笔。
另一方面,Ruby 版中則沒有這樣的制約千所,就算是計(jì)算 13 的階乘狂魔,甚至是 200 的階乘,都可以直接計(jì)算出來(lái)淫痰,而無(wú)需擔(dān)心如 int 的大小最楷、計(jì)算機(jī)的限制等問題。
其實(shí)這里還是有點(diǎn)小把戲的待错。同樣是動(dòng)態(tài)語(yǔ)言籽孙,用 JavaScript 來(lái)計(jì)算 200 的階乘就會(huì)輸出 Infinity(無(wú)窮大)。其實(shí)火俄,JavaScript 的數(shù)值是浮點(diǎn)數(shù)犯建,因此無(wú)法像 Ruby 那樣支持大整數(shù)的計(jì)算。也就是說(shuō)瓜客,要不受制約地進(jìn)行計(jì)算适瓦,除了類型的性質(zhì)之外竿开,庫(kù)的支持也是非常重要的。
有鴨子樣的就是鴨子
在動(dòng)態(tài)語(yǔ)言中玻熙,一種叫做鴨子類型(Duck Typing)的風(fēng)格被廣泛應(yīng)用否彩。鴨子類型這個(gè)稱謂,據(jù)說(shuō)是從下面這則英語(yǔ)童謠來(lái)的:
- If it walks like a duck and quacks like a duck, it must be a duck.
如果像鴨子一樣走路嗦随,像鴨子一樣呱呱叫列荔,則它一定是一只鴨子
從這則童謠中,我們可以推導(dǎo)出一個(gè)規(guī)則枚尼,即如果某個(gè)對(duì)象的行為和鴨子一模一樣贴浙,那無(wú)論它真正的實(shí)體是什么,我們都可以將它看做是一只鴨子姑原。也就是說(shuō)悬而,不考慮某個(gè)對(duì)象到底是哪一個(gè)類的實(shí)例呜舒,而只關(guān)心其擁有怎樣的行為(擁有哪些方法)锭汛,這就是鴨子類型。因此袭蝗,在程序中唤殴,必須排除由對(duì)象的類所產(chǎn)生的分支。
這是由 “編程達(dá)人” 大衛(wèi) · 托馬斯(Dave Thomas)所提出的到腥。
例如朵逝,假設(shè)存在 log_puts(out, mesg)
這樣一個(gè)方法乡范,用來(lái)將 mesg
這個(gè)字符串輸出至 out
這個(gè)輸出目標(biāo)中配名。out
需要指定一個(gè)類似 Ruby 中的 IO
對(duì)象,或者是 Java 中的 ReadStream
這樣的對(duì)象晋辆。在這里渠脉,本來(lái)是向文件輸出的日志,忽然想輸出到內(nèi)存的話瓶佳,要怎么辦呢芋膘?比如說(shuō)我想將日志的輸出結(jié)果合并成一個(gè)字符串,然后再將它取出霸饲。
在 Java 等靜態(tài)語(yǔ)言中为朋,out
所指定的對(duì)象必須擁有共同的超類或者接口,而無(wú)法選擇一個(gè)完全無(wú)關(guān)的對(duì)象作為輸出目標(biāo)厚脉。要實(shí)現(xiàn)這樣的操作习寸,要么一開始就事先準(zhǔn)備這樣一個(gè)接口,要么重寫原來(lái)的類傻工,要么準(zhǔn)備一個(gè)可以切換輸出目標(biāo)的包裝對(duì)象(wrapper object)融涣。無(wú)論如何童番,如果沒有事先預(yù)計(jì)到需要輸出到內(nèi)存的話,就需要對(duì)程序進(jìn)行大幅的改動(dòng)了威鹿。
如果是采用了鴨子類型風(fēng)格的動(dòng)態(tài)語(yǔ)言剃斧,就不容易產(chǎn)生這樣的問題。只要準(zhǔn)備一個(gè)和 IO 對(duì)象具有同樣行為的對(duì)象忽你,并將其指定為 out
的話幼东,即便不對(duì)程序進(jìn)行改動(dòng),log_puts
方法能夠成 功執(zhí)行的可能性也相當(dāng)大科雳。實(shí)際上根蟹,在 Ruby 中,確實(shí)存在和 IO
類毫無(wú)繼承關(guān)系糟秘,但和 IO 具有同樣行為的 StringIO
類简逮,用來(lái)將輸出結(jié)果合并成字符串。
動(dòng)態(tài)類型在編譯時(shí)所執(zhí)行的檢查較少尿赚,這是一個(gè)缺點(diǎn)散庶,但與此同時(shí),程序會(huì)變得更加簡(jiǎn)潔凌净,對(duì)于將來(lái)的擴(kuò)展也具有更大的靈活性悲龟,這便是它的優(yōu)點(diǎn)。
Structural Subtyping
在 4 種語(yǔ)言中最年輕的 Go冰寻,雖然是一種靜態(tài)語(yǔ)言须教,但卻吸取了鴨子類型的優(yōu)點(diǎn)。Go 中沒有所謂的繼承關(guān)系斩芭,而某個(gè)類型可以具有和其他類型之間的可代換性轻腺,也就是說(shuō),某個(gè)類型的變量中是否可以賦予另一種類型的數(shù)據(jù)划乖,是由兩個(gè)類型是否擁有共同的方法所決定的贬养。例如,對(duì)于 “A 型” 的變量迁筛,只要數(shù)據(jù)擁有 A 型所提供的所有方法煤蚌,那么這個(gè)數(shù)據(jù)就可以賦值給該變量。像這樣细卧,以類型的結(jié)構(gòu)來(lái)確定可代換性的類型關(guān)系尉桩,被稱為結(jié)構(gòu)子類型(Structural Subtyping);另一方面贪庙,像 Java 這樣根據(jù)聲明擁有繼承關(guān)系的類型具有可代換性的類型關(guān)系蜘犁,被稱為名義子類型(Nominal Subtyping)。
在結(jié)構(gòu)子類型中止邮,類型的聲明是必要的这橙,但由于并不需要根據(jù)事先的聲明來(lái)確定類型之間的關(guān)系奏窑,因此就可以實(shí)現(xiàn)鴨子類型風(fēng)格的編程。和完全動(dòng)態(tài)類型的語(yǔ)言相比屈扎,雖然增加了對(duì)類型的描述埃唯,但卻可以同時(shí)獲得鴨子類型帶來(lái)的靈活性,以及靜態(tài)編譯所帶來(lái)了類型檢查這兩個(gè)優(yōu)點(diǎn)鹰晨,可以說(shuō)是一個(gè)相當(dāng)劃算的交換墨叛。
小結(jié)
在這里,我們對(duì) Ruby模蜡、JavaScript漠趁、Java、Go 這 4 種語(yǔ)言忍疾,從服務(wù)器端闯传、客戶端,以及靜態(tài)卤妒、動(dòng)態(tài)這兩個(gè)角度來(lái)進(jìn)行了對(duì)比甥绿。這 4 種語(yǔ)言由于其不同的設(shè)計(jì)方針,而產(chǎn)生出了不同的設(shè)計(jì)風(fēng)格荚孵,大家是否對(duì)此有了些許了解呢妹窖?
不僅僅是語(yǔ)言纬朝,其實(shí)很多設(shè)計(jì)都是權(quán)衡的結(jié)果收叶。新需求、新環(huán)境共苛,以及新范式判没,都催生出新的設(shè)計(jì)。而學(xué)習(xí)現(xiàn)有語(yǔ)言的設(shè)計(jì)及其權(quán)衡的過(guò)程隅茎,也可以為未來(lái)的新語(yǔ)言打下基礎(chǔ)澄峰。