BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

DSL的演进

| 作者 Peter Bell 关注 0 他的粉丝 ,译者 王丽娟 关注 0 他的粉丝 发布于 2010年5月19日. 估计阅读时间: 19 分钟 | 都知道硅谷人工智能做的好,你知道 硅谷的运维技术 也值得参考吗?QCon上海带你探索其中的奥义

简介

领域特定语言(DSL) 是针对特定问题领域的编程语言,而非通用语言。要创建“不重复自己”(Don't Repeat Yourself)、“业务用户可读”的代码,DSL可是个好方法。在过去的几年里,有关DSL的文章比比皆是。

创建一种领域特定语言并非难事。但我们对领域的理解总是不断深入,要让DSL长期有用,我们就需要一种不断完善DSL的策略。如果你正在开发一个大型项 目,或是一条软件产品线(SPL), 在很长一段时间内都需要使用DSL的话,那你最好考虑清楚该如何处理DSL的演进。

从借助版本化实现的向后兼容性,到语句的自动转换,本文将着眼于不断简化DSL演进过程的DSL构建方法。

避免问题

在第三代编程语言(3GL)的世界里,语言设计者非常清楚向后兼容性的重要性。无论Java的下一个版本会有哪些变化,都不太可能破坏先前版本中添加的任 何功能。不过使用DSL时,随着我们对问题空间的进一步探索,我们对领域的理解会发生彻底改变。在单独项目中,业务专家往往会在后期提出新的领域概念,这 就迫使你要追根溯源,重新考虑怎样才能最好地为领域建模。软件产品线里的每个新项目都会带来不同的需求,这些需求则会对DSL的优化设计产生影响。

过去,我们始终建议大家在进行领域特定建模(DSM)时,只有在业务规则频繁变化、而领域结构却相对稳定时再引入DSL(两个条件分别是为了提高开发 DSL及其相关工具的投资回报率,减少改进DSL结构时遇到的问题),从而减少这些问题。不过DSL现在应用得越来越广泛,理解与DSL演进相关的问题、 处理这些问题的一些策略就非常重要了。

问题是什么?

有些类型的DSL演进根本不是问题。如果你想增加一个新的领域概念,或是给概念新增一个可选特性【译注】,你只用扩展DSL语法就可以了,而这并不会破坏任何已有的代码。但在有些情况下,你必须考虑语法的这些变化会对已有的语句产生什么影响。这些情况有:

  • 删除一个概念或特性
  • 添加一个特性,不同语句需要的特性值可能会有所不同
  • 将特性连同其子特性转变为一个独立的概念
  • 增加一项新的约束,而已有的语句可能并不满足该约束

抽象语法(Abstract Grammar)vs. 具体句法(Concrete Syntax)

有一个重要的DSL概念能让接下来的讨论稍微简单一些,那就是抽象语法和具体句法之间的差异。DSL的抽象语法描述了有效语句的结构,也包括所有相关的约 束。具体句法则描述了如何在DSL中正确编写语句的细节问题。

举例来说,假设有一个描述状态机的DSL,它可能包括一个这样的概念:一个对象能有多个状态。抽象语法只能传达“一个对象能有多个状态”,(还有所有的约束,比如每个对象至少要有一个状态、给定对象的每个状态都应该有唯一的名称)。具体句法则可能是建模工具里的图、XML文档、GroovyRuby这些DSL本身的代码、电子表格、基于DSL的数据库 Schema,或者是自定义的文本句法。尽管在特定的具体句法中对DSL的某些内容作出改进会更具有挑战性(比如处理与图形化语言相关的位置和图形数据),但我们还是要着眼于抽象语法,来讨论大部分问题和DSL演进带来的影响,要明白表述语句的具体句法只是个次要问题。

进行DSL演进的方法

在已经有DSL语句的系统中,进行DSL演进的常见方法有三个:

  • 依靠向后兼容
  • 语言进行版本化
  • 自动进行语句转换

向后兼容性

解决问题最简单的方法就是回避问题。对DSL语法的修改坚决不能破坏已有语句。人们使用DSL一段时间后,往往会演进DSL语法,却不关心修改是否会破坏已有的语句。最终,用例会在这些演进的地方突然出现问题。

语言版本化

对于可能会破坏已有语句的DSL演进来说,最快捷的解决办法是对DSL进行版本化。无论你使用的是某种内部DSL的内置分析,还是“外部DSL+显式的解 析器”(或许是ANTLRXtext,也可能是使用XML具体语法时用到的XML解析器), 当你需要做重大更改时,你只用发布解析器的新版本、确保你的系统支持多版本,在语句需要利用后续版本中可用的扩展语法时,只要更新语句就可以了。这个方法 在一定程度上可以说是相当不错的,但对语言多版本进行维护、支持、调试的开销最终会变得难以承受。

语句转换自动化

理想的解决办法是能对DSL语句进行自动演进,只要你修改了语法,(如果可能)语句就可以自动更新。最简单的处理方法之一就是将转换应用到语法,然后使用 脚本语言或是XSLT、ATLAS这种允许模型到模型(M2M)转换的语言编写脚本,将同样的改变运用于DSL语句。

转换示例

假设我们使用XML这一具体句法来描述应用中领域对象的DSL。最初,我们可能有两个领域类——User和Product,如清单1所示。

清单1:User和Product领域对象的XML句法。

<domainObject name="User" />
<domainObject name="Product" />

很快,我们决定添加Property的概念,而且每个domainObject可以有0到n个属性。这个转换并不会破坏现有的语句。它只涉及“添加概念” 和“添加可选关系”,也就是说,我们增加了一个新的概念——属性,以及一个新的可选关系(每个领域有n个属性,但属性并不是必需的)。清 单2是带有Property概念的语句:

清单2:带有属性的领域对象。

<domainObject name="User">
    <properties>
        <property name="FirstName" />
        <property name="LastName" />
    </properties>
</domainObject>

<domainObject name="Product">
    <properties>
        <property name="Title" />
        <property name="Price" />
    </properties>
</domainObject>

现在再添加一条——属性可以有一条验证规则。这样我 们就有了“添加可选特性”的转换,这里我们要为属性添加可选的“validationRule”特性。同样,由于特性是可选的,先前的语句仍然有效,所以 对语法应用了这一转换之后,我们并没有破坏当前的DSL语句,也就不需要对语句做什么处理了。

比方说我们就这样工作了一段时间,XML最终就像清单3所显示的那样。

清单3:带有验证规则的领域对象属性。

<domainObject name="User">
    <properties>
        <property name="FirstName" />
        <property name="LastName" validationRule="Required" />
    </properties>
</domainObject>

<domainObject name="Product">
    <properties>
        <property name="Title" validationRule="maxlength=50"/>
        <property name="Price" validationRule="isNumeric" />
    </properties>
</domainObject>

不过现在我们意识到,有些情况下,我们要为一个属性 关联多个验证规则。这一问题有很多解决办法。让我们看看其中之一。首先,我们可以只改变validationRule,使其成为以逗号分隔的验证规则列 表。这一变更不需要对现有语句进行任何修改(假设当前所有的验证规则中都没有逗号)。但语言中会出现容易让人误解的特性,因为 validationRule支持以逗号分隔的规则列表并不是很容易理解。

接下来可能会采用的转换是“为特性重命名”。将validationRule重命名为validationRuleList,你会有一个从语义上来说更有意义的特性名称。要做到这一点,你要有方法将这类转换应用于已有的语句。最佳方法因使 用的具体句法而不同,但XML具体句法(或是任何能与XML工程互相转换的内容)要做到这点还是相对容易的,这只是举了个例子。

我们继续扩展应用,不幸的是我们发现验证规则需要更复杂的参数。例如我们有一个规则,用户在网站上注册时密码和确认密码必须匹配。这个验证规则可以写成清单4所示的XML片段。

清单4:带有密码和确认密码属性的验证规则。

<validationRule name="PasswordMatchesConfirmation" type="propertyValuesMatch" 
    firstPropertyName="Password" secondPropertyName="PasswordConfirmation" />

我们现在的问题是要将“特性应用到关联的概念转换上 去”。让我们分析一下。首先,这个特性要“转换概念”,因为我们将validationRule作为属性使用,而现在要替换为单独的 validationRule概念,这一概念在XML具体语法中用单独的XML元素表示。这个特性还要“转换为关联的概念”,因为我决定在 语言里用独立的片段来描述这些规则,而这些规能被不同的属性重用。举例来说,如果FirstName和LastName都是必需的属性,那它们就可以用同 一个“Required”验证规则。更适合这些情况的替代方法是使用“转换为组合概念的特性”——每个属性可以包含规则。

XML片段使用“组合概念”的特性会是清单 5所示的样子。

清单5:带有“组合概念特性”的领域对象。

<domainObject name="User">
    <properties>
        <property name="FirstName" />
        <property name="LastName">
            <validationRule name="Required" />
        </property>
    </properties>
</domainObject>

<domainObject name="Product">
    <properties>
        <property name="Title">
            <validationRule name="maxlength" value="50" />
        </property>
        <property name="Price" validationRule="isNumeric">
            <validationRule name="isNumeric" />
        </property>
    </properties>
</domainObject>

清单6显示了使用转换为关联概念的特性后,XML的样子。

清单6:带有“关联概念特性”的领域对象。

<domainObject name="User">
    <properties>
        <property name="FirstName" />
        <property name="LastName" validationRuleNameList="Required" />
    </properties>
    <validationRules>
        <validationRule name="Required" />
    </validationRules
</domainObject>

<domainObject name="Product">
    <properties>
        <property name="Title" validationRuleNameList="TitleMaxlength" />
        <property name="Price" validationRuleNameList="isNumeric" />
        <validationRules>
            <validationRule name="TitleMaxlength" value="50" />
            <validationRule name="isNumeric" />
        </validationRules>
    </properties>
</domainObject>

同样,这些转换都可以自动应用于已有的DSL语句。

自动化的局限性

当然,有一些转换是不能自动进行的。当你想应用“删除概念”或“删除特性”的转换时,你使用的工具很有可能会自动扫描已有的语句,但无论你是必须基于转换脚本提供的报告进行手工修改,还是不得不使用“deprecate”来代替“删除”转换,每当工具发现这些条目,不让你添加新条目、却又不会强迫你移除那些条目时,这些工具可能都会让你觉得相当痛苦。

同样的,如果你想用“添加必要特性”的转换为所有属性添加一个数据类型特性的话,除非你能给出缺省值(没特殊说明就是字符串)或一些智能的脚本规则,否则自动化工具最好能提供一个高效的UI,能为历史语句填充所有的条目。

内部DSL vs. 外部DSL

认识到内部DSL的局限性是很重要的。在“最终用户可读性”方面,内部DSL提供了很多好处,不过对于那些使用某种语言内置DSL编写的语句来说,自动应用转换通常会比较棘手。内部DSL很好,但你要是在大型项目或软件产品线中广泛使用这些内部DSL的话,就要确保你要有一个将转换应用到这些DSL上的策略。否则在项目的生命周期里,为外部DSL创建工具所花的那点儿时间与使用内部DSL相比来说可能更划得来。

结论

本文最重要结论的是,只要你的DSL是成功的,那你最终会有许多使用这些DSL的语句。要真是这样,如果你确实需要演进你的DSL,你最好是有一个处理这些语句转换的策略。

此外,认识到这个问题还没有解决也很重要。这一领域还需要很多工作要做,除了来自MetaCase的MetaEdit+之外,大部分领域特定建模工具都不能很出色地处理元模型演进。

引用

关于作者

Peter Bell是SystemsForge的 CEO兼CTO。他开发了生成自定义Web应用的软件产品线,该产品线融合了特征建模、产品线工程和领域特定建模。他的文章和演讲遍布全球,内容涉及领域 特定建模、代码生成、精益/敏捷开发,以及Groovy和CFML等JVM上运行的动态脚本语言。他的Blog为:http://appgen.pbell.com/

查看英文原文:DSL Evolution


译注:根据文中示例,attribute在本文译为“特性”,property译为“属性”。

感谢曹云飞对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家加入到InfoQ中文站用户讨论组中与我们的编辑和其他读者朋友交流。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

一直有个问题不明白。。。 by 朱 敏

我一直有个问题不明白,为什么大家要脱离计算机程序语言(代码、库)去弄一套专门的“设计语言”来描述业务领域的要求?计算机程序语言(代码、库)本身就是描述业务要求,并可以通过计算机执行的啊?有朋友了解其中缘由的,请赐教!

Re: 一直有个问题不明白。。。 by 杨 光

通用语言的代码和库是需要通用语言的语法调用的,例如我所在的船舶领域,船舶设计软件会提供给船舶工程师二次开发的接口,一个船舶工程师需要掌握通用语言再去使用这些代码和库,相比使用DSL来说还是麻烦的。比如说添加一个肘板,船舶工程师希望的语法是类似这样的:
添加 肘板(参数)
而通用语言OO的一般是这样:
实例化肘板类
调用肘板对象的添加方法(参数)
我觉得差别就在于提供一个肘板类,还是一个肘板语法。肘板类需要在通用语言的环境中执行,而肘板语法在领域的软件中直接就可以执行。许许多多这些小差异在一起就构成了DSL的优势。

Re: 一直有个问题不明白。。。 by shi rc

简单来说就是,专注才会高效。

Re: 一直有个问题不明白。。。 by Shooter Evan

赶快写篇答疑解惑吧。。。

Re: 一直有个问题不明白。。。 by 朱 敏

通用语言的代码和库是需要通用语言的语法调用的,例如我所在的船舶领域,船舶设计软件会提供给船舶工程师二次开发的接口,一个船舶工程师需要掌握通用语言再去使用这些代码和库,相比使用DSL来说还是麻烦的。比如说添加一个肘板,船舶工程师希望的语法是类似这样的:
添加 肘板(参数)
而通用语言OO的一般是这样:
实例化肘板类
调用肘板对象的添加方法(参数)
我觉得差别就在于提供一个肘板类,还是一个肘板语法。肘板类需要在通用语言的环境中执行,而肘板语法在领域的软件中直接就可以执行。许许多多这些小差异在一起就构成了DSL的优势。


谢谢您的回复!这块内容希望和您简单探讨一下。

我又回头简单浏览了一下infoq的《领域驱动设计(精编版)》,还是觉得很奇怪。如果建筑设计师拿着建筑效果渲染图给业主单位看,大家都不会觉得有问题;为什么软件就不能直接拿着效果图(界面设计、原型)来和客户沟通,而需要另外再弄出来一套“领域建模语言”?而且《领域驱动设计(精编版)》里面也说了“软件是给人用的”,那么,软件的成果必然会有一部分是人能够理解的,把这一部分清楚的表示出来不就可以作为领域专家和架构师沟通的语言了吗?真是奇怪,还是我没有理解到位?

《领域驱动设计(精编版)》里面的民航领域的示例,也很难说服我。因为里面提取出领域对象的手法太随意了,“Aircraft”、“Flight Plan”从何而来,为什么是这些对象而不是与领域专家的访谈中谈到的其它名词呢?这方面我还是觉得UML的用例分析更加实用一些,对象提取也更规范,不同的人提取出的对象不会差到哪里去。

Re: 一直有个问题不明白。。。 by 朱 敏

谢谢您的回复!
让领域专家学习“领域建模语言”,我总觉得难度颇大。因为他们用业务语言描述就可以了,至于从业务领域到软件架构(设计)的对应,这本来就是软件架构师(设计师)做的事情,呵呵。

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

6 讨论

深度内容

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT