iThome

這邊將以「Java」、「JavaScript」、「Ruby」、「Go」這四種語言為例,從語言設計的角度進行比較。請見圖1。

用戶端vs伺服端

把主要運用在伺服端的Java分在用戶端,應該會有一些讀者覺得有點奇怪。事實上Java最近確實在用戶端幾乎沒什麼運用。不過請不要忘記Java在1995年是以瀏覽器內嵌applet功能的形式登場的。

如此登場的Java在用戶端沒有獲得多大成功,卻逐漸轉往伺服端。理由應該有好幾個,不過最重要的或許是因為applet的戰略一直沒有出現殺手應用的關係。

筆者本身沒有積極撰寫Java程式,所以說真的不太清楚Java在伺服端獲得人氣的過程。

不過光靠想像的話,感覺應該可以歸結到下面幾個因素。

● 可攜

儘管說是環境比用戶端容易控制,伺服端用的平台還是有各種選擇。Linux、Solaris、FreeBSD、Windows等等。有些狀況下,系統運用中還會改變平台。在這種情形下,Java的「Write once, run anywhere」的性質應該會十分吸引人。

●  高機能

Java誕生、在伺服端存在感開始提昇的1990年代後半,對Java來說是很有利的狀況。

因為與Java具有類似目標,也就是靜態型別、編譯的物件導向語言,最主要的對手大概只有C++而已。

Java登場的1990年代後半,那時筆者是開發CAD相關系統的C++程式設計師。不過當時C++也還正在發展,實務上template、exception等功能都還不是能正式運用的東西。

相較之下,Java一開始就具備垃圾收集(GC),例外功能也一開始就由語言內建,標準函式庫也確實活用例外的機制做設計。簡直是堪稱天國的狀況。要說是這些語言的優越特性讓它在伺服端確立地位也沒有錯。

● 高性能

為了實現Java的標語「Write once, run anywhere」,Java並不是直接轉換成平台的機械碼,而是把程式轉換成某個假想CPU的機器碼「bytecode」。而後執行時由模擬這個假想CPU的JVM來執行。JVM就是執行時解譯bytecode的直譯器,所以跟直接執行機器碼的原生編譯器相較之下,執行速度絕對不會有優勢。

實際上,Java登場當時無法實現身為編譯語言的執行速度,讓許多使用者抱怨「Java很慢」,應該有不少讀者都還有印象才對。

不過技術的革新是很棒的。由於各種技術進步,Java的性能在現今可說是頂級。

比如說,有個稱為JIT(Just In Time)編譯器的技術。這是在執行時把bytecode轉換成機械碼的技術。轉換後是以原生速度執行,速度非常快,但在執行時編譯,也代表執行時間會包含編譯時間。而優秀的JIT編譯器會測量執行時的資訊,只在會頻繁執行的瓶頸部份進行編譯,以削減無謂的時間浪費。除此之外,利用執行時才編譯的優勢,可以不擔心連結問題,而積極在行內展開程式,有些狀況下甚至能得到比C++更棒的性能表現。

Java還有一個阻礙達成高性能的因素,那就是GC。掃描物件、負責回收用不到的物件的GC,與程式作的主要工作無關,某種角度可說是「無謂的處理」。它消費的時間會影響Java程式的性能。最新的JVM是以平行GC、世代別GC等技巧減輕這個問題的影響。

● 豐富的函式庫

隨著Java人氣上昇、廣受應用,Java可以用的函式庫也隨之增加。函式庫越多,生產力就越高。

用戶端的JavaScript

而在幾乎同時期登場的JavaScript,同樣是嵌入瀏覽器的語言。不過它是將處理邏輯嵌入一般的網頁,跟Java applet用的是不同的作法,而它成功了。

Netscape Communications開發的JavaScript,可在點按連結、按鈕的時候,不執行傳統的頁面轉移動作,而是改寫頁面內容。這非常方便,所以讓 Netscape Navigator以外的瀏覽器也開始嵌入JavaScript。

隨著瀏覽器淘汰、主要的瀏覽器都支援JavaScript之後,狀況發生了變化。「Google Maps」這種透過HTML提供整體架構、顯示的本質部份再以JavaScript從伺服端取得資料來顯示的手法開始流行。

性能顯著提昇

現在的用戶端程式語言,JavaScript可說視為一選擇,JavaScript的重要性仍在不斷上升。隨著這個趨勢,JavaScript的實作也不斷獲得投資、性能得到顯著改善。提昇JavaScript性能的主要技術,除了與Java相同的JIT與GC之外,還有「特殊化」。

跟Java不同,對JavaScript這種變數與運算式沒有型別資訊的動態型別語言來說,難以利用型別資訊進行最佳化,所以性能有低於靜態型別語言的傾向。特殊化是提昇動態語言性能的技法之一。

