領域特定語言筆記

,發表於2017-02-03

第二章 領域特定語言的使用

2.1 領域特定語言的定義

書中定義如下:

Domain-specific language (noun): a computer programming language of limited expressiveness focused on a particular domain.

四個要點:

  1. DSL本身是一門程式語言;
  2. 語言特性(language nature),也就是說多個表示式連綴起來具有“語言”的流暢性;
  3. 有限的表達能力(limited expressiveness)
  4. 專注於特定的小領域 值得注意的是,第四點僅僅是“有限的表達能力”的一個結果。因為表達能力有限,所以僅適用於特定領域。

三個分類:

  1. 外部DSL 通常與宿主語言不一致。可以是自定義語言或者是其他語言。宿主語言使用文字解析技術對外部DSL進行解析。
  2. 內部DSL 內部DSL是宿主語言的一種特殊用法,符合宿主語言的規範,只不過是使用特殊的方式應用到了宿主語言特定的子集。只使用了宿主語言的部分特性。 內部DSL看起來像是一門自定義語言,但是是如假包換的宿主語言。 這方面的經典例子是LispRuby
  3. 語言工作臺(language workbench) 用來定義和構建DSL的整合開發環境(IDE)。會有專門一章對其進行說明,但是因為是新出現的,並且一直在演進,所以不會著墨太多。

DSL與語義模型

DSL是對語義模型進行操作的一種方式。

2.1.1 DSL的邊界

在這一節馬丁大叔從定義出發對於什麼屬於DSL,什麼不屬於DSL進行了區分。有點繞,但是還算清晰。

  1. 內部DSL與普通的命令查詢介面(command-query API)有什麼區別? 內部DSL是一種特殊的命令查詢介面。二者的區別在於“語言特性”。如果說後者僅定義了詞彙,那麼前者加入了語法(Mike Roberts)。 普通的命令查詢介面中每一個方法都是可以獨立使用的命令,而內部DSL的方法們組合起來使用才有意義。這也是後者被稱為流式介面(fluent interfaces)的原因。 內部DSL的“有限的表達能力”體現在對於通用語言(宿主語言)的使用方式。也就是說它僅僅用到了宿主語言的一部分特性,比如沒有使用條件判斷、迴圈結構和變數定義。內部DSL可以看做是一種“皮欽語”。

  2. 外部DSL與通用語言的邊界。 有些語言會專注於特定的領域,但是依然具有通用語言的表達能力,不能稱為DSL,例如R語言。“是否圖靈完備”可以作為一門語言是否是DSL的評定指標。所以外部DSL與通用語言的邊界在於是否具有“有限的表達能力”。 為了說明邊界的模糊性,作者接下來又舉了一個XSLT的例子。XSLT具有一門通用語言的所有特性,但是作者說在這個案例中,語言的使用領域決定了它是否是DSL。(也許是XSLT只用於對XML文件進行轉化的緣故?)

  3. 外部DSL與序列化資料結構的邊界。配置檔案中一系列賦值語句算不算DSL? 作者認為配置檔案的賦值語句缺乏流暢性,也就是不滿足“語言特性”這一條,所以不能稱之為DSL。 對於XML配置檔案,作者認為兩分來看。通常XML被看做是DSL,但是如果檔案是由其他工具生成的,僅僅用於序列化而不是供人使用,即便格式是對人友好的,也不被歸為DSL。區分點在於是否用於人與系統的互動。

  4. 語言工作臺與普通定義資料結構/表單的工具的邊界。 關鍵看工具的主要用途。

  5. 行話算不算DSL? DSL被定義為計算機語言,所以不算。行話可以成為“領域語言(domain language)”。如果行話被實現為計算機語言,那麼當然算。

總結:上述區分存在很多模糊地帶,因為“語言特性”以及“有限的表達能力”本身就很模糊。

2.1.2 片段(Fragmentary)與獨立的DSL

獨立的DSL可以單獨存在,比如存在在一個單獨的檔案中,使用者可以通過讀取這個指令碼檔案來理解業務領域知識。

DSL片段與宿主語言混雜在一起,理解業務知識需要讀懂宿主語言。

對於外部DSL來說,DSL片段的例子是正規表示式和SQL語句。

對於內部DSL來說,DSL片段的例子是註解以及測試用例中模擬物件的預期。

有的DSL既可以獨立使用,又可以分成片段使用,例如SQL語句。

2.2 為什麼要使用DSL?

DSL帶來的不是軟體開發領域的正規化轉移,而是適用於特定情形下的小工具。DSL是位於模型(類庫或框架)上的一層修飾(a thin veneer)。區分由模型帶來的好處和由DSL帶來的好處很重要。

2.2.1 提高開發生產率

