基於範型的多語言編程

基於範型的多語言編程

作者 Sadek Drobi譯者 韓鍇

【整理】:Ackarlix

 

你在同一個項目中會用到多少種語言?如果算一算的話,會發現數量真的不少。我指的是XML、Java、XSLT、HTML和CSS等等。但是,你爲什麼會用到它們,原因無非在於它們就是主流,更何況,它們可能是某個必需框架下的唯一可選的語言。實際上,你幾乎是被迫使用這些語言的。所有選擇都已經替你完成了。樣式?CSS。配置?通常是XML。Web界面描述?Html。然而,如果你想真正採用多語言編程,就不可避免地要從衆多語言中做出選擇。

我想如果一個人想要做出正確的選擇,就必須時刻牢記,採用多語言編程的主要目的在於能夠選擇正確的語言解決手頭的業務問題。於是,現在的問題就在於如何爲給定的領域及子領域選擇正確的語言?

首先,理解現有語言的特性是至關重要的(包括與生產環境相關的因素以及在企業級項目中各種限制語言選擇的因素)。理解語言的特性並不僅僅意味着理解它的優點和缺點,更重要的是,要理解語言是如何對世界進行描述(建模)的。

理解現有的程序設計語言

我們可以這樣看待程序設計語言,它是一個有限的詞彙與規則的集合,那些詞彙與規則可以組合起來描述一個特定的問題。語言提供的結構和概念,在很大程度上決定了我們描述特定領域問題的能力。這意味說,程序設計語言中非常重要的兩個方面包括:1)現有的詞語和規則集合,2)已存在的組合規則。

編程語言的範型

一個連貫完整的詞彙集以及組合規則,可以產生一種範型,從而定義出很大一部分的語言特質(或者是在某種多範型程序設計語言中的一個子集語言的特質)。

在思考現有的語言時,範型是最重要的特性。真正關鍵的是使用恰當的範型生成簡潔的、可讀性強的代碼。使用正確的範型,有助於保持問題域和軟件模型之間的一致性,由此能夠創建出更清晰的模型,產生更高的可讀性。使用了錯誤的範型則恰恰相反,它通常不可避免地會產生大量的ad-hoc代碼,代碼數量會爆炸式地增長(大量代碼完成一個相對直接的任務),並出現hack行爲。

編程語言範型示例

讓我們以Xml爲例。Xml可以這樣來描述:它是一些元素的集合,其中每個元素還可以包含屬性(或者標籤)。元素中還可以嵌套其他元素,這樣一個元素可以包含其他元素,屬性可以設置到每個元素上。這可能表現了XML這種語言最重要的特質——組合式的本質。簡而言之,這些語言範型的信息已經足以應付我們稍後在領域分析階段中的所需(cf. infra)。到此爲止,我們已經瞭解到Xml擁有一個內嵌式的結構;它的構造包含了最高地位(first class)的元素,元素又包含了地位其次的屬性。

再來看看Lua。它的構建是基於表(table)的。Lua可以描述爲最高地位的鍵/值表包含其他的表(table)。因此,很明顯,它的樣子看上去類似於表格,我們暫且稱其爲表格式(tabular)範型。它同樣揭露了足夠多關於範型的信息,至少足夠幫我們應付(對領域問題)最初的快速分析。

我們可以使用相同的方式來考察其他語言。Lisp的主要數據結構是列表(List),使用Lisp編程可以想象爲列表操作,這也順理成章地成爲了該語言底層的範型。Lisp同時還是一種函數式編程語言,其最高地位的函數可以組合起來,生成程序其他的部分,這反映出它的函數式編程範型。Haskell是另一種函數式程序設計語言,但它同時還帶着濃濃的數學特質。這種強類型的語言擁有廣爲人知的強大(數據)類型,這是一個重要的抽象工具,它使得Haskell 成爲了一種兼顧安全與強大抽象能力的語言。Prolog主要是由布爾謂詞和斷言組成的。正像它的名字暗示的那樣,Prolog可以看作是一種基於邏輯的程序設計語言。

其他需要考慮的語言特質

在你爲某個特定子領域選擇語言時,除了前面提到的範型,還需要考慮其他一些特質。類型種類(動態/靜態),句法和其他一些重要性低於範型的因素。這篇文章並不打算更進一步介紹這些特性的細節。我的建議是不要高估它們的重要性,把更多的注意力放在範型上,它纔會在子領域的編碼表現力上扮演真正重要的角色。

發現與定義不同子領域的差異性

當考慮多語言編程時,我們大多可能已經做出了決定,對於眼前項目的領域而言,有幾種語言可以使用。通常,這樣的決定來自於兩件事:明確知曉某些語言更加適合某些種類的問題,另外,注意到給定的領域是由多個可能具有不同特質的部分組成的。

因此,一個問題域可以被分爲多個部分(子領域)。這種劃分立刻就能明顯體現出的各部分的性質差異,令子領域之間的區隔清晰地顯露出來。但是,這些子領域之間仍然存在交互,在實現交互的時候,不應該忽略它們特質上的不同。

一旦將問題領域正確地分爲了多個子領域,接下來尋找與領域一致的程序設計語言的任務就很容易了。領域問題通常都非常繁雜,很難從一個角度窺視其全貌。通過分拆問題,我們有機會使用不同的工具來解決不同的問題,因爲正確的工具支持更好的抽象和設計。

然而要想識別出子領域和它們的特質並不容易。它需要我們具備分析能力以及對目標領域的全盤掌握。在這個過程中,爲了避免得出關於子領域特質的錯誤結論來,應該和領域專家一起緊密協作。這項工作的主要內容是識別出各模塊的屬性和它們之間的交互行爲,並將交互的模式和已有的一些概念(可以將它們描述爲範型或者子領域的特徵)進行抽象。有很多技術和實踐能夠幫助我們學習和發現領域知識與特質,比如Eric Evan的著作“領域驅動設計”中提及的Domain Crunching,以及James Coplien的多範型(Multi-Paradigm)設計等等。

時刻在頭腦中牢記各種設計語言範型,這的確有助於我們將領域概念規範化,以和建模的構建相匹配;但是,不能矯枉過正,以至於在分析問題時摻雜過多的外力,刻意改變領域概念以讓它適合首選的範型。在OOP中,如果領域對象都默認被當成一個對象,這種現象就非常普遍了。

爲子領域選擇正確的語言/範型

識別出每個子領域以後,就可以爲它們挑選正確的程序設計語言了。什麼是“正確的語言”?簡而言之,就是可以用它以最流暢的方式編寫子領域描述的問題。編寫程序的時候,我們通常會受到語言及其概念的影響。某種語言在解決特定的子領域問題時可能非常笨拙,且代碼會令人費解;但如果選擇了合適範型的語言,就能利用子領域與程序設計語言概念之間的一致性,產生更簡潔與更具備擴展性的代碼。選擇範型匹配的語言的重要優勢之一就是“可擴展性”。領域自身不會突然改變它的特質,即使它會不斷地發展。因此,完全可能沿着同一種範型的路線一直走下去。如果語言的特質與領域的範型匹配,那麼可以斷定,在領域發展的過程中,這將會減少變化所產生的開銷,並控制的變化的範圍。

案例:子領域 - 程序設計語言範型的一致性

圖形用戶界面

接下來看一看圖形用戶界面。當我們觀察一個界面時,所見的是層層嵌套的面板。每個面板內部又包含很多圖形組件(按鈕、圖形、構建塊等),後者又可以包含其他的控件,控件還可以包含其他控件。通過這樣簡單的分析,我們認識到圖形用戶界面具有嵌套的特質。一件事物可以嵌套在另一個事物中。同時任何組件又都具有屬性。有了這些事實基礎後可以看出,XML似乎是開發GUI的完美語言。

然而,我想我們的結論來得有些急躁。我覺得有很大一部分GUI都可以用XML來表現,但未必是所有事情。WPF和XAML是很好的研究案例。XAML採用了基於XML的語法,正如我們在前面所說的,這是爲了適應GUI所具有的嵌套包含的特質。但是,也有一些工作使用的不是基於XML的特殊語法,比如元素綁定。還有很多圖形界面描述的子領域是可以得益於使用比XML更適合的語言。我們以樣式爲例。使用XML編寫樣式會帶來大量的干擾,因爲xml的開閉標籤並不能爲這個子領域表達任何有意義的信息。尋找表現樣式的更佳方法應該認真地考慮它的特質。但是,圖形樣式描述究竟有什麼特質呢?GUI的樣式無非就是一個屬性集,要求能通過一個特定的名稱找到對應的值。更進一步,樣式並不關心它的屬性值被用在哪裏;也許是一個組件,或者一個子組件,或者粒度更小的東西。樣式僅僅是對屬性的描述。如果組件擁有某個屬性,就會考慮使用該屬性值。否則可以把它們忽略,也完全沒有任何影響。通過這些簡單的分析,我們就能明白,爲什麼CSS爲圖形樣式這一子領域提供了極佳的句法選擇。