假設有圖2這樣的JavaScript函式。這是個計算階乘的函式,在大多數情形下,可以假設「n」引數是整數。JavaScript直譯器在為JIT收集執行時期資訊的時候可以判別這種情形。

而直譯器在以JIT編譯fact函式的時候,分別產生n可以是任意物件的「普遍版」以及假設n是整數的「高速版」。而後在n引數是整數的大多數情形下,可以執行高速版,達到與靜態語言幾乎相等的執行性能。

除此之外,最新的JavaScript實作也引進了各種驚人的技術。要說現在JavaScript是最快的動態語言也不為過。

伺服端的Ruby

開發用戶端程式的最大問題,是必須拜託各個用戶端逐一安裝相關軟體。Java與JavaScript登場的1990年代後半,要說Internet使用者都是進階使用者也不為過。不過現在Internet大為普及,使用群大為擴張,技術程度也變得分散許多。逐一要求所有用戶端安裝相關軟體的門檻變得非常高。

另一方面,伺服端幾乎沒有這樣的限制。可以選擇最適合自己的程式語言。

Ruby誕生的1993年,Internet還沒有普及,所以Ruby沒有特別以Web伺服端作為目標。不過在WWW黎明期有實現動態頁面的「CGI(Common Gateway Interface)」技術,這邊常常會拿Ruby來用。

CGI是Web伺服器透過標準輸出入與程式溝通、產生動態HTML頁面的介面。只要能處理標準輸出入,不管什麼語言都能寫CGI程式。能夠輕易做出動態頁面,突顯了WWW設計的靈活性呢。正因為如此,WWW才能如此普及吧。

在WWW之下,Web伺服器傳來的要求資訊是文字格式,回傳給Web伺服器的回應內容也是以文字構成的HTML內容,所以使用擅長處理文字的語言會比較輕鬆。這樣一來,就該scripting語言上場了。在這之前主要用來處理文字內容的scripting語言,應用範圍一口氣擴展開來。

初期以CGI產生Web頁面的程式多半以Perl撰寫,而後被稱為「Better Perl」的Ruby也越來越多人運用。

伺服端的Go

Go還是很新的語言,開發者是以UNIX開發者聞名的Rob Pike與Ken Thompson因而非常受到注目。

Go誕生的背景,似乎跟Google內的語言議題有關。Google為了最佳化程式開發環境,限制只能以C/C++、Java、Python、JavaScript開發自身產品。實際上似乎有偷偷使用Ruby的開發者,不過正式產品可以用的語言只有上面列的這幾個。

選用語言的方針,推測用戶端語言是JavaScript、伺服端語言的scripting用的是Python、大規模或要求高性能的地方用Java、檔案系統實作等平台底層系統開發則是用C/C++。似乎Google公司內對C/C++的不滿也不少。

話說回來,跟其他語言相較之下歷史悠久許多的C/C++,幾乎沒有像GC這類最近的語言會提供的程式開發支援功能。也因為如此,生產力不怎麼高,而有要求「更好的」系統程式設計語言的呼聲。而Go就是為了滿足這個角色而新設計的語言。

Go有著許多特徵,(從筆者的角度來看)最重要的有下面幾點。

• 支援平行處理的Goroutine

• Structural Subtyping(結構化子型別)

靜態vs動態

前面把四種語言依照用戶端、伺服端的觀點來分類,這邊從動態、靜態的觀點來看看吧。

靜態指的是「只看程式原始碼的字面,不必執行就能確定」的意思,動態則是「執行時才能決定」。

不過,各種程式語言先不管程度如何,在某種程度來說都是動態的。如果程式完全是靜態的話,就代表只靠程式原始碼的字面分析就能得到所有結果了,而這樣一來就沒有把程式拿來執行的意義。比如說「計算6的階乘」的程式,如果寫成完全靜態的程式,就是這樣:

不過,除了像玩具般的範例之外,基本上不會開發這種程式。實際上都會有輸入資料、或者與使用者對話,每次執行的時候都載入不同的資料才對。

實現這種程式的程式語言,也會擁有動態的性質。

在談到語言是靜態還是動態的時候,通常指的是這些語言擁有的動態功能有著怎樣的限制、或是有沒有積極朝這個方向強化的設計方針。

比如說,這邊舉例的四個語言都是物件導向語言,而物件導向語言會擁有多型(polymorphism)或是動態綁定(dynamic binding)之類的動態性質。簡單來說,是配合儲存變數的物件的實際性質,自動選擇適當的處理(method),要說這功能是物件導向程式設計的本質也不為過。

被分類到「動態」的程式設計語言,它們動態的部份,主要是「執行模型」與「型別」。這兩個是彼此獨立的概念,但引進動態型別的語言,執行模型會有偏向動態的傾向。同樣地,靜態語言則是執行模型在執行時的靈活性有受到限制的傾向。

