语法噪音
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做了一个。)