與流和數據有關的流程處理

想象我們將要處理一個由動作(action)、信息或者數據構成的流。基本上這樣的流都是無止境的,它們經過修改、處理,產生另一個序列——基於這個流的另一個動作、信息或者數據流。它的實例包括RSS feed、http請求/響應,網絡信號……還有很多與領域相關的流。這些例子當然都具有面向流的範型,畢竟是爲了舉例而特別找的;在實際工作中,經過充足的領域分析後,勾畫出這種特性並不難。選擇具有同樣特質的技術,有助於降低代碼的複雜度,並提高代碼的可讀性及質量。否則,你會看到自己在代碼中寫了大量的嵌套循環和“while”塊,這通常是模塊化被削弱的起點。

通常,函數式語言(比如Haskell)有種有趣的特質,可以處理無窮的列表和流。對於面向流的範型而言,函數可以串連在一起,組成一個顯式的工作流,這樣可以產生一個更具模塊化的方案。

更多關於範型與子領域相匹配的實例

這樣的例子還有很多,領域與技術方案的範型相匹配,這在選擇工具的時候非常關鍵。如果某個領域中包含了大量的規則,那麼校驗謂詞可以從面向謂詞的或者邏輯式語言中受益匪淺。比如Prolog和Haskell就是這類語言。像Erlang這樣的併發程序設計語言最適合基於併發的子領域。而涉及大量字符串處理的子領域可以使用具有強大的字符串處理範型的語言。

多範型程序設計語言

有些程序設計語言並不只提供單一的範型,而是混合了多個可供選擇的範型。儘管它看上去可能改善範型匹配的程度,但是並不能從根本上取代多語言編程。這兩種 “多範型設計”的方法分別有其優點和缺點。舉例來說,對於單一範型的語言,其語法已經被最大限度地優化,以表現它關注的範型。另一方面,對於多範型語言來說,在設計語言自身的同時就考慮到了將若干種範型進行整合。這既可能是優勢——我們可以更容易地享受多種範型,也可能是劣勢——缺少直觀的表現力。多範型程序設計語言會變得越來越複雜,不過其中也有一些由於成爲主流而從中收益頗豐,比如C#和C++。

在從這兩種技術中做出選擇時,需要考慮這些以及其他各種權衡條件。由於生產環境的限制,目前並非能夠真正做到使用多種語言(不過這個事實正在一點點地被瓦解,我們已經看到在有些主流的平臺上實現了很多有趣的語言),那麼多範型語言可能是一個很好的選擇。團隊成員的技能也可能成爲一個受到限制的因素(不過有很多人,包括我,並不同意這樣的觀點)。

結論

雖然上述的一些原則可以激發使用多語言編程的動機,比如根據軟件架構分層來選擇語言,但是,這些方法過於簡單,而且並不能非常直接地、有效地解決問題,即哪些項目需要使用多於一個的程序設計語言?多語言設計的核心在於透徹的領域分析和多範型設計。而保持程序設計語言範型和子領域之間特質的匹配,其結果是編寫出更具有可讀性的、簡潔的、容易更新的代碼,這樣的代碼不會有過多的干擾。最大化多語言編程效力的第一步,是要理解並識別出程序設計語言的範型。而在這個項目開發的過程中,“領域驅動設計”和“多範型設計”兩項技術會一直伴你左右。

參考資料

James O. Coplien, Multi Paradigm Design

Eric Evans, Domain Driven Design

關於作者

Sadek Drobi是一名軟件工程師,他關注企業及應用程序的設計與實現。他對各種能夠彌合業務和開發人員之間隔閡的解決方案(例如敏捷、DSL、領域驅動設計)懷有濃厚的興趣。他目前的研究關注於面向語言編程和多範型設計。Sadek是來自Valtech Consulting的諮詢師。他還是一位相當專業的攝影家。他的技術博客是www.sadekdrobi.com,並在http://photos.sadekdrobi.com維護了一個相冊。

閱讀英文原文:Paradigm based Polyglot Programming

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章