如何判断真伪 Ruby DSL?

  • Werner Schuster
  • Jason lai

2007 年 6 月 21 日

话题:Ruby编程语言语言 & 开发架构

在 Ruby 社区中,“领域特定语言(Domain Specific Language,DSL)”已经成为一个非常流行的时髦词汇。Make 的替代工具Rake就是其中一个人气很旺的例子,尤其在 Martin Fowler 撰文介绍它之后,它更是声名鹊起。DSL 这个词同样也用于描述 Ruby on Rails 中 ActiveRecord 组件的某些概念。

实际上,DSL 这个概念和 Ruby 没有什么关系,早在 Ruby 之前它就已经问世了很长的一段时间了。通常,我们用它来描述拥有特定的语言构造(Construct)、适用于表述某个特定领域的概念的一门语言。举例而言,Make 这个构建工具就包含了自己的一门语言,允许以声明的方式定义构建目标(Build Targets)和这些目标的构建方式,以及目标之间的依赖关系。此外的一个概念就是内部 DSL,内部 DSL 使用的是一门已有语言的语法,这个已有语言称为宿主语言(Host Language),比如 Ruby。我们使用宿主语言的各种方式来构建语言构造,使其看起来像一门截然不同的语言。上面提到的 Rake 就使用了这种方式,使得下面的代码成为可能:

task :codeGen do

#do the code generation

end

正如你所能看到的,宿主语言应当非常灵活,才能允许 DSL 看起来截然不同。

然而,近来 DSL 这个字眼似乎有被滥用的趋势,导致它所代表的意思越来越含糊不清。似乎在一段代码中把方法调用的括号省掉以后,我们就可以大大方方地给这段代码贴上 DSL 的标签。是这样吗?O'Reilly 的博客作者 chromatic 贴出一个半开玩笑的列表,作为判断一段代码是否为 DSL 的依据——或者说,至少在我们给这段代码贴上 DSL 标签的时候,我们可以根据这些标准看看。其中的几条是像这样的:

  1. 除了 Ruby 以外,你还用过哪门语言写过代码没(PHP 和 HTML 不算数)?如果没有的话,那这是一门 DSL。
  2. 你所聪明地去掉括号的定义型语法特性是否一系列函数参数?如果是这样的话,那这是一门 DSL。
  3. 代码是否主要就包含一系列的键 - 值对?欢迎来到 DSL 的国度,你就是其中的一员!

chromatic 列表中的第 5 项特别说到:

  1. 你是否曾经认真使用过“……这代码读起来就像英语!”这样的话?你最好去一趟医院,因为你已经患 DSL 流感了!

关于创建 DSL,存在着一个非常流行的支持论调:DSL 使我们得以编写出易于阅读及理解的代码,并且不会将代码的意思隐藏在如 for 循环、条件语句之类的语言构造之后。然而,David A. Black 对此提出了一个不同的观点

下面这个:
with Employee "123-45-6789" do

dock_salary 1000

warn_about :misconduct

end
并不是一门领域特定语言,它是领域特定语言。注意在这里我少用了“一门”这个词。它是领域特定的没错,而且也使用了类似语言的构造,但它并不是一门语言。它只是 Ruby 的代码,使用直表其意的方法名称和易懂的语义,从而有助于领域特定用语(Idioms)的使用。

使用一个针对领域的词汇表来命名代码元素并不是件新鲜事,相反,对于设计人来说它应当是一个显而易见的选择。

那么,DSL 这个字眼是有害或者有误导作用的么?未必。Smalltalk 开发人员 Blaine Buxton 对此发表观点说

我一直就不理解:到底 DSL 出了什么大问题?当然,我并不是反过来说 DSL 不好。但我相信 DSL 是一个良好的面向对象设计的绿色副产品。所以,我曾经觉得所有在这方面的讨论和技巧挺烦人的。我心里一直这么跟自己说:“如果你的设计非常好,那么它 [DSL] 就自然而然产生了。”

但是我该好好敲敲自己的脑壳了。良好的设计又重新成为流行趋势,对此我应当感到高兴才对。你知道吗?我确实感到高兴。现在,我不再对那些讨论感到厌烦了。相反,我开始慢慢品味这些讨论的结果了。我甚至为之前保佑负罪感。你看,DSL 一直都是 Smalltalk 的一部分。对于我们 [Smalltalk 程序员] 来说,它再自然不过了,因为这就是我们学会写 [Smalltalk] 代码的方式。我记得我已经把“代码读起来应当像交谈(Code should read like a conversation)”这句至理名言吞进肚里,变成我思维的一部分了。而现在社区里面新一代的人们已经发现这个事实,并且在使用它,这真是太酷了。

含有可被扭转 / 改变的语法,从而可以写出更加简练的代码的语言是很有用的,但良好的设计仍然取决于开发人员本身。

这一点存在另外一面。有一些 DSL 使用 Ruby 的构造及 Ruby 语法中比较罕见的一些情况。尽管这可以让开发人员实现出某种特定形式的代码,但是并不意味着这是一种良好的设计。毕竟,虽然代码只要写一次,但需要经过多次阅读和修改,这个事实毫无疑问到现在都还没有人能否认。

最近在 Urban Honking 上的一篇文章引发了对这个话题的一次辩论。文章揭示了为什么 HTML 解析库Hpricot使得下面一段代码成为可能:

Hpricot(my_document)

从根本上说,这里使用了一个叫做Hpricot函数创建了Hpricot的一个新对象,并对my_document进行解析。然而,Smalltalk 领域的博客作者 James Robertson 表示反对

Urban Honking 费了九牛二虎之力,解释了为什么使用 Ruby 的一些语法技巧得出类似Hpricot(my_document)这样的东西会是一件好事的原因。在这里我提个问题:如果你偶然在代码中碰到这样的技巧性写法,你会明白这代码是干嘛的吗?

在解释如何提高代码质量之后,他接着说:

我已经用 Smalltalk 用得聪明过头好多次了;这绝不是一个好主意。

Stuart Halloway 也站出来声援这个观点

如果你打算改变一门语言的惯用法,那么你得先有一个很有说服力的理由。我同意 James 的观点,这个例子不够令人信服。

接着,他又针对 API 设计的多种不同解决方案给出了一个颇有意思的纵览,并且阐述了如何设计出一套可以用来编写出表义清晰代码的 API。

亲爱的读者,您又是持什么样的看法呢?对于使代码更容易阅读的良好设计和使用能让我们少敲几下键盘、但令代码难于维护的编程技巧之间的界限,你又是如何划分的呢?如果你需要审视自己是否已从良好设计的航线上迷失,并且误入 Hacking 以及灵活运用语法技巧的百慕大,存在什么有效的方法吗?

查看英文原文:What's a Ruby DSL and what isn't?

Ruby编程语言语言 & 开发架构