2006 年 6 月 12 日 當(dāng)談到 Java? 語(yǔ)言的類(lèi)型方法時(shí),Java 社區(qū)分為兩大陣營(yíng)。一些人喜歡編譯時(shí)錯(cuò)誤檢查,更好的安全性,以及改善的工具 —— 這些都是靜態(tài)類(lèi)型所能提供的特性。而另一些人則偏愛(ài)更動(dòng)態(tài)的類(lèi)型體驗(yàn)。這一次在 跨越邊界 中,您將看到兩種高生產(chǎn)力的非 Java 語(yǔ)言所使用的一些截然不同的類(lèi)型策略,并發(fā)現(xiàn)在 Java 編程中提高類(lèi)型靈活性的一些方法。 在對(duì)任何編程語(yǔ)言的討論中,爭(zhēng)議較大的一個(gè)問(wèn)題就是類(lèi)型模型。類(lèi)型決定可以使用哪些種類(lèi)的工具,并影響到應(yīng)用程序的設(shè)計(jì)。很多開(kāi)發(fā)人員將類(lèi)型與生產(chǎn)率或可維護(hù)性聯(lián)系起來(lái)(我就是其中的一個(gè))。典型的 Java 開(kāi)發(fā)人員通常都特別樂(lè)于維護(hù) Java 語(yǔ)言的類(lèi)型模型的地位,強(qiáng)調(diào) Java 語(yǔ)言可采用更好的開(kāi)發(fā)工具,在編譯時(shí)捕捉某些種類(lèi)的 bug(例如類(lèi)型不兼容和拼寫(xiě)錯(cuò)誤),以及性能等方面的優(yōu)勢(shì)。 如果您想理解一種新的編程語(yǔ)言,甚至一系列語(yǔ)言,那么通常應(yīng)該從類(lèi)型策略著手。在本文中,您將看到 Java 之外的一些語(yǔ)言中的類(lèi)型模型。我首先簡(jiǎn)要介紹任何語(yǔ)言設(shè)計(jì)者在類(lèi)型模型中必須考慮的一些決策,著重介紹靜態(tài)類(lèi)型和動(dòng)態(tài)類(lèi)型的一些不同的決策。我將展示一些不同極端的例子 —— Objective Caml 中的靜態(tài)類(lèi)型和 Ruby 中的動(dòng)態(tài)類(lèi)型。我還將談到 Java 語(yǔ)言的類(lèi)型限制,以及如何突破 Java 類(lèi)型的限制快速編程。
至少可以從三個(gè)角度來(lái)看待類(lèi)型:
下面兩個(gè)例子很好地闡釋了其中兩個(gè)角度的內(nèi)涵。假設(shè)您編譯下面這段 Java 代碼:
會(huì)收到如下錯(cuò)誤消息:
執(zhí)行以下 Ruby 代碼:
會(huì)收到以下錯(cuò)誤消息:
這兩種語(yǔ)言都傾向于強(qiáng)類(lèi)型,因?yàn)楫?dāng)您試圖使用一個(gè)它們期望之外的類(lèi)型結(jié)構(gòu)的對(duì)象時(shí),它們都會(huì)拋出錯(cuò)誤消息。Java 類(lèi)型策略在編譯時(shí)給出錯(cuò)誤消息,因?yàn)樗鼒?zhí)行靜態(tài)類(lèi)型檢查。而 Ruby 則是在運(yùn)行時(shí)給出錯(cuò)誤消息,因?yàn)?Ruby 支持動(dòng)態(tài)類(lèi)型。換句話說(shuō),Java 在編譯時(shí)將對(duì)象綁定到類(lèi)型。而 Ruby 則是在運(yùn)行時(shí)每當(dāng)更改對(duì)象的時(shí)候?qū)?duì)象綁定到類(lèi)型。由于我是在 Java 代碼中,而不是在 Ruby 中聲明變量的,因此可以看到 Java 語(yǔ)言的顯式類(lèi)型與 Ruby 的隱式類(lèi)型的工作方式不同。 在這三個(gè)角度中,靜態(tài)類(lèi)型與動(dòng)態(tài)類(lèi)型對(duì)于語(yǔ)言的特征有最大的影響,因此接下來(lái)我將重點(diǎn)解釋這兩種策略各自的優(yōu)點(diǎn)。 在靜態(tài)類(lèi)型語(yǔ)言中,程序員(通過(guò)聲明或根據(jù)約定)或編譯器(根據(jù)結(jié)構(gòu)和語(yǔ)法線索)將一種類(lèi)型指定給一個(gè)變量,然后那個(gè)類(lèi)型就不會(huì)改變。靜態(tài)類(lèi)型通常需要額外的成本,因?yàn)殪o態(tài)類(lèi)型語(yǔ)言(例如 Java 語(yǔ)言)通常是顯式類(lèi)型的。這意味著必須聲明所有的變量,然后編譯代碼。成本也伴隨著收益:早期的錯(cuò)誤檢測(cè)。靜態(tài)類(lèi)型在最基層為編譯器提供多得多的信息。更多信息所帶來(lái)的好處就是,可以更早地捕捉到某些類(lèi)型的錯(cuò)誤,而動(dòng)態(tài)類(lèi)型語(yǔ)言只有到運(yùn)行時(shí)才能檢測(cè)到這些錯(cuò)誤。如果您一直等到運(yùn)行時(shí)才捕捉這些 bug,那么其中一些將進(jìn)入生產(chǎn)環(huán)境。也許這正是動(dòng)態(tài)類(lèi)型語(yǔ)言受到最多指責(zé)的一個(gè)方面。 另一種觀點(diǎn)則認(rèn)為現(xiàn)代軟件開(kāi)發(fā)團(tuán)隊(duì)通常會(huì)運(yùn)行自動(dòng)測(cè)試,動(dòng)態(tài)語(yǔ)言的支持者聲稱,即使是最簡(jiǎn)單的自動(dòng)測(cè)試也可以捕捉到大多數(shù)的類(lèi)型錯(cuò)誤。而動(dòng)態(tài)語(yǔ)言的支持者所能提供的對(duì)編譯時(shí)錯(cuò)誤檢測(cè)不利的最好論據(jù)是,早期檢測(cè)所帶來(lái)的好處相對(duì)于成本來(lái)說(shuō)是得不償失的,因?yàn)椴还苁欠袷褂脛?dòng)態(tài)類(lèi)型,最終都要進(jìn)行測(cè)試。 一種有趣的折中方法是在靜態(tài)類(lèi)型語(yǔ)言中使用隱式類(lèi)型,從而減少類(lèi)型的成本。開(kāi)放源代碼語(yǔ)言 Objective Caml (OCaml) 是靜態(tài)類(lèi)型語(yǔ)言 Lisp 的衍生物,它既能提供很好的性能,又不會(huì)犧牲生產(chǎn)率。OCaml 使用隱式類(lèi)型,因此可以編寫(xiě)下面這樣的采用靜態(tài)類(lèi)型的代碼:
OCaml 返回:
根據(jù)表達(dá)式中的語(yǔ)法線索,OCaml 推斷出 隱式類(lèi)型系統(tǒng)一個(gè)較大的優(yōu)點(diǎn)是,不需要為函數(shù)的參數(shù)聲明類(lèi)型,因?yàn)榫幾g器會(huì)從傳入的值推斷出參數(shù)的類(lèi)型。因此同一個(gè)方法可以有多種用途。
并不是只有編譯器才能利用靜態(tài)類(lèi)型所提供的附加信息。IDE 可以通過(guò)靜態(tài)類(lèi)型為重構(gòu)提供更好的支持。幾年前,一種革命性的思想改變了開(kāi)發(fā)環(huán)境的工作方式。在 IDEA 和 Eclipse 中,您的代碼看上去像一個(gè)文本視圖,但是開(kāi)發(fā)環(huán)境實(shí)際上正在編輯 Abstract Syntax Tree (AST)。因此,當(dāng)需要重新命名一個(gè)方法或者類(lèi)的時(shí)候,開(kāi)發(fā)環(huán)境很容易通過(guò)在 AST 中精確定位找到方法或類(lèi)被引用的每個(gè)地方。如今,如果沒(méi)有通過(guò)靜態(tài)類(lèi)型簡(jiǎn)化的優(yōu)秀的重構(gòu),我們很難想像用 Java 語(yǔ)言編程。在我探索 Ruby 的時(shí)候,相對(duì)于其他任何工具或特性,我更懷念 IDEA。 靜態(tài)類(lèi)型還有其他一些優(yōu)點(diǎn),在這里我不會(huì)詳細(xì)描述。靜態(tài)類(lèi)型可以提供更好的安全性,而且顯然還可以提高代碼的可讀性。靜態(tài)類(lèi)型還可以提供更多的信息,使得編譯器更容易進(jìn)行優(yōu)化,從而提高性能。但是靜態(tài)類(lèi)型贏得開(kāi)發(fā)人員青睞的最大原因是更容易檢測(cè)錯(cuò)誤,而且有更多可用的工具。 動(dòng)態(tài)類(lèi)型的優(yōu)點(diǎn) Ruby 專(zhuān)家 Dave Thomas 將動(dòng)態(tài)類(lèi)型稱作duck typing(見(jiàn)參考資料),這有兩層意思。第一層意思是說(shuō),這種語(yǔ)言不真正實(shí)現(xiàn)類(lèi)型 —— 它利用鴨子理論 解決這個(gè)問(wèn)題。第二層意思是說(shuō),如果什么東西走起來(lái)像鴨子,叫起來(lái)也像鴨子,那么它很可能就是一只鴨子。在編程語(yǔ)言的上下文中,duck typing 意味著如果一個(gè)對(duì)象對(duì)于某種類(lèi)型的方法有反應(yīng),那么事實(shí)上就可以把它看作那種類(lèi)型。這樣的特性可以導(dǎo)致一些有趣的優(yōu)化。 大多數(shù)偏愛(ài)動(dòng)態(tài)類(lèi)型的開(kāi)發(fā)人員除了強(qiáng)調(diào)早期錯(cuò)誤檢測(cè)會(huì)帶來(lái)不必要的成本外,還提到動(dòng)態(tài)類(lèi)型語(yǔ)言具有很好的可表達(dá)性和生產(chǎn)率。很簡(jiǎn)單,您通常可以用更少的關(guān)鍵詞表達(dá)更多的思想。作為一名新的 Ruby 擁護(hù)者,我深信動(dòng)態(tài)語(yǔ)言更能提高生產(chǎn)率,雖然我不能比常見(jiàn)的靜態(tài)語(yǔ)言的支持者拿出更多具體的證據(jù)來(lái)。但是,從我開(kāi)始編寫(xiě)更多的 Ruby 代碼起,我就感覺(jué)到自己的生產(chǎn)率有了明顯的提高。誠(chéng)然,我仍然會(huì)看到靜態(tài)類(lèi)型的優(yōu)點(diǎn),尤其是在工具集方面,但是我逐漸認(rèn)識(shí)到了靜態(tài)類(lèi)型的缺點(diǎn)。 當(dāng)我開(kāi)始用 Ruby 編寫(xiě)代碼時(shí),我受到的最大改變是產(chǎn)生和使用元編程結(jié)構(gòu)的能力。如果您從頭開(kāi)始一直關(guān)注跨越邊界 系列統(tǒng)的話,您就知道元編程,或者說(shuō)編寫(xiě)用于編寫(xiě)程序的程序,是 Ruby on Rails 的一大推動(dòng)力量,更一般地說(shuō),是特定于領(lǐng)域的語(yǔ)言的一大推動(dòng)力量。用 Ruby 編程時(shí),我通常會(huì)編寫(xiě)更大的構(gòu)建塊,或者用更大的塊進(jìn)行構(gòu)建。我發(fā)現(xiàn),與使用 Java 編程相比,我可以用更多類(lèi)型的可重用塊擴(kuò)展我的程序。就像在 Java 編程中,您可以用新的類(lèi)來(lái)擴(kuò)展程序。還可以添加方法和數(shù)據(jù)到已有的類(lèi)中,因?yàn)轭?lèi)是開(kāi)放式的。您可以使用 mix-in(后面 運(yùn)行時(shí)綁定 會(huì)講到)來(lái)添加核心功能到已有的類(lèi)中。還可以在任何時(shí)候根據(jù)需要改變一個(gè)對(duì)象的定義。我還是一名 Ruby 編程新手,這些功能用到的不多,但是當(dāng)我真正開(kāi)始使用它們時(shí),結(jié)果令人大吃一驚。 例如,為了添加一個(gè)攔截器,只需重新命名一個(gè)方法,并為原有的方法創(chuàng)建一個(gè)新的實(shí)現(xiàn)。為了攔截
您不需要 AspectJ 庫(kù)、字節(jié)碼增強(qiáng)或一大堆的庫(kù)。您可以直接編寫(xiě)所需的攔截器。 動(dòng)態(tài)類(lèi)型在原始代碼行方面也可以節(jié)省精力。由于動(dòng)態(tài)語(yǔ)言幾乎都是類(lèi)型推斷式的,所以您不需要花多大力氣來(lái)表達(dá)基本思想。變量無(wú)需聲明即可直接使用。您也不必表達(dá)參數(shù)類(lèi)型的所有可能排列,只需輸入一組名稱。您的代碼可以更加具有多態(tài)性 —— 任何對(duì)一種類(lèi)型的方法有反應(yīng)的對(duì)象都可以看作這種類(lèi)型 —— 所以通常可以比其他語(yǔ)言更精簡(jiǎn)地表達(dá)思想。代碼中的耦合也可以變得更松散。當(dāng)您想改變某個(gè)東西的類(lèi)型時(shí),這種變化所波及的范圍很有限,所以不需要在更多的地方作出相應(yīng)的更改。
生產(chǎn)率提高的最后一個(gè)原因是減少了編譯環(huán)節(jié)。很多動(dòng)態(tài)類(lèi)型語(yǔ)言是解釋性的,所以在編寫(xiě)程序后可以立即看到變化。即使沒(méi)有慣用的調(diào)試器,在 Ruby 中探索庫(kù)和應(yīng)用程序代碼的行為也更為容易,因?yàn)槟梢源蜷_(kāi)一個(gè)解釋器,通常可以直接在調(diào)試會(huì)話中打開(kāi)(我在 本系列的上一篇文章 中就展示了這一點(diǎn)),然后隨意探索。 然而,編譯不只是支持靜態(tài)類(lèi)型。靜態(tài)類(lèi)型的支持者還認(rèn)為可以獲得更好的性能。很多靜態(tài)語(yǔ)言,例如 Java 代碼、C 和 C++,都被稱作系統(tǒng)語(yǔ)言,因?yàn)樗鼈兪菢?gòu)建操作系統(tǒng)、設(shè)備驅(qū)動(dòng)程序和其他高性能系統(tǒng)代碼的最常用的語(yǔ)言。這又經(jīng)常導(dǎo)致動(dòng)態(tài)語(yǔ)言的支持者指責(zé)靜態(tài)語(yǔ)言總是太低級(jí),用它們來(lái)編寫(xiě)應(yīng)用程序生產(chǎn)率很低 —— 但那是一種很狹隘的觀點(diǎn)。OCaml 語(yǔ)言是一種很高級(jí)的語(yǔ)言,支持面向?qū)ο蟪绦蛟O(shè)計(jì)、函數(shù)式程序設(shè)計(jì)(如 Lisp 或 Erlang)或傳統(tǒng)的結(jié)構(gòu)化程序設(shè)計(jì)。其類(lèi)型模型是靜態(tài)的,很多人說(shuō)它的性能甚至比 C++ 的性能還好(參見(jiàn) 參考資料)。使用 OCaml 時(shí),靜態(tài)類(lèi)型導(dǎo)致的開(kāi)銷(xiāo)很小,因?yàn)檫@種語(yǔ)言是類(lèi)型推斷式的。雖然付出了這一點(diǎn)成本,但可以得到非常好的性能,編譯時(shí)類(lèi)型檢查,以及一個(gè)非常高級(jí)的語(yǔ)言。即使是 duck typing 最頑固的支持者也不得不承認(rèn)那些優(yōu)點(diǎn)。
Java 開(kāi)發(fā)人員充分利用靜態(tài)類(lèi)型。他們有最好的開(kāi)發(fā)工具,這些工具帶有代碼完成和重構(gòu)等功能,這些都傾向于靜態(tài)類(lèi)型?,F(xiàn)在開(kāi)始利用測(cè)試優(yōu)先開(kāi)發(fā)的很多 Java 程序員獲得了更大的穩(wěn)定性,因?yàn)榫幾g器可以捕捉與類(lèi)型相關(guān)的 bug。新的類(lèi)型特性,例如泛型,增強(qiáng)了類(lèi)型模型,并為編譯器提供更多的信息。但 Java 開(kāi)發(fā)人員常常對(duì)動(dòng)態(tài)類(lèi)型的優(yōu)點(diǎn)一無(wú)所知。 動(dòng)態(tài)類(lèi)型的靈活性比您想像的更重要。在某些方面,Java 開(kāi)發(fā)人員試圖通過(guò)使用更多的 XML(這樣可以推遲到運(yùn)行時(shí)進(jìn)行綁定)和字符串(這樣可以表示很多不同的類(lèi)型)來(lái)突破靜態(tài)類(lèi)型的限制。Ruby 中的配置通常采用 Ruby 代碼的形式,而 Java 編程中的配置通常采用 XML 的形式??紤] Spring 框架(參見(jiàn) 參考資料):為了配置一個(gè)一般的 Spring bean,您使用 XML。您必須提供一個(gè)有效的 Java 類(lèi)名,并為每個(gè)變量設(shè)置屬性。例如,持久引擎(如 Hibernate)需要一個(gè)會(huì)話工廠(參見(jiàn) 參考資料)。用 Java 語(yǔ)法配置一個(gè)數(shù)據(jù)訪問(wèn)對(duì)象很輕松:
問(wèn)題是,這行代碼是在編譯時(shí)綁定的,這就太靜態(tài)了。為了測(cè)試,您常常需要用其他東西,例如一個(gè)模擬的數(shù)據(jù)訪問(wèn)對(duì)象來(lái)替換會(huì)話工廠或數(shù)據(jù)訪問(wèn)對(duì)象。所以,您不必像前面那樣硬編碼這個(gè)例子,而是使用一個(gè) Spring 之類(lèi)的框架,以 XML 來(lái)配置項(xiàng)目,如下所示(摘自名為 petclinic 的 Spring Framework 例子):
Spring 框架是目前 Java 社區(qū)中最重要、最有影響力的框架之一,因?yàn)樗鼓梢匝舆t綁定,并使系統(tǒng)主要元素之間的耦合性更為松散。而且,您不需要關(guān)心繼承就可以去耦。在 Java 編程中,尤其是在編寫(xiě)越來(lái)越多的 POJO(plain old Java object)的時(shí)候,使用繼承時(shí)必須特別小心,因?yàn)樵?Java 語(yǔ)言中只有一次這樣的機(jī)會(huì)。 在動(dòng)態(tài)語(yǔ)言,例如 Ruby 中,解決方案就截然不同。首先,我傾向于使用一個(gè) mix-in 來(lái)實(shí)現(xiàn)持久性。所有關(guān)聯(lián)只在 mix-in 中發(fā)現(xiàn)一次??梢园岩粋€(gè) mix-in 想像成一個(gè)接口,其背后有一個(gè)實(shí)現(xiàn)。換句話說(shuō),通過(guò) mix-in,可以添加多個(gè)功能到同一個(gè)對(duì)象中,而不必使用多重繼承。實(shí)際上,Active Record 通過(guò)繼承一個(gè)公共基類(lèi)來(lái)解決這個(gè)問(wèn)題,這個(gè)公共基類(lèi)混合了多種功能:
在 Ruby 中,您不必關(guān)心繼承,因?yàn)槭褂瞄_(kāi)放的類(lèi)(允許動(dòng)態(tài)添加功能)和模塊(允許混入其他功能),您可以隨意添加更多的功能到對(duì)象中。那么緊密耦合呢?如果您想按 Java 的方式實(shí)現(xiàn)該類(lèi),那么可以看到:
從某種意義上講,作為某種編程語(yǔ)言的用戶,您就是那種語(yǔ)言的類(lèi)型策略的奴隸。而作為一名 Java 程序員,您應(yīng)該盡量用一種擁護(hù)類(lèi)型的方式編寫(xiě) Java 代碼。最大限度地利用類(lèi)型,并依靠社區(qū)來(lái)通過(guò)框架獲得更好的元編程支持,而不是自己進(jìn)行元編程,這些都是發(fā)揮自身優(yōu)勢(shì)的好方法。有很多 Java 框架都支持用于持久性(Hibernate 和 JDO)、事務(wù)(Spring 和 EJB)、模型-視圖-控制器(WebFlow 和 RIFE)以及編程模型(AspectJ)的元編程。 但是有時(shí)候需要放棄您所選擇的語(yǔ)言的類(lèi)型,不管您是在編寫(xiě)需要附加描述以獲得更好可讀性的代碼,還是試圖延遲類(lèi)型綁定,都可以這樣。Java 語(yǔ)言非常強(qiáng)大,您可以利用很多現(xiàn)成的項(xiàng)目:
您還有一個(gè)選擇。通過(guò)理解其他語(yǔ)言中的類(lèi)型策略,可以識(shí)別不適合 Java 策略的問(wèn)題。當(dāng)需要訪問(wèn) Java 平臺(tái) 而不是 Java 語(yǔ)言 時(shí),可以使用其他語(yǔ)言的 JVM 實(shí)現(xiàn)(參見(jiàn) 參考資料)。 在本系列的下一篇文章中,您將看到 Ruby on Rails 中的測(cè)試。您可以看到 Rails 與 Java 語(yǔ)言共有的一些思想,有些方面 Rails 占優(yōu),而在某些方面 Java 又具有明顯優(yōu)勢(shì)。到那時(shí),請(qǐng)繼續(xù)關(guān)注 “跨越邊界” 的后續(xù)文章。 學(xué)習(xí)
獲得產(chǎn)品和技術(shù)
|
聯(lián)系客服