BT

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

Obie Fernandez谈敏捷Ruby DSL

| 作者 Werner Schuster 关注 6 他的粉丝 ,译者 胡凯 关注 0 他的粉丝 发布于 2007年4月17日. 估计阅读时间: 7 分钟 | Google、Facebook、Pinterest、阿里、腾讯 等顶尖技术团队的上百个可供参考的架构实例!

Software-Engineering Radio(一个向资深软件开发者提供音频节目的站点)最近就DSL(特定领域语言)和Ruby语言如何使得编写内部DSL变得更加方便等问题采访了InfoQ的专家Obie Fernandez,摘要如下:

DSL是一种专注于某一特定领域的语言,使用通用语言(如C或者Java)当然可以得到与DSL相同的功能。但是这样会产生大量繁琐的代码并导致大量的领域知识被隐藏在通用语言构造中(如for循环,if条件,方法调用,import声明等等)。

生成以及维护通用语言编写的代码本身也是问题所在:专家们必须将他们的知识变为代码。通常这些专家(销售人员,经理或者园丁)缺少编程知识,这意味着他们必须与程序员进行协作。当然,这也意味对代码的任何一次修改都包括许多繁琐的步骤:如果一个领域专家需要改变某些功能,她必须首先与程序员进行交流,由程序员实现这些修改,然后领域专家检查这些代码是否表现出期望的行为,诸如此类。

对于上述问题,其中一个可能的解决方案是开发一种适应特定环境的语言,非技术人员可以用它来解决问题。这种语言较通用语言更简洁并且仅仅提供目标领域所需要的类型以及特定的语言构造。

在访谈中,Obie将DSL类比为自然语言中的俚语或者行话。全世界喜爱咖啡的人对下面这句话一定非常熟悉:

Venti half-caf, non-fat, no foam, no whip latte

在正常的对话中,上述语言不能传递正确的语义。并且世界上大多数的咖啡馆里可能仅仅提供最普通的牛奶加咖啡。但是在合适的场所使用上述语言(例如,星巴克),它将让你使用最少的词语喝到想要的饮料并且很少会造成误解。

有多种方式可以实现DSL。其中一个选择是定义一种语法并使用解析器生成工具(如ANTLR或者YACC)来生成解析器。通过它可以将DSL代码转化为某种可以被解释的数据结构(语法树)。其中一个例子是Make文件,它被用来定义构建过程(编译,打包,部署)。另一种方式是使用XML而不是解析器,它将对于解析器生成工具的依赖变为对于XML解析器的依赖,有许多工具可以用来支撑XML处理(使用DOM解析器可以得到类似语法树的数据结构,同时使用XPATH来提取数据,等等)。Ant,是一个基于XML的DSL, 构建过程被定义为XML格式。Make和Ant就是外部DSL最典型的实现。

另一个解决方案是内部DSL,它不需要使用任何解析器生成工具以及XML解析器,取而代之的是使用现有的合法的通用语言的构造,很明显,这种语言的构造必须非常灵活才可以容纳各种简练的DSL代码。

内部DSL的终极解决工具非LISP以及类LISP语言莫属,原因是LISP灵活的语法,它可以被概括为:

  • 原子
    几乎任何字符都可以成为原子,如foo:::bar:::甚至加号都是合法的原子
  • 零个或多个原子组成的序列
    放入圆括号中

LISP宏特性使得开发者可以很轻易的在LISP中定义DSL并且将其解释或者展开为可以执行的通常的LISP代码。

另外一种适于使用内部DSL的语言是Ruby, 没什么可惊奇的,想想Ruby丰富的语法吧,它是Ruby得以支持内部DSL的部分特性。下面是Obie在PPT上所使用到的观点以及相应的示例代码:

  • 方法调用中括号可以省略。这看起来没什么大不了的, 但是它使得下面的声明成为合法的Ruby代码:
    order = latte venti, half_caf, non_fat, no_foam, no_whip
    在上面的例子中,latte(拿铁咖啡)以及我们所需要的特别的口味儿都是方法调用的一部分。latte方法返回一个将咖啡各个属性进行了初始化的对象。
  • 类的定义在载入时可以被动态执行
    class RuleSet < ActiveRecord::Base   has_many :commends, :dependend => :delete_all   # ... more... end
    这是一个内部DSL在Rails的ActiveRecord中的例子。has_many调用会在类第一次被载入时执行。这个调用被用来设定类的关联以及行为,例如,它可以通过define_method调用向类添加一些方法。这样这段DSL代码的用户可以使用非常简洁,描述性的方式来定义类某些方面的行为。事实上,这与LISP宏有很多相似的地方,他们都在代码被载入时进行工作。
  • 简洁的代码块书写格式(Block)

    块是自包含的Ruby代码。它可以被保存,当作参数传递并在以后执行,这个概念也被称做是匿名方法,lamda演算或者闭包(Closures),在Ruby中向方法传递块非常简洁紧凑,它使得实现特定语言构造变得非常容易,Rake是与Make和Ant非常相似的构建工具,我们来看一个例子:
    task :default => [:test] task :test do   ruby "test/unittest.rb" end
    上面的类使用了我们在这里提到的所有的三种概念。task是一种方法调用,但是没有圆括号让它变的更具描述性。task调用在载入时被执行,对内部数据结构进行设置。test任务的逻辑在块中进行定义(在do和end之间的代码),并在适当的时候被执行。

在Ruby中,内部DSL使得编写简洁和描述性的规范变得非常容易。正如我们从has_many例子中看到的,内部DSL也可以很容易的与通常命令式的Ruby代码进行混合。

译者简介:胡凯是InfoQ中文站的志愿者翻译。2006年加入ThoughtWorks,通过在ThoughtWorks多个国家和多个项目的敏捷实践,坚定地站在了敏捷阵营中,目前在进行CruiseControl相关的敏捷开源项目。他和许多敏捷开发者一样活跃在敏捷中国CruiseControl-China社区中。

评价本文

专业度
风格

您好,朋友!

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

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的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通知我

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

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

讨论

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


找回密码....

Follow

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

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

Like

内容自由定制

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

Notifications

获取更新

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

BT