华章程序员书库:领域特定语言.pdf

华章程序员书库:领域特定语言.pdf
 

书籍描述

编辑推荐
《华章程序员书库:领域特定语言》全面详尽地讲解各种DSL及其构造方式,揭示与编程语言无关的通用原则和模式,阐释如何通过DSL有效提高开发人员的生产力以及增进与领域专家的有效沟通。

作者简介
Martin Fowler:世界级软件开发大师,软件开发“教父”,敏捷开发方法的创始人之一,在面向对象分析与设计、UML、模式、极限编程、重构和DSL等领域都有非常深入的研究并为软件开发行业做出了卓越贡献。他乐于分享,撰写了《企业应用架构模式》(荣获第13届Jolt生产力大奖)、《重构:改善既有代码的设计》、《分析模式:可复用的对象模型》、《UML精粹:标准对象建模语言简明指南》等在软件开发领域颇负盛名的著作。

目录
译者序
前言
第一部分 叙述
第1章入门例子2
1.1 哥特式建筑安全系统2
1.2 状态机模型4
1.3 为格兰特小姐的控制器编写程序7
1.4 语言和语义模型13
1.5使用代码生成15
1.6 使用语言工作台17
1.7 可视化20
第2章 使用DSL21
2.1定义DSL21
2.1.1DSL的边界22
2.1.2片段DSL和独立DSL25
2.2为何需要DSL25
2.2.1 提高开发效率26
2.2.2与领域专家的沟通26
2.2.3执行环境的改变27
2.2.4其他计算模型28
2.3DSL的问题28
2.3.1语言噪音29
2.3.2构建成本29
2.3.3集中营语言30
2.3.4 “一叶障目”的抽象30
2.4广义的语言处理31
2.5DSL的生命周期31
2.6设计优良的DSL从何而来32
第3章实现DSL34
3.1DSL处理之架构34
3.2解析器的工作方式37
3.3文法、语法和语义39
3.4解析中的数据39
3.5宏41
3.6测试DSL42
3.6.1语义模型的测试42
3.6.2解析器的测试45
3.6.3脚本的测试49
3.7错误处理50
3.8DSL迁移51
第4章实现内部DSL54
4.1连贯API与命令–查询API54
4.2解析层的需求57
4.3使用函数58
4.4字面量集合61
4.5基于文法选择内部元素63
4.6闭包64
4.7解析树操作66
4.8标注67
4.9为字面量提供扩展69
4.10消除语法噪音69
4.11动态接收69
4.12提供类型检查70
第5章实现外部DSL72
5.1语法分析策略72
5.2输出生成策略74
5.3解析中的概念76
5.3.1单独的词法分析76
5.3.2文法和语言77
5.3.3正则文法、上下文无关文法和上下文相关文法77
5.3.4自顶向下解析和自底向上解析79
5.4混入另一种语言81
5.5XML DSL82
第6章内部DSL vs 外部DSL84
6.1学习曲线84
6.2创建成本85
6.3程序员的熟悉度85
6.4与领域专家沟通86
6.5与宿主语言混合86
6.6强边界87
6.7运行时配置87
6.8趋于平庸88
6.9组合多种DSL88
6.10总结89
第7章其他计算模型概述90
7.1几种计算模型92
7.1.1决策表92
7.1.2产生式规则系统93
7.1.3状态机94
7.1.4依赖网络95
7.1.5选择模型95
第8章代码生成96
8.1选择生成什么96
8.2如何生成99
8.3混合生成代码和手写代码100
8.4生成可读的代码101
8.5解析之前的代码生成101
8.6延伸阅读101
第9章语言工作台102
9.1语言工作台之要素102
9.2模式定义语言和元模型103
9.3源码编辑和投射编辑107
9.4说明性编程109
9.5工具之旅110
9.6语言工作台和CASE工具112
9.7我们该使用语言工作台吗112
第二部分 通 用 主 题
第10章各种DSL116
10.1Graphviz116
10.2JMock117
10.3CSS118
10.4HQL119
10.5XAML120
10.6FIT122
10.7Make等123
第11章语义模型125
11.1工作原理125
11.2使用场景127
11.3入门例子(Java)128
第12章符号表129
12.1工作原理129
12.2使用场景131
12.3参考文献131
12.4以外部DSL实现的依赖网络(Java和ANTLR)131
12.5在一个内部DSL中使用符号键(Ruby)133
12.6用枚举作为静态类型符号(Java)134
第13章语境变量137
13.1工作原理137
13.2使用场景137
13.3读取INI文件(C#)138
第14章构造型生成器141
14.1工作原理141
14.2使用场景142
14.3构建简单的航班信息(C#)142
第15章宏144
15.1工作原理144
15.1.1文本宏145
15.1.2语法宏148
15.2使用场景151
第16章通知153
16.1工作原理153
16.2使用场景154
16.3一个非常简单的通知(C#)154
16.4解析中的通知(Java)155
第三部分 外部DSL主题
第17章分隔符指导翻译160
17.1工作原理160
17.2使用场景162
17.3常客记分(C#)163
17.3.1 语义模型163
17.3.2解析器165
17.4使用格兰特小姐的控制器解析非自治语句(Java)168
第18章语法指导翻译175
18.1工作原理175
18.1.1词法分析器176
18.1.2语法分析器179
18.1.3产生输出181
18.1.4语义预测181
18.2使用场景182
18.3参考文献182
第19章BNF183
19.1工作原理183
19.1.1多重性符号(Kleene运算符)184
19.1.2其他一些有用的运算符186
19.1.3解析表达式文法186
19.1.4将EBNF转换为基础BNF187
19.1.5行为代码189
19.2使用场景191
第20章基于正则表达式表的词法分析器192
20.1工作原理192
20.2使用场景193
20.3格兰特小姐控制器的词法处理(Java)194
第21章递归下降法语法解析器197
21.1工作原理197
21.2使用场景200
21.3参考文献200
21.4递归下降和格兰特小姐的控制器(Java)201
第22章解析器组合子205
22.1工作原理206
22.1.1处理动作208
22.1.2函数式风格的组合子209
22.2使用场景209
22.3解析器组合子和格兰特小姐的控制器(Java)210
第23章解析器生成器217
23.1工作原理217
23.2使用场景219
23.3Hello World(Java和ANTLR)219
23.3.1编写基本的文法220
23.3.2构建语法分析器221
23.3.3为文法添加代码动作223
23.3.4使用代沟225
第24章树的构建227
24.1工作原理227
24.2使用场景229
24.3使用ANTLR的树构建语法(Java和ANTLR)230
24.3.1标记解释230
24.3.2解析231
24.3.3组装语义模型233
24.4使用代码动作进行树的构建(Java和ANTLR)236
第25章嵌入式语法翻译242
25.1工作原理242
25.2使用场景243
25.3格兰特小姐的控制器(Java和ANTLR)243
第26章内嵌解释器247
26.1工作原理247
26.2使用场景247
26.3计算器(ANTLR和Java)247
第27章外加代码250
27.1工作原理250
27.2使用场景251
27.3嵌入动态代码(ANTLR、Java和JavaScript)252
27.3.1语义模型252
27.3.2语法分析器254
第28章可变分词方式258
28.1工作原理258
28.1.1字符引用259
28.1.2词法状态261
28.1.3修改标记类型262
28.1.4忽略标记类型263
28.2使用场景264
第29章嵌套的运算符表达式265
29.1工作原理265
29.1.1使用自底向上的语法分析器265
29.1.2自顶向下的语法分析器266
29.2使用场景268
第30章以换行符作为分隔符269
30.1工作原理269
30.2使用场景271
第31章外部DSL拾遗272
31.1语法缩进272
31.2模块化文法274
第四部分 内部DSL主题
第32章表达式生成器276
32.1工作原理276
32.2使用场景277
32.3具有和没有生成器的连贯日历(Java)278
32.4对于日历使用多个生成器(Java)280
第33章函数序列282
33.1工作原理282
33.2使用场景283
33.3简单的计算机配置(Java)283
第34章嵌套函数286
34.1工作原理286
34.2使用场景287
34.3简单计算机配置范例(Java)288
34.4用标记处理多个不同的参数(C#)289
34.5针对IDE支持使用子类型标记(Java)291
34.6使用对象初始化器(C#)292
34.7周期性事件(C#)293
34.7.1语义模型294
34.7.2DSL296
第35章方法级联299
35.1工作原理299
35.1.1生成器还是值300
35.1.2收尾问题301
35.1.3分层结构301
35.1.4渐进式接口302
35.2使用场景303
35.3简单的计算机配置范例(Java)303
35.4带有属性的方法级联(C#)306
35.5渐进式接口(C#)307
第36章对象范围309
36.1工作原理309
36.2使用场景310
36.3安全代码(C#)310
36.3.1 语义模型311
36.3.2DSL313
36.4使用实例求值(Ruby)315
36.5使用实例初始化器(Java)317
第37章闭包319
37.1工作原理319
37.2使用场景323
第38章嵌套闭包324
38.1工作原理324
38.2使用场景325
38.3用嵌套闭包来包装函数序列(Ruby)326
38.4简单的C#示例(C#)327
38.5使用方法级联(Ruby)328
38.6带显式闭包参数的函数序列(Ruby)330
38.7采用实例级求值(Ruby)332
第39章列表的字面构造335
39.1工作原理335
39.2使用场景335
第40章Literal Map336
40.1工作原理336
40.2使用场景336
40.3使用List和Map表达计算机的配置信息(Ruby)337
40.4演化为Greenspun式(Ruby)338
第41章动态接收342
41.1工作原理342
41.2使用场景343
41.3积分——使用方法名解析(Ruby)344
41.3.1模型345
41.3.2生成器347
41.4积分——使用方法级联(Ruby)348
41.4.1模型349
41.4.2生成器349
41.5去掉安全仪表盘控制器中的引用(JRuby)351
第42章标注357
42.1工作原理357
42.1.1定义标注358
42.1.2处理标注359
42.2使用场景360
42.3用于运行时处理的特定语法(Java)360
42.4使用类方法(Ruby)362
42.5动态代码生成(Ruby)363
第43章解析树操作365
43.1工作原理365
43.2使用场景366
43.3由C#条件生成IMAP查询(C#)367
43.3.1语义模型367
43.3.2以C#构建369
43.3.3退后一步373
第44章类符号表375
44.1 工作原理375
44.2使用场景376
44.3在静态类型中实现类符号表(Java)377
第45章文本润色383
45.1工作原理383
45.2使用场景383
45.3使用润色的折扣规则(Ruby)384
第46章为字面量提供扩展386
46.1工作原理386
46.2使用场景387
46.3食谱配料(C#)387
第五部分 其他计算模型
第47章适应性模型390
47.1工作原理390
47.1.1在适应性模型中使用命令式代码391
47.1.2工具393
47.2使用场景394
第48章决策表395
48.1工作原理395
48.2使用场景396
48.3为一个订单计算费用(C#)396
48.3.1模型397
48.3.2解析器400
第49章依赖网络403
49.1工作原理403
49.2使用场景405
49.3分析饮料(C#)405
49.3.1语义模型406
49.3.2解析器407
第50章产生式规则系统409
50.1工作原理409
50.1.1链式操作410
50.1.2矛盾推导411
50.1.3规则结构里的模式412
50.2使用场景412
50.3俱乐部会员校验(C#)412
50.3.1模型413
50.3.2解析器414
50.3.3演进DSL414
50.4适任资格的规则:扩展俱乐部成员(C#)416
50.4.1模型417
50.4.2解析器419
第51章状态机421
51.1工作原理421
51.2使用场景423
51.3安全面板控制器(Java)423
第六部分 代 码 生 成
第52章基于转换器的代码生成426
52.1工作原理426
52.2使用场景427
52.3安全面板控制器(Java生成的C)427
第53章模板化的生成器431
53.1工作原理431
53.2使用场景432
53.3生成带有嵌套条件的安全控制面板状态机(Velocity和Java生成的C)432
第54章嵌入助手438
54.1工作原理438
54.2使用场景439
54.3安全控制面板的状态(Java和ANTLR)439
54.4助手类应该生成HTML吗(Java和Velocity)442
第55章基于模型的代码生成444
55.1工作原理444
55.2使用场景445
55.3安全控制面板的状态机(C)445
55.4动态载入状态机(C)451
第56章无视模型的代码生成454
56.1工作原理454
56.2使用场景455
56.3使用嵌套条件的安全面板状态机(C)455
第57章代沟457
57.1工作原理457
57.2使用场景458
57.3根据数据结构生成类(Java和一些Ruby)459
参考文献463

序言
在我开始编程之前,DSL(Domain–Specific Language,领域特定语言)就已经成了程序世界中的一员。随便找个UNIX或者Lisp老手问问,他一定会跟你滔滔不绝地谈起DSL是怎么成为他的镇宅之宝的,直到你被烦得痛不欲生为止。但即便这样,DSL却从未成为计算领域的一大亮点。大多数人都是从别人那里学到DSL,而且只学到了有限的几种技术。
我写这本书就是为了改变这个现状。我希望通过本书介绍的大量DSL技术,让你有足够的信息来做出决策:是否在工作中使用DSL,以及选择哪一种DSL技术。
造成DSL流行的原因有很多,我只着重强调两点:首先, 提升开发人员的生产力;其次,增进与领域专家之间的沟通。如果DSL选择得当,就可以使一段复杂的代码变得清晰易懂,在使用这段代码时提高程序员的工作效率。同时,如果DSL选择得当,就可以使一段普通的文字既可以当做可执行的软件,又可以充当功能描述,让领域专家能理解他们的想法是如何在系统中得到体现的,开发者和领域专家的沟通也会更加顺畅。增进沟通比起工作效率提升困难了一些,但带来的效果却更为显著。因为它可以帮助我们打通软件开发中最狭窄的瓶颈─程序员和客户之间的沟通。
我不会片面夸大DSL的价值。我常常说,无论你什么时候谈到DSL的优缺点,你都可以考虑把“DSL”换成“库”。实 际上,大多数DSL都只是在一个框架或者库上又加了薄薄的一层外壳。于是,DSL的成本和收益往往会比人们预想的要小,但也未曾得到过充分的认识。掌握良好的技术可以大大降低构造DSL的成本,我希望这本书可以帮你做到这一点。这层外壳虽薄,却也实用,值得一试。
为什么现在写这本书DSL已问世很长时间,但近些年来,它们掀起了一股流行风潮。与此同时,我决定用几年的时间写这本书。为什么呢?虽然我并不知道自己能否给这股风潮下一个权威的定义,但是可以分享一下自己的观点。
在千禧年到来的时候,编程语言界─至少在我的企业软件世界里─隐约出现了一种颇具统治性的标准。先是 Java,它在几年的时间里风光无限。即使后来微软推出的C#挑战了Java的统治地位,但这个新生者依然是一门跟Java很相似的语言。新时代的软件开发被编译型的、静态的、面向对象的、语法格式跟C类似的语言统治着(甚至连VB都被迫变得尽可能具有这些性质)。
然而人们很快发现,并不是所有的事情都能在Java/C#的霸权下运作良好。有些重要的逻辑用这些语言无法很好实现,这导致了XML配置文件的兴起。不久之后,有程序员开玩笑说,他们写的XML代码比Java/C#代码都多。这固然有一部分原因是想在运行时改变系统行为,但也体现出人们的另外一个想法:用更容易定制的方式表达系统行为的各个方面。虽然XML噪音很多,但确实可以让你定义自己的词汇,而且提供了非常强大的层次结构。
不过后来人们实在无法忍受XML那么多的噪音了。他们抱怨尖括号刺伤了他们的双眼。他们希望一方面能够享受XML 配置文件带来的好处,另一方面又不用承受XML的痛苦。
我们的故事到现在讲了一半,这个时候Ruby on Rails横空出世,耀眼的光芒让一切都褪尽了颜色。无论Rails这个实用平台在历史上会占据什么样的位置(我觉得这确实是个优秀的平台),它都已经给人们对于框架和库的认识带来了深远的影响。Ruby社区有一个很重要的做事方式:让一切显得更加连贯。换句话说,在调用库的时候,就像用一门专门的语言进行编程一样。这不禁让我们回想起一门古老的编程语言:Lisp。通过它我们也看到了Java/C#这片坚硬的土地上绽开的花朵:连贯接口(fluent interface)在这两门语言中都变得流行起来,这大概要归功于JMock和 Hamcrest的创始人的不断努力。
回头看看这一切,我发现这里面有知识壁垒。有的时候,使用定制的语法会更容易理解,实现也不难,人们却用了XML;有的时候,使用定制的语法会简单很多,人们却把Ruby用得乱七八糟;有的时候,本来在他们常用的语言中使用连贯接口就可以轻易实现的事情,人们非要使用解析器。
我觉得这些事情都是因为存在知识壁垒才发生的。经验丰富的程序员对DSL的相关技术所知寥寥,没法对使用哪一项技术做出明智的判断。我对打破这个壁垒很感兴趣。
为什么DSL很重要
本书2.2节会讲述更多细节,不过我觉得需要学习DSL(以及本书中提到的其他技术)的原因主要有两个。
第一个原因是提升程序员的生产力。先看下面这段代码:
input =~ /\d{3}-\d{3}-\d{4}/
你会认出这是个正则表达式匹配,也许你还知道它匹配的是什么。正则表达式常常由于过于费解而遭受指责,但试想一下,如果你所能够使用的都是普通的正则控制代码,这段模式匹配会变成什么样子。而这段代码跟正则表达式相比,又是何等容易理解,容易修改?
DSL的第一个优势是它擅长在程序中的某些特定地方发挥作用,并且让它们容易理解,进而提高编写、维护的速度,也会减少bug。
DSL的第二个优势就不仅仅限于程序员的范畴了。因为DSL往往短小易读,所以非程序员也能看懂这些驱动他们重要业务的代码。把这些真实的代码暴露在理解该领域的人们面前,可以确保程序员和客户之间有非常顺畅的沟通渠道。
当人们谈论这类事情的时候,他们常说DSL可以让你不再需要程序员。我对这一论调极度不认同,毕竟那是说COBOL的。不过也确实有些语言是由那些自称不是程序员的人来用的,比如CSS。对这种语言来说,读比写要重要得多。如果一个领域专家可以阅读并且理解核心业务代码中的绝大部分,那他就可以跟写这段代码的程序员进行深入细节的交流。
第二个原因是使用DSL并非易事,不过回报也是相当丰厚的。软件开发中最狭窄的瓶颈就是程序员和客户之间的沟通,任何可以解决这一问题的技术都值得学习。
别畏惧这本大厚书
看到这本书这么厚,你可能会吓一跳;我自己发现要写这么多内容的时候都忍不住倒吸一口冷气。我对大厚书的态度总是小心翼翼,因为我们用来阅读的时间是有限的,一本厚书就意味着时间上的大量投资。因此在这种情况下我更倾向于使用“姊妹篇”的方式。
姊妹篇实际上是关于一个主题的两本书。第一本是叙述性质的书,需要仔细阅读。我希望它可以大致地描述出这个主题的主要内容,让读者有个整体认识就好,不用深入细节。我觉得叙述部分最好不要超过150页,这是个比较合理的厚度。
第二本书是参考资料,不需要一页一页翻阅(虽然有些人也这样看)。用的时候再仔细看就行。有些人喜欢先读完第一本,有了整体认识之后,再去看第二本书里面感兴趣的章节。有些人喜欢一边读第一本,一边找第二本中感兴趣的地方读。我之所以采用这种划分方式,主要还是想让你们了解哪些地方可以跳过,哪些地方不能忽略,这样你也就可以有选择地深入阅读了。
我已经尽力让参考资料那部分独立成篇了,如果你想让某人使用“树的构建”(第24章),就让他阅读那个模式,即使他可能对DSL没有清楚的认识,但是也能知道怎么做。这样一来,一旦你完全理解了概述部分,这本书就变成了参考资料,想查详细资料的话,一翻开就能找到。
本书之所以这么厚,是因为我没能找到把它变薄的方法。它的一个主要目的是分析、比较DSL的各项技术。讨论代码生成、Ruby元编程、 “解析器生成器”(第23章)工具的书有很多,本书涵盖所有这些技术,让你可以了解它们的异同。它们都在广阔的舞台上扮演着自己的角色,在帮助你了解这些技术之外,我还想介绍一下这个舞台。
本书主要内容
本书旨在全面介绍各种DSL及其构造方式。当人们尝试DSL的时候,经常就只选一种技术。你可以在本书里看到对多种技术的介绍,真正用的时候就可以做出最合适的选择。本书还提供了很多DSL技术的实现细节和例子。当然,我无法把所有的细节都写下来,但也足以使读者入门,在早期决策时起到辅助作用。
前3章讲述什么是DSL、DSL的用途以及DSL与框架和库的区别。第5章和第6章可以帮你理解如何构建外部DSL和内部DSL。第三部分讲述解析器所扮演的角色, “解析器生成器”(第23章)的作用,用解析器解析外部DSL的各种方式。第四部分展示了在一种DSL风格中所能使用的多种语言结构。虽然它不能告诉你怎样充分利用你钟爱的语言,却能帮助你理解一门语言中的技术在不同语言之间的对应关系。第五部分介绍其他计算模型,有助于读者学习如何构建模型。
第六部分列出了生成代码的各种策略,你需要的话可以看一下。第9章简单介绍了新一代的工具。本书所介绍的绝大部分技术都已经面世很长时间了;语言工作台更像是未来科技,虽然应当有美好的前景,但没有经验证明。
本书读者对象
本书的理想读者是那些正在思考构建DSL的职业软件程序员。我觉得这种类型的读者都应该有多年的工作经验,认同软件设计的基本思想。
如果你深入研究过语言设计的话题,那这本书里大概不会有什么你没有接触过的资料。我倒是希望我在书中整理并表述信息的方式对你有所帮助。虽然人们在语言设计方面做了大量的工作─尤其是在学术领域,可这些成果能够为专业编程领域服务的却寥寥无几。
叙述部分的前几章还可以澄清一些困惑,比如什么是DSL,DSL有什么用途。通读第一部分以后,你就可以更全面地掌握DSL的不同实现技术。
这是本Java书或者C#书吗
本书和我曾写过的大部分书一样,与编程语言没有多大关系。我最想做的事情是揭示一些与编程语言无关的通用原则和模式。因此,不管你用的是哪种流行的面向对象语言,本书里的思想都会为你提供帮助。
函数式语言可能会是一条代沟。虽然我觉得很多内容依然对函数式语言适用,但我在函数式编程中的经验并不足以让我做出判断,它们的编程范式到底会从多大程度上影响到书中的建议。本书对于过程式语言(即非面向对象的语言,例如C)的作用也很有限,因为我讲的很多技术都依赖于面向对象技术。
虽然我写的是通用原则,但为了能够把它们恰当地讲述出来,我还需要一些例子─于是需要一门具体的编程语言。在选择用哪门语言来写例子的时候,我的首要标准是有多少人能读懂它。于是绝大多数例子都是用Java或C#写的。这两门语言在业界广泛使用,有很多相似之处:类C的语法、内存管理,为人们提供各种便利的类库。但我的
意思可不是说它们就是写DSL的最佳选择了(这里需要特别强调一下,因为我根本就不认为它们是最佳选择),只是说它们最能够帮助读者理解我讲的通用概念。我尽力让二者出现的机会均等,只有当使用某种语言更方便的时候,我的天平才会稍稍倾斜一下。虽然内部DSL的良好运用常常要用到某些另类的语法特色,但我也尽力避免使用一些需要太多语法知识才能理解的语言元素,着实挺困难的。
还有一些思想是必须使用动态语言才能满足的,没法用Java或C#实现。在那些情况下我就换用Ruby,因为这是我最熟悉的动态语言。它为我提供了很多帮助,因为它的特性完美地契合了编写DSL的需求。另外再强调一点,虽然我个人更熟悉某种语言,在选择时也考虑了个人偏好,但这并不能推断出这些技术换个地方就不能用了。我很喜欢Ruby,但如果你想看看我对语言的偏执,那只有贬低Smalltalk才行。
值得一提的是,另外有许多语言都适合构建DSL,尤其还有一些是专门为了写内部DSL而设计出来的。我之所以没有提到它们,是因为我对它们所知不多,没有足够的信心进行评价。你不要认为我对它们有什么负面观点。
另外还要提一句,在写这本书的时候,本来试图和语言无关,可大多数技术的实用性偏偏都要直接依赖于某种语言的特性。这是让我最苦恼的地方了。我为了实现大范围内的通用性做出了很多权衡,但你必须意识到,这些权衡可能会因具体的语言环境而彻底改变。
本书缺少什么
在写这样一本书的过程中,要说什么时候最让人垂头丧气、濒临崩溃,莫过于自己意识到必须停笔的那一刻了。我为这本书花费了几年的时间,我相信这里面有很多值得你阅读的内容。但我也知道我留了很多疏漏之处。我本来想弥补这些疏漏,可这得占用大量时间。我的信念是,宁可出版一本未完成的书,也不要再等上几年把书写完─即便是真的能够写完的话。下面简单介绍一下我实在没时间补充的内容。
前面曾提到过一点─函数式语言。实际上,在当代那些基于ML和/或Haskell的函数式语言中,构建DSL已经是广为人知的事实了。我在书中基本没提这部分内容。我也很想知道,当我熟悉了函数式语言及其DSL应用之后,本书的内容安排会发生多大的改变。
有一件事情最让我心神不安,那就是没有把近期有关诊断和错误处理的讨论加进去。我记得上大学的时候曾学到过,诊断这件事情在写编译器的过程中是何等艰难,因此忽略这一点让我觉得自己像是在作表面文章。
我个人最喜欢第7章。我本来可以写很多内容的,只可惜时不我予。最后我只好决定少写一些─希望它依然可以激发你主动探索更多模型的兴趣。
章节引用
虽然本书的结构比较普通,但引用章节的结构还是需要稍稍介绍一下的。我把引用章节分成一系列主题,按照相似性组成不同的章节。我的想法是每个主题都可以独立成篇,于是你读完第一部分以后,就可以任选一个主题深入了解,无须再涉及其他章节。如果有例外情况的话,我会在对应主题的开篇提到。
大部分主题都以模式的形式呈现。模式是对于一再重复出现的问题的通用解决方案。所以如果你有一个常见的问题:“我该怎么处理我的解析器结构呢?”对这个问题的两种可行模式是“分隔符指导翻译”(第17章)和“语
法指导翻译”(第18章)。
在过去的二十年间,人们写了很多关于软件开发模式的书,不同的人有不同的视角。我的看法是,模式给我提供了一种组织参考资料的良好方式。第一部分告诉你如果想要解析文本,可以考虑上面两种模式。模式本身提供了更多的信息以供选择和具体实施。
引用章节大都是以模式的结构来写的,但也有些例外:并不是所有的主题在我眼中都是解决方案。比如“嵌套的运算符表达式”(第29章),它的重点就不是解决方案,也不符合模式的结构,因此我没采用模式风格的描述方式。还有一些情况很难称为模式,比如“宏”(第15章)和“BNF”(第19章),可是用模式结构来描述它们却很合适。总的来说,只要是模式结构─尤其是把“如何起作用”和“何时使用模式”分离开这种形式─能够帮我描述概念,我就一直在使用它。
模式结构
大多数作者在写模式的时候都用了一些标准模板。我也不例外,既用了一个标准模板,又与别人用的不一样。我所用的模板,或称模式形态,是我第一次用在企业应用架构模式(P of EAA,[Fowler PoEAA])中的模式。它的形式如下。
模板中最重要的元素大概要数名字。我喜欢用模式来描述各个引用主题,最大的原因就是它可以帮我创建一个强大的词汇表,方便展开讨论。虽然这个词汇表不一定能得到广泛应用,但至少可以让我的写作保持一致性,也可以在别人想要用这个模式的时候,给他提供一个起始点。
接下来的两个元素是意图和概要。它们对模式进行简要的概括。它们还能起到提醒作用,如果你已“将模式纳入囊中”,但忘了名字,它们可以轻轻拨动你的记忆。意图用一句话总结模式,而概要是模式的一种可视化表示─有时候是一张草图,有时候是代码示例,不管是什么形式,只要能够快速解释模式的本质就好。我有时候使用图表,有时候会用UML画图,不过要是有其他方式可以更容易表达意图,我也是很乐意用的。
接下来就是稍长一些的摘要了。我一般会在这一部分给出一个例子,用来说明模式的用途。摘要由几段话组成,同样是为了让读者在深入细节之前先了解模式全貌。
模式有两个主体部分: 工作原理和使用场景。这两部分没有固定顺序,如果你想了解是否该用某个模式,可能就只想读“使用场景”这一节。不过,一般来说,不了解工作原理的话,只看“使用场景”是没什么作用的。
最后一部分是例子。我尽力在“工作原理”这一节把模式的工作原理讲清楚,但人们一般还是需要通过代码来理解。然而,代码示例是有危险的,它们演示的只是模式的一种应用场景,可有些人却会以为模式就是这个用法,没有理解背后的概念。你可以把一种模式用上千百遍,每次稍稍有些差异,可我没有地方容纳那么多代码,所以请记住,模式的含义远远不止从特定示例中看到的那么多。
所有的例子都设计得非常简单,只关注要讨论的模式本身。我用的例子都是相互独立的,因为我的目标是每一个引用章节都独立成篇。一般来说,在实际应用模式的时候还需要处理其他大量问题,但在一个简单的例子中,起码可以让你有机会理解问题的核心。丰富的例子更贴近现实,可它们也会引入大量与当前模式无关的问题。于是我只会展示一些片段,你需要自己把它们组装起来,满足特定的需求。
这同时也意味着我在代码中主要追求的是可读性。我没有考虑性能、错误处理等因素,它们只会把你的注意力从模式的核心转移到别处。
也基于这个原因,我力图避免写一些我觉得很难借鉴的代码,即便是更符合该语言的惯例也不予考虑。这个折衷在内部DSL上会显得有些笨拙,因为内部DSL经常要靠语言的小窍门来强化语言的连贯性。
很多模式都缺少上面讲的一两个部分,因为确实没什么可写的。有些模式没有例子,因为最合适的例子在其他模式里面用到了─在发生这种情况的时候,我会把它指出来。
致谢
当我每次写书的时候,很多人都为我提供了大量帮助。虽然作者那里写的是我的名字,但许多朋友都为提升本书的质量作出了巨大的贡献。
我首先要感谢的是我的同事Rebecca Parsons。我对DSL这个主题曾有很多顾虑,例如,它会涉及很多学术背景的知识,而那些是我所不熟悉的。Rebecca有深厚的语言理论背景,她在这方面给了我很多帮助。此外,她也是
我们公司的首席技术探路人和战略家,她可以将她的学术背景和大量的实践经验合二为一。她有能力,并且也愿意为本书付出更多心血,但ThoughtWorks在其他方面更需要她。我很高兴,我们曾关于DSL这个主题滔滔不绝。
作者总是希望(并且带着小小的恐惧)审校者可以通读全书,找出不计其数的大大小小的错误。我幸运地找到了Michael Hunger,他的审校工作做得极其出色。从本书刚刚出现在我网站上的时候,他就开始不断地给我挑错, 催促我改正─这正是我需要的态度。他同时也催促我详细介绍使用静态类型的技术,尤其是静态类型的“符号表”(第12章)。 他给我提供了无数建议,足以再写两本书了。我希望有朝一日可以把这些想法写下来。
在过去的几年里,我和同事们包括Rebecca Parsons、Neal Ford、Ola Bini,写过很多这方面的文章。在本书里,我也借鉴了他们的一些成型的想法。ThoughtWorks慷慨地给予我大量时间来写这本书。在花了很长时间决定不再为某一家公司工作之后,我很高兴找到一家让我愿意留下来,并且积极地参与其建设的公司。
本书还有很多正式的审校者,他们为本书提供了大量建议,找出了很多错误。他们是:
David Bock David Ing
Gilad Bracha Jeremy Miller
Aino Corry Ravi Mohan
Sven Efftinge Terance Parr
Eric Evans Nat Pryce
Jay Fields Chris Sells
Steve Freeman Nathaniel Schutta
Brian Goetz Craig Taverner
Steve Hayes Dave Thomas
Clifford Heath Glenn Vanderburg
Michael Hunger
我还要感谢David Ing,是他提出了第10章的章名。
成为一个系列丛书的编辑之后,我就有了些美妙的特权,比如,我拥有了一个很出色的作者团队,他们可以帮我出谋划策。其中我尤其要感谢Elliotte Rusty Harold,他提供了很多精彩的建议和评论。
我在ThoughtWorks的很多同事也成为我想法的源泉。我要谢谢过去几年里允许我在项目中闲逛的每个人。我能写下来的远不及所看到的,能在这样广袤的海洋中徜徉,我感到无比愉悦。
有些人给Safari联机丛书的初稿提供了很多建议,我在正式付梓之前也参考了他们的想法。这些人是: Pavel Bernhauser、Mocky、Roman Yakovenko、 tdyer。
我还要谢谢本书出版商Pearson的工作人员。Greg Doench是本书的组稿编辑,他负责出版的整体流程。John Fuller是本书的执行编辑,他监管生产流程。
Dmitry Kirsanov调整了我拙劣的英语,让本书语言流畅。Alina Kirsanova组织了本书的布局。

文摘
译者序
2008年,老马(Martin Fowler)在Agile China上做主旨发言,题目就是领域特定语言(Domain Specific Language,DSL)。老马提携后辈,愿意跟我合作完成这个演讲。而我呢,一方面,年少轻狂认为这个领域我也算个中好手,另一方面,也感激老马的信任和厚爱,就答应了。当时我已经知道老马在写一本关于这个主题的书,便 跟他讨要原文来看。当时还没有成型的稿子,只有非常简略的草稿和博客片段。
2010年年底,ThoughtWorks技术战略委员会(ThoughtWorks Technology Advisor Board)在芝加哥开会。那时候,这本书的英文原版已然出版。晚上聚餐的时候,我心血来潮,跟老马说如果有机会,希望能将这本书翻译成中文,介绍给中国的开发者。老马听了很高兴,把最终定稿的电子版访问权限授予了我。
几个月后,同事刀哥(李剑)问我有没有兴趣参加这本书的翻译,我说当然有。后来回想起来,我还是上了刀哥的当,因为突然之间我就从参与翻译变成负责翻译了。
由于我手里已经有原稿的缘故,因此我们没有采用出版社提供的Word版本,而是在英文原稿上直接翻译。原稿除了文字部分之外,还有几千行代码。其中包括XSTL、Ruby、C++、Java、图像处理脚本,甚至还有用于构建样书的rake脚本。惊讶之余,作为程序员的求知欲也被激发出来了。在动手翻译之前,我们花了两天彻底了解这些代 码的作用。然后就发现了这个惊人的秘密:这是一本用领域特定语言写就的关于领域特定语言的书。
原文的文字部分是老马在docbook基础上定义的领域特定语言。这种语言除了在docbook的基础结构上定义了章节模板之外,还有两个专用结构:patternRef和codeRef 。
patternRef用于处理模式名称在不同章节中的引用。本书分为两部分,前面的概念讲述部分和后面的模式部分。它耗时3年写就,在草稿阶段模式名称都未确定,各章节之间交叉引用很多。一旦出现模式名称改变,更新同步成本就很高。为此,老马定义了专有的语言结构,patternRef。所有对于模式的引用,都通过patternRef实现。由 patternRef解析处理应该使用那个具体的名称。
这个巧妙的做法在后来的翻译中给我们带来了很大的困扰。因为patternRef会处理英语中的单复数,而中文不会有这样的情况。翻译稿中出现了大量的s和es。最后还是通过修改DSL解析器里才解决了这个问题。
codeRef则表示代码引用。这本书属于技术领域,其中会有大量代码示例;同一份代码示例会在不同章节中引用,一旦写法变化,就需要同步检查它在上下文内是否还能起到示范作用。老马先在示范代码的源代码中通过注释加入XML标注,把代码分解成一段段可引用的例子。因为是代码注释,所以不会影响源代码的编译、调试和重构。 然后,再通过codeRef,表明是哪个例子的哪段示例。最后,再通过Ruby和XSLT,摘取对应的代码段,生成相应的 文本。
我一直认为在澄清概念和发现模式上老马是有超能力的。通常会忘记他也是个ThoughtWorker,而让做事情变 得有趣,则是每个ThoughtWorker都有的超能力。
翻译这本书并不轻松,其中很多概念中文并无定译。为了呈现最好的结果,我们成立了一个翻译小组,包括熊节、郑烨、李剑、张凯峰、金明等有较多翻译经验的ThoughtWorker悉数在内。虽然如此,仍然难免疏漏 ,望读者 不吝斧正。
徐昊   
ThoughtWorks中国

内容简介
《华章程序员书库:领域特定语言》是DSL领域的丰碑之作,由世界级软件开发大师和软件开发“教父”Martin Fowler历时多年写作而成,ThoughtWorks中国翻译。全面详尽地讲解了各种DSL及其构造方式,揭示了与编程语言无关的通用原则和模式,阐释了如何通过DSL有效提高开发人员的生产力以及增进与领域专家的有效沟通,能为开发人员选择和使用DSL提供有效的决策依据和指导方法。
全书共57章,分为六个部分:第一部分介绍了什么是DSL,DSL的用途,如何实现外部DS和内部DSL,如何生成代码,语言工作台的使用方法;第二部分介绍了各种DSL,分别讲述了语义模型、符号表、语境变量、构造型生成器、宏和通知的工作原理和使用场景;第三部分分别揭示分隔符指导翻译、语法指导翻译、BNF、易于正则表达式表的词法分析器、递归下降法词法分析器、解析器组合子、解析器生成器、树的构建、嵌入式语法翻译、内嵌解释器、外加代码等;第四部分介绍了表达式生成器、函数序列、嵌套函数、方法级联、对象范围、闭包、嵌套闭包、标注、解析数操作、类符号表、文本润色、字面量扩展的工作原理和使用场景;第五部分介绍了适应性模型、决策表、依赖网络、产生式规则系统、状态机等计算模型的工作原理和使用场景;第六部分介绍了基于转换器的代码生成、模板化的生成器、嵌入助手、基于模型的代码生成、无视模型的代码生成和代沟等内容。

购买书籍

当当网购书 京东购书 卓越购书

PDF电子书下载地址

相关书籍

搜索更多