面向领域专家的语言,而不仅仅是程序员的语言

特定领域专家使用的编程语言,而不仅仅是为程序员设计的。这些语言可能旨在帮助领域专家(如生物学家、物理学家、金融分析师等)更有效地进行数据分析、建模和自动化任务,而无需深入了解编程的复杂性。

当 DSL 与问题领域紧密结合时,可以实现更好的结果——将抽象提升到代码之外。此类语言可以促进协作,使领域专家能够验证和确认他们的设计,并且在许多情况下,允许生成代码、配置、测试、报告等。

例子
让我用医学领域的一个例子来说明这一点。想象一台设计用于通过混合过程制造定制药物的机器。这台机器有一个沿着传送带移动的针头,三个用于在混合过程中在内部振动杯中混合的成分的输入杯,以及两种可在使用成分时使用的过滤器。有时,针头也需要清洁。

在这种情况下,作为领域专家的医生、护士或实验室技术人员可以这样描述某种特定药物混合过程的一部分:“用过滤器 A 从第二个杯子中取出 5 个单位,将 2 个单位放入 6 号杯子中,将 3 个单位放入 7 号杯子中,然后清洁针头。”

move(-3); filt(1); suck(5); 
move(4); filt(0); blow(2); 
move(1); blow(3); 
move(-3); suck(30);
move(1); blow(30);

语言是否解决了问题领域?
这种文本 DSL 只有四种命令类型,与传统编程语言相比,它简单明了,明显提高了抽象程度。 但是,它仍有局限性。 例如,它要求用户记住指针的位置并手动计算要移动的单位。

更糟糕的是,这种 DSL 允许创建毫无意义甚至危险的混合过程,例如:
suck(10); move(-3); suck(5);    /* => 在针头上涂抹清洁液 */ 
suck(10); move(6); blow(10);     /* => 成药含有清洁液 */

类似的问题还有很多:
比如用清洁液毁坏过滤器、针头撞上安全快门、在针头中仍含有药物时清洗针头,甚至指定一个永远不会产生任何有效药物的混合过程。

这种语言在很大程度上忽略了问题领域。 虽然我们可以期望测试能发现上述问题,但其中有些问题需要特定领域的知识。例如,我们能否在向内部杯子添加配料时应用过滤器,还是只应用于输出杯子? 还是取决于原料的类型?

针对问题领域的语言
针对问题领域的优秀特定领域语言包含领域规则,通过使许多典型错误无法指定来减少测试需求。

更重要的是,与问题领域紧密结合的 DSL 能让领域专家验证、确认甚至直接指定所需的功能。

那么,针对问题领域的 DSL 应该是什么样的呢?

为了实现协作并确保领域专家能够参与其中,该语言必须使用与医生和实验室技术人员相同的术语,如 "取 "和 "清洁"--就像前面引用的原始问题描述一样。 它指定了前面讨论过的同一混合过程。 最有可能的是,任何熟悉机器的领域专家都能读取该模型,并检查混合过程的准确性。

此外,这种语言还能执行前面提到的许多领域规则,例如,如果之前的操作是将药物注入针头,那么就不能对针头进行清洁。 根据这个模型,我们仍然可以生成代码,包括前面展示的文本 DSL。

虽然这种语言改善了情况,但它仍然有局限性。 用户仍然需要手动计算针头的位置,确保接下来使用正确的杯子,并验证该过程是否真的会产生任何结果--药品。

由于还有进一步改进的空间,我们可以通过提出类似下面的语言来进一步提高抽象程度。

这个 DSL 描述的混合过程与前面的示例相同,但现在不需要计算位置了,而且明确显示了杯子,使领域专家对模型一目了然。 同样重要的是,通过提高抽象程度并在语言中嵌入领域规则,模型变得更容易创建和修改。 例如,以前的 DSL 需要为小型混合过程指定 16 个项目,而这个版本只需要 8 个。 第一个文本 DSL 有 12 条命令和 12 个参数。

虽然这个例子很小,但这种语言可以扩展到处理更大的流程。 它还可以根据领域的其他特征进行扩展,例如等待时间、强制安全程序(例如,关闭安全百叶窗以防止有人在机器运行时进入机器内部)等等。 下图展示了一个稍大的混合流程,根据这个模型,还可以自动生成代码和其他工件。

通过提高抽象层次,使其更接近问题领域,创建所需功能的工作就会变得更快。 协作也变得更容易,因为领域专家可以阅读、验证和确认模型。 在许多情况下,根据具体情况和 DSL,他们甚至可以自己创建模型。

创建特定领域语言的努力?
在所有三种情况下,使用 DSL 创建的规范都可以转化为运行中的程序代码。 创建这三种小型 DSL 和工具所需的时间都不到 1 小时,但它们所能提供的结果却截然不同。如果只有你一个人使用 DSL,这并不重要,但如果有多个人需要遵循领域规则,或者你希望领域专家能更好地参与开发,那么就应该为问题领域而不是解决方案领域创建语言。

医学混合的例子只展示了一种小型语言,但这个想法可以扩展到比它大 100 倍的语言,自然也可以扩展到其他领域。 根据我在各种案例中积累的经验,在适当的工具支持下实现真实世界的 DSL 所需的时间大约为 1-3 周。

下面是四个来自不同领域的例子,它们使非程序员也能读取、验证、确认甚至创建用于生成代码的形式化规范:

  • 1) 供暖系统(左上)
  • 2) 装配线自动化(右上),
  • 3) 养鱼场自动化系统(左下)
  • 和 4) 网络应用程序测试。
这些语言以图表和地图的形式展示,因为它们所解决的问题很容易用这些表示法来表达,但语言也可以以矩阵、表格、文本或它们的混合形式展示模型--这一切都取决于问题领域。

工具支持
特定领域语言需要工具--无论是定义语言还是使用语言。 在为领域专家创建 DSL 时,期望他们使用程序员通常使用的集成开发环境或工具是不现实的。

相反,他们需要易学易用的工具。 这一点尤为重要,因为与使用编程语言的开发人员不同,领域专家通常并不一定每天都要与他们的 DSL 进行交互。

领域专家还希望 DSL 中的协作、审查和变更管理更简单,更符合问题领域,而不是通过文件中的字符级差异来跟踪和合并变更。

从这个意义上说,无论是 DSL 及其工具,还是使用流程,都需要根据领域专家的具体需求量身定制。

结论
特定领域语言已被广泛使用,但它们往往只关注解决方案领域,而不是问题领域。 由于创建 DSL 和构建其工具所需的努力是相同的,我们应该优先考虑为领域专家而不仅仅是程序员创建语言。

当语言与问题领域紧密结合时,许多典型的开发任务(如需求规格、检查和验证)都可以由领域专家来处理。

事实上,领域专家往往可以自己创建规范,然后用于生成代码、配置、测试、部署说明等。 这种方法可以提高质量,实现早期反馈,并显著改善协作和生产率--就像提高抽象层次一直以来所做的那样。