主要體現在:

  1. 清晰地傳達程式碼所要表述的意圖,犯錯更少,糾錯更容易。
  2. 模型本身可以極大地提高生產率,而增加一層DSL可以通過更有表現力的形式對模型進行增強,因為DSL專注於API如何聯合使用,幫助開發者更好地學習API。
  3. 一個典型的應用場景是對介面怪異的第三方類庫進行封裝。

2.2.2 與領域專家進行溝通

可以做為與領域專家溝通的工具。

  1. 並非所有的DSL都適用於作為與領域專家溝通的工具,比如正規表示式就不適用;
  2. 適用於溝通工具的DSL,領域專家如果能夠直接閱讀的話,可以在很大程度上提高溝通效率,比如理解系統是如何執行的,以及及時發現規則裡面的錯誤。
  3. 在系統分析階段與專家一起建模,建立通用語言(Ubiquitous Language)以及DSL都是進行有效溝通的手段或技術。(通用語言與DSL的區別在於前者是人類語言而後者是計算機語言?)

2.2.3 執行環境的改變

(說實在話,這段沒大看懂。感覺書中所說的都是領域模型的好處,而跟DSL關係不大。)

2.2.4 另一種計算模型

命令式計算模型描述“怎麼做”,宣告式計算模型描述“做什麼”。 宣告式計算模型核心功能是由語義模型提供的,但是DSL可以使其更加容易操作。

2.3 DSL的問題

馬大叔認為很多人們聲稱的DSL的問題其實源於不熟悉!當然如果經過評估確認不需要或者成本大於收益,那麼就不要採用。

2.3.1. 語言雜音(Language Cacophony)

  1. 語言雜言問題:程式語言很難掌握,使用多種語言會讓情況變得更加複雜。 破解: 學習DSL比通用程式語言要簡單很多,二者不能相提並論;
  2. 系統中使用多種不同的DSL會讓人難以理解。 破解:專案中總會有非常複雜的部分需要去學習。即便是沒有DSL,也依然需要學習不同的類庫。
  3. 真正的問題在於相比學習領域模型,學習DSL的難度會增加多少? 會增加很少,甚至降低學習難度。

2.3.2 構建的成本

開發與維護的成本,維護成本不容忽視。

不熟悉DSL的團隊會有額外的學習成本,但是可以攤銷到未來的專案中去。

DSL的成本是基於領域模型之上的成本,構建領域模型的成本不應該計算在內。而DSL可以幫助更好地思考模型,降低成本。

儘管對於DSL的鼓吹會導致很多低質量的DSL出現,但是這種指責同樣適用於任何程式碼,關鍵在於DSL的構建是否讓事情變得更糟。

2.3.3 自制語言問題

自制語言問題指的是一個單位的很多系統都是由自制語言構建的,導致招聘困難,以及難以跟上技術的發展。

值得注意的是如果整個系統都是用一種語言開發的,那麼這種語言就不再是DSL而是通用程式語言了。

使用DSL的時候要注意防止DSL向著通用語言的方向演進。基本原則是不用的任務可以使用不同的DSL。 另外要注意不要重複造輪子。

2.3.4 固守狹隘

當一種抽象不再適用於新情況的時候,需要對抽象本身做出改變,而不是削足適履地去讓新情況適用現有的模式。出現這種情況的原因在於已經習慣於原有的模式,捨不得去將其進行重造。任何抽象模式都存在這種情況,或許DSL帶來的舒適感更強,從而情況更嚴重。關鍵是記住一點:DSL是在不斷演進的。

2.4 語言處理技術

使用DSL離不開語言處理技術。一般的專案開發團隊在用到語言處理技術的時候,90%的情況下是處理DSL。但是語言處理技術並非DSL專屬,完全可以將其用在其他地方(所以呢,這算是學習DSL技術帶來的額外收益吧)

2.5 DSL生命週期

不同的設計和使用方式:

  1. 模型與DSL一同開發,逐步演進
  2. 先有模型,然後在此基礎上新增DSL
  3. 先有DSL,然後構建程式庫,再使它們適配

推薦使用前兩者。

對於已有模型的情況,新增DSL也有兩種方案,一種是language-seeded,即把模型看做黑箱,統觀模型裡面的方法,做出偽DSL,然後在此基礎上逐步實現。這種方法幾乎不對模型進行改動。另外一種是model-seeded,先給模型新增流暢介面,然後演進為DSL。後一種更適用於內部DSL,相當於一次重構。

2.6 好的DSL設計是什麼樣的?

沒有一定之規,基本原則是讓使用者感到清晰易懂。 一些小建議:

  1. 迭代開發
  2. 使用領域專家的行話
  3. 語法上可以與專案中使用的常規程式語言保持連貫不違和
  4. 不要試圖將其構建成自然語言的樣子

相關文章