動態執行模型

動態執行模型,一言以蔽之,就是執行中的程式可以看懂程式本身、進行操作的意思。

這是對程式自身進行操作的程式設計,所以也稱為「metaprogramming」。

Ruby與JavaScript的metaprogramming十分自然,查詢某個物件擁有哪些方法、或是在執行時定義新class、method都是理所當然的事情。

另一方面,Java在需要metaprogramming的時候,必須透過「reflection API」進行。取得class、改寫內容都辦得到,但不像Ruby與JavaScript那樣自由自在,而比較有「雖然辦得到,但平常用不太到」的感覺。

Go的情形也一樣。Go可以透過「reflect套件」取得程式的執行時期資訊(主要是型別),但(在筆者的理解之中)不允許進一步metaprogramming。不允許Java那樣的動態執行模型的理由,推測大概是因為系統程式設計領域內不怎麼必要(也是啦),還有怕執行速度受到影響等等。

型別是什麼

從一般的角度來說,「型別」指的是「一份資料擁有哪些性質」的敘述。它擁有怎樣的結構、允許哪些操作等等。而「資料擁有型別、只有資料擁有型別」是動態型別的立場,「資料也擁有型別,但儲存資料的變數、運算式也都擁有編譯時可決定的型別」則是靜態型別的立場。

不過,就算是靜態型別,物件導向語言也需要多型之類的動態性質,所以會多加「實際的資料(的型別),屬於靜態型別指定的型別的子型別」這條規則。子型別(subtype)指的是擁有繼承關係、擁有相同介面等等,靜態型別與資料的型別在系統上「擁有相同性質」的東西。

靜態型別的好處

動態型別比較簡單、靈活性高,但靜態型別也有好處。有了編譯時確定的型別資訊,就更容易發現臭蟲。當然,程式的臭蟲多半與邏輯有關,單純的型別問題反而是少數,但邏輯問題常常隨著編譯時能檢查的型別一致性問題而生。也就是說,很可能隨著型別錯誤找到其他問題。

除此之外,程式內寫了型別資訊,也能成為閱讀程式時的提示,或是成為確實描述程式運作流程的文件,這是很大的好處。

另外,靜態型別也讓編譯過程中最佳化時能運用的資訊變多,讓編譯器能產生更好的機器碼、有提昇效能的趨勢。不過,有了JIT之類的功能,讓動態語言也能得到與編譯出來的原生機器碼相近的效能,所以今後靜態語言與動態語言的性能差距應該會縮小才對。

動態型別的好處

反之,動態型別的好處,就是簡潔與靈活了吧。

從極端的角度來說,型別資訊與程式執行的本質無關。計算階乘的程式,以明確描述型別的Java來寫(圖3)、還是以不必明確描述型別的Ruby來寫(圖4),演算法都完全沒有差別。不過,多了型別等資訊的Java版本之中,不屬於演算法本身的程式碼比例比較大。

除此之外,有些型別也會造成制約。圖3、圖4的程式計算到6的階乘,若是增加要計算的階乘數,Java將無法求得13以上的階乘。圖3的程式裡fact method接受的引數型別明確指定為int,而Java的int是32bits,也就是說只能表現到20億的整數。若計算結果超出這個範圍,就會導致溢位。

當然,因為Java擁有豐富的函式庫,所以可以用「BigInteger class」,以沒有上限的整數進行計算,但這需要要大幅改寫程式。有著為了「int是32 bits寬」這個方便電腦處理的條件,降低階乘計算靈活性的感覺。

另一方面,Ruby版沒有這樣的限制,所以不管是13的階乘、還是200的階乘,都能直接計算出來。不必擔心int的大小之類,跟電腦處理方便性有關的條件。

這邊其實有點棘手。同樣身為動態語言的JavaScript以圖1的程式計算200的階乘,會得到「Infinity」(無限大)。其實是JavaScript計算數值時用的是浮點數,支援的數字範圍沒有Ruby這麼大的緣故。也就是說,要不受限制進行計算,不僅型別性質重要,函式庫的支援也不可或缺呢。(摘錄整理自第三章)

 

新要求、新環境、以及新模範都會導向新的設計。學習現有的語言設計以及它們的取捨,可說是對未來的語言所作的準備吧。

 

松本行弘談程式世界的未來

松本行弘/著

Studio Tib./譯

碁峯出版

售價:450元

 

作者簡介

松本行弘

筑波大學第三學群情報(資訊)學類畢業。1993年著手開發物件導向指令稿語言「Ruby」,於1995年公開。現在兼任網路應用通訊研究所(NaCl)研究員、樂天技術研究所研究員、Heroku總架構師。以「Matz」的通稱為人所知。

熱門新聞

Advertisement