关于DSL的一些思考

51 篇文章 5 订阅
12 篇文章 0 订阅

前言

随着近几年新语言(例如,rust、kotlin、dart等10后的语言)的出现,经常有同事问起“最近有个xx语言挺火,你怎么看”这类问题。这种情况下,一般都不太想回答(根本不知道从哪说起…)。平时身边的同事中是某个语言的用户或者说是依赖/利用某个语言来完成工作的居多,把某个语言用得如手脚般灵活的极少,把自己擅长的语言控制得如手术刀那样精细的更少,每次自己写的程序出了问题5分钟都查不出原因,我会把这样的情况归结为–程序失控了。没有办法,工作中很多时候都是赶鸭子上架,两天学一个语言,一天学框架,然后直接做项目也不是什么罕见的事了。每次看到这个情况,我都会感觉,那个开发者被语言绑架了,他模仿书上和网上的代码来编码,而不是结合应用场景、自己的思维方式和喜好来设计代码。而本文接下来要讲的DSL可以帮助开发者成为编程语言的主人(一定程度上)。

DSL是什么

DSL是领域特定语言(domain-specific language)的缩写,先看两个定义:

  1. 领域特定语言指的是专注于某个应用程序领域的计算机语言。(来自百度百科)
  1. 所谓DSL,是指为特定领域所专门设计的词汇和语法,简化程序设计过程,提高生产效率的技术,同时也让非编程领域专家直接描述逻辑成为可能。(来自松本行弘《代码的未来》)

或许看定义不是非常形象生动,那么,来看下面几个例子:

  1. HTML

一个标题为title切内容为content的网页:

<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>title</title>
    </head>
    <body>
        <div>content</div>
    </body>
</html>
  1. SQL

从表table1中查找处id为1的项:

SELECT * FROM table1 WHERE id=1;
  1. Common Lisp的Loop宏

i从10下降到1,把i打印并收集起来:

(loop for i from 10 downto 1 do (print i) collect i)

看到这里,大家应该明白为什么叫领域特定语言了:Html特定于web页面展示领域,SQL特定于数据库查询领域,Loop宏则特定于循环迭代的场景。(有点像“子语言”或“内嵌语言”的感觉)

DSL的分类

DSL根据实现方式的不同,可以分为内部DSL和外部DSL。

外部DSL

定义:使用专用的语言引擎来实现的DSL,例如:SQL、HTML、Awk。

优点:
1. 独立于程序开发所使用的语言;
2. 根据目的设计语法,不被现有语言的语法所影响;

缺点:
1. 学习成本较高(毕竟要学习一个全新的语言)

内部DSL

定义:在现有的语言中,利用语言的特性实现的DSL,例如:多种Lisp方言,开发者自己使用Scala、Ruby实现的DSL。

优点:
1. 学习成本较低,无需学习一本新语言;
2. 可以使用宿主语言的全部特性;
3. 无需实现全部的词法、语法解析,实现成本低;

缺点:
1. 可以实现的语法和语义受宿主语言影响;
2. DSL依赖于程序开发所使用的语言;

为什么使用DSL

“计算机的语言会影响你怎样思考问题,影响你怎样看待信息传播。”

这是《程序员修炼之道》中的看法,其中提到一点就是靠近问题域编程,我比较认同,毕竟语言最主要的作用是描述信息。在开发功能时,要把这个功能用编程语言简洁、直观地表述出来,直接使用这个功能领域的语法和名词是最合适的。例如,下面这个网络请求的示例:

  • 功能描述:发送一个get请求,搜索指定关键字的指定页的内容

  • 接口描述:

    请求URL:

    • http://www.test.com/api/search

    请求方式:

    • GET

    参数:

    参数名必选类型说明
    keywordstring关键字
    pageint页码

假设关键字为 test,页码为1,那么大致信息总结如下:(其中一种设计,其他形式可自己思考)

http /www.test.com/api/search
    keyword : test
    page : 1
get
res => print res

使用Lisp实现的内部DSL效果如下:

(http /www.test.com/api/search
    args = ((keyword test)
            (page 1))
    get
    (res => (print res)))

Java虽然没有什么支持DSL的特性,不过可以使用流式接口(Fluent Interface)来实现类似的效果,例如:

NetTool.http("/www.test.com/api/search")
        .arg("keyword", "test")
        .arg("page","1")
        .get() //这里返回RxJava的Observable
        .map(SearchResult::fromJson)
        .subscribe((SearchResult result) -> {
            ///TODO 处理结果
        })

以上的实现方式,和普通的代码比较起来,简洁很多吧,逻辑清晰,没有缺少信息,也没有多出什么不必要的字符。

DSL的缺点

最后还是得提一下DSL的缺点,毕竟一个东西没有缺点就太假了,在使用技术的过程中扬长避短才是明智之举

上面说到了内部DSL和外部DSL之间对比的优缺点,下面是和没有采用DSL的情况对比的缺点:

  1. 学习成本高。不管是内部DSL还是外部DSL,学习成本都比无DSL的情况要高。而且,很多时候,内部DSL的开发者并不会准备详尽的文档和教程,导致学习难度大;
  2. 如果用外部DSL,则面临多语言混合编程的共有问题,语法一致性差,试想一下:一张图片,上半张是西瓜,下半张是苹果的样子。例如,一个html文件里同时又有js代码(虽然可以通过训练来克服,但是我就是不喜欢);
  3. 如果使用内部DSL,那么可能面临设计的DSL和宿主语言之间不同编程思想的冲突,例如,平常使用Java进行面向对象编程,而用流式接口实现近似DSL时,会使用一些函数式编程的思想,但是并不是所有人都熟悉函数式编程(虽说OOP和FP并不对立,不过要在实际项目中合理的融合这两种风格也不是简单的事 >_<);

结语

DSL是把双刃剑,这是不争的事实,或者说,所有的技术、语言、框架在不正确使用时都是有害的。在适当的时机、适当的场合设计适当的DSL来解决适当的问题,很可能写出“明显没有bug的代码”,避免“没有明显bug的代码”。(至于这个“适当”就只能由开发者自行判断了。。。)

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值