語法雜訊

2008 年 6 月 9 日

在討論 特定領域語言(或任何電腦語言)時,一個常見的流行用語是雜訊語法。人們可能會說 Ruby 比 Java 的雜訊較少,或外部 DSL 比內部 DSL 的雜訊較少。所謂的語法雜訊,是指不屬於我們真正需要表達的內容,但為了滿足語言定義而存在的額外字元。雜訊字元很糟糕,因為它們會模糊程式碼的意義,迫使我們必須費力找出程式碼在做什麼。

與許多概念一樣,語法雜訊既寬鬆又主觀,因此很難討論。不久前,Gilhad Braha 在 JAOO 的一場演講中試圖說明他對語法雜訊的看法。在這裡,我將嘗試採用類似的做法,並將其套用在我目前 DSL 書籍介紹 中使用的 DSL 的幾個公式。(我使用狀態機範例的子集,以保持文字在合理的長度。)

在他的演講中,他透過為他認為是雜訊字元的字元上色來說明雜訊。當然,這樣做有一個問題,就是這需要我們定義雜訊字元的含義。我將避開這個問題,並做出不同的區分。我將區分我所謂的領域文字和標點符號。我正在檢視的 DSL 腳本定義了一個狀態機,因此會討論狀態、事件和命令。任何描述我特定狀態機資訊的內容(例如狀態名稱),我都將定義為領域文字。其他任何內容都是標點符號,我將以紅色突出顯示後者。

我將從外部 DSL 的自訂語法開始。

events
  doorClosed  D1CL
  drawOpened  D2OP
  lightOn     L1ON
end
   
commands
  unlockDoor D1UL
  lockPanel   PNLK
end
   
state idle
  actions {unlockDoor lockPanel}
  doorClosed => active
end
   
state active
  drawOpened => waitingForLight
  lightOn    => waitingForDraw
end

自訂語法傾向於將雜訊降到最低,因此您會看到這裡的標點符號相對較少。這段文字也清楚地表明我們需要一些標點符號。事件和命令都是透過提供它們的名稱和程式碼來定義的,您需要標點符號才能將它們區分開來。因此,我認為標點符號不等於雜訊,錯誤的標點符號才是雜訊,或過多的標點符號才是雜訊。特別是,我不認為嘗試將標點符號減少到絕對最低值是一個好主意,標點符號太少也會讓 DSL 更難理解。

現在讓我們來看看 Ruby 中相同領域資訊的內部 DSL。

event :doorClosed, "D1CL"  
event :drawOpened,  "D2OP"  
event :lightOn, "L1ON"  

command  :lockPanel,   "PNLK" 
command  :unlockDoor,  "D1UL" 

state :idle do 
  actions :unlockDoor, :lockPanel
  transitions :doorClosed => :active
end 

state :active do 
  transitions :drawOpened => :waitingForLight, 
              :lightOn => :waitingForDraw
end 

現在我們看到更多標點符號。當然,我可以在我的 DSL 中做出一些選擇來減少標點符號,但我認為大多數人仍會同意,Ruby DSL 比自訂 DSL 有更多標點符號。對我來說,這裡的雜訊至少是這些小東西:標記符號的「:」、分隔引數的「,」、引號字串的「"」。

我的 DSL 思考中的一個主要主題是,DSL 是一種填充架構的方式。在這種情況下,架構是描述狀態機器的架構。除了使用 DSL 填充架構之外,您也可以使用常規按鈕 API 來執行此操作。讓我們為此著色標點符號。

Event doorClosed = new Event("doorClosed", "D1CL"); 
Event drawOpened = new Event("drawOpened", "D2OP"); 
Event lightOn = new Event("lightOn", "L1ON"); 
 
Command lockPanelCmd = new Command("lockPanel", "PNLK"); 
Command unlockDoorCmd = new Command("unlockDoor", "D1UL"); 

State idle = new State("idle"); 
State activeState = new State("active"); 
 
StateMachine machine = new StateMachine(idle); 

idle.addTransition(doorClosed, activeState);
idle.addCommand(unlockDoorCmd);
idle.addCommand(lockPanelCmd);

activeState.addTransition(drawOpened, waitingForLightState);
activeState.addTransition(lightOn, waitingForDrawState);

這裡有更多標點符號。各種引號和括號,以及方法關鍵字和局部變數宣告。後者提出了有趣的分類問題。我將宣告局部變數視為標點符號(因為它會複製名稱),但它稍後用作網域文字。

Java 也可以用流暢的方式撰寫,因此以下是書中的流暢版本。

public class BasicStateMachine extends StateMachineBuilder { 
  Events doorClosed, drawOpened, lightOn; 
  Commands lockPanel, unlockDoor; 
  States idle, active; 

  protected void defineStateMachine() { 
    doorClosed. code("D1CL"); 
    drawOpened. code("D2OP"); 
    lightOn.    code("L1ON"); 

    lockPanel.  code("PNLK"); 
    unlockDoor. code("D1UL"); 
 
    idle 
        .actions(unlockDoor, lockPanel) 
        .transition(doorClosed).to(active) 
        ; 
 
    active 
        .transition(drawOpened).to(waitingForLight) 
        .transition(lightOn).   to(waitingForDraw) 
        ; 
 } 
 

每當兩或三個人聚在一起討論語法雜訊時,XML 必定會出現。

<stateMachine start = "idle"> 
    <event name="doorClosed" code="D1CL"/>  
    <event name="drawOpened" code="D2OP"/> 
    <event name="lightOn" code="L1ON"/> 

    <command name="lockPanel" code="PNLK"/> 
    <command name="unlockDoor" code="D1UL"/> 

  <state name="idle"> 
    <transition event="doorClosed" target="active"/> 
    <action command="unlockDoor"/> 
    <action command="lockPanel"/> 
  </state> 

  <state name="active"> 
    <transition event="drawOpened" target="waitingForLight"/> 
    <transition event="lightOn" target="waitingForDraw"/> 
  </state>
</stateMachine> 

我不認為我們可以從這個特定範例中讀取太多內容,但它確實提供了一些思考的材料。雖然我不認為我們可以在有用的標點符號和雜訊之間做出嚴格的區分,但網域文字和標點符號之間的區別可以幫助我們專注於標點符號,並考慮哪個標點符號最適合我們。我還可以補充一點,在 DSL 中,標點符號字元比網域文字多是一種氣味。

(Mikael Jansson 已發布此範例的 Lisp 版本。Mihailo Lalevic 在 JavaScript 中執行了一個版本。)