SlideShare ist ein Scribd-Unternehmen logo
1 von 72
Downloaden Sie, um offline zu lesen
使用DSL改善软件设计
    金明
关于我
•    金明
•    ThoughtWorks高级咨询师
•    InfoQ中文站主编
•    爱好敏捷、编程、读书、体育运动
•    联系方式
     –  金明i@weibo
     –  mingjin@twitter
聊聊软件设计那些事儿...
为什么我们的Java程序里面那么多XML文件?
为什么我们的Java程序里面那么多XML文件?

为什么我们需要面向切面编程(AOP)?
为什么我们的Java程序里面那么多XML文件?

为什么我们需要面向切面编程(AOP)?

为什么大家对Ruby on Rails仍然津津乐道?
为什么我们的Java程序里面那么多XML文件?

为什么我们需要面向切面编程(AOP)?

为什么大家对Ruby on Rails仍然津津乐道?

面向对象编程的下一个“革命”是什么?
长期以来,
我们一直使用通用目的型语言
进行软件设计
但是,
我们还缺少什么?
上下文
The context
Subject Matter Experts,
难于与领域专家、需求人员沟通
  Business analysts...
表达性
通用目的型
 编程语言
 还不够!


   C##Erlang#
      #
 C++#Java# #
      Python
Ruby# Groovy#
  Fortran#
         C#
不够?
 缺什么?
不够?
 缺什么?
少即是多
更抽象、更内聚的DSL或许是解决方案
DSL早已充斥我们周围
graphviz

                                                  LINQ
ant                  rake


                             Hibernate Query Language
       regular expressions


              DSL早已充斥我们周围
SQL                                                             make


                                JMock expectations
                                                          FIT
      struts-config.xml


                                      rails validations
            CSS
SQL
^[w-.]+@([w-]){2,4}$
Visual!
DSL
•  Domain Specific Language
•  领域专用语言

•  “两天以后”的DSL例子
  –  2.days.from.today
定义

      a"computer"programming"language"of""
       limited"expressiveness"focused"on"a""
                par4cular"domain"
"
"""""""""""""""""""""""""""""""""""""""""""""""""""""5"Mar4n"Fowler"
定义

      a"computer"programming"language"of""
       limited"expressiveness"focused"on"a""
                par4cular"domain"
"
"""""""""""""""""""""""""""""""""""""""""""""""""""""5"Mar4n"Fowler"
定义

      a"computer"programming"language"of""
       limited"expressiveness"focused"on"a""
                par4cular"domain"
"
"""""""""""""""""""""""""""""""""""""""""""""""""""""5"Mar4n"Fowler"
定义

      a"computer"programming"language"of""
       limited"expressiveness"focused"on"a""
                par4cular"domain"
"
"""""""""""""""""""""""""""""""""""""""""""""""""""""5"Mar4n"Fowler"
让我们点杯星巴克拿铁
例子:咖啡订单DSL
•  星巴克咖啡订单
“一杯超大杯(Venti)、低咖(half-caf)、
   脱脂(non-fat)、无奶沫、不加鲜奶油(no
   whip)的拿铁(latte)”
•  传统的实现
上面例子中的问题
•  依赖于技术实现API
•  代码与我们向服务员下单的描述不匹配
•  阅读性不佳,难以验证
DSL style




•  更好地展现了代码意图
•  利用了Ruby特性
 –  无类型声明
 –  无括号参数列表
 –  元编程支持
基于Java的咖啡订单DSL
API 方式
CoffeeOrder order = new Latte(VENTI,
  HALF_CAF, NONFAT_MILK);
CoffeeOrder coffee = order.prepare(false);


DSL 方式
CoffeeOrder order = new
  Latte().size(VENTI).caffeine(HALF).milk(NO
  NFAT).foam(FALSE);
CoffeeOrder coffee = order.prepare();
其他的展现形式?
•  XML
<Order type=“Latte”>
  <caffeine>half</caffeine>
  <milk>nonfat</milk>
  <foam>false</form>
  <whip>false</whip>
</Order>
•  英语
“Venti half-caf, non-fat, no foam, no whip
   latte”
“Venti latte with no whip cream, no foam,
   non-fat milk, half-caffeine”
DSL的两种类型
外部DSL                    内部DSL
•  与宿主语言无关               •  以宿主语言进行编写
•  执行需要编译器或者             •  基于约定地使用宿主
   解释器                      语言语法的子集
•  可以是图形化的               •  有时也被称为嵌入式
                            DSL或者流畅接口




               ThoughtWorks 2008
外部DSL
•  需要构造解析器以处理特定的语法
•  sql, make files, xml config files,
   regular expressions
优势
•  语法不受限制
•  运行时执行
不足
•  一开始可能简单,但可能变得丑陋和复杂
•  构造解析器比较困难
•  缺乏工具的支持
内部DSL
•  在宿主语言之上进行扩展
•  Rake, gradle, Jmock Expectations,
   Xspecs
优势
•  不必编写、调试一门新语言
•  宿主语言自身强大的能力
•  IDE等工具的支持更好
不足
•  受宿主语言的特性限制
语言工作台
•  Language Workbench
•  提供了图形界面的、元建模以及/或者代码
   生成,以定义和使用DSL
 –  Microsoft DSL tool for Visual Studio
 –  Intentional Software
 –  Eclipse EMF
 –  JetBrains Meta Programing System
DSL
•  使用更具表达性语言而非通用目的型语言
•  在开发人员与业务专家之间共享共同的理
   解
•  业务专家可以帮助设计应用程序的业务逻
   辑
•  避免技术代码噪音掩盖了业务逻辑代码
•  清晰地将业务逻辑与应用程序代码分离
•  业务规则主导开发
DSL的优势
•  增加开发生产率
 –  清晰的代码更易于演进
 –  易于达成,但影响相对较小
•  易于与领域专家交流
 –  定义系统行为的通用语言
 –  易于阅读胜于易于编写
 –  难以达成,但影响很大
创建DSL
•  由底向上(Framework Out)
 –  基于框架或者库定义DSL
•  由顶向下(Language In)
 –  与使用者一起设计DSL
 –  再驱动DSL实现与其语义模型
•  遗留系统打补丁(Patch Legacy)
 –  为遗留系统自动生成其“拙劣”的代码
内部DSL的几种形式
computer()
  .processor()
    .cores(2)
    .i386()
  .disk()
    .size(150)
  .disk()
    .size(75)
    .speed(7200)
    .sata()
  .end();
模型与构造器
       Expression Builder                    Semantic Model
       (fluent)                              (push-button)


             Computer"Builder"                       Computer"
computer()
  .processor()                   new Computer(processor, disks)
    …                            new Processor(…)
  .disk()


                 Disk"Builder"                          Disk"

  .size(75)
  .speed(7200)                   new Disk(75, 7200, Disk.Interface.SATA)
  .sata()
相同的问题,不同的DSL
computer()         computer();
  .processor()       processor();
    .cores(2)          cores(2);
    .i386()            processorType(i386);
  .disk()            disk();
    .size(150)         diskSize(150);
  .disk()            disk();
    .size(75)          diskSize(75);
    .speed(7200)       diskSpeed(7200);
    .sata()            diskInterface(SATA);
  .end();
函数序列
computer();             •  一系列函数(功能)调用
   processor();
                        •  调用全局函数可能比较不便
     cores(2);
                        •  全局的解析状态
 processorType(i386);
                        •  需要上下文变量
   disk();
     diskSize(150);
   disk();
     diskSize(75);
     diskSpeed(7200);

 diskInterface(SATA);
方法链
computer()
                    • 
  .processor()
    .cores(2)       • 
    .i386()
  .disk()                • 
    .size(150)
  .disk()
    .size(75)       • 
    .speed(7200)
    .sata()         • 
  .end();
嵌套函数
computer(
     processor(        • 
       cores(2),
                            • 
 Processor.Type.i386   •         size(75)
     ),
                       • 
     disk(
       size(150)       • 
     ),
     disk(
       size(75),
       speed(7200),

 Disk.Interface.SATA
     )
   );
DSL最佳实践
•  启发式思考最理想的结果
 –  你将会站在问题域的角度进行思考
 –  引出更好的DSL
•  朝着理想结果不断重构
•  善假于物(Rake例子)
测试,测试,再测试!
•  编写DSL是一件复杂的事情
•  使用DSL则应该简单
 –  否则你肯定犯错了
•  测试所有的细节部分
•  3种类型的测试
 –  测试模型本身
 –  测试DSL到模型的转换
 –  测试使用DSL的特定脚本
•  非常重要,因为你将来会进行修改
问题域
•    尽可能保持其精简
•    不要试图覆盖完整的问题域
•    推荐使用一组更为特定的DSL
•    利用元编程
没有银弹!
没有银弹!
•  学习曲线
没有银弹!
•  学习曲线
•  设计良好的语言很难
没有银弹!
•  学习曲线
•  设计良好的语言很难
•  构造成本
没有银弹!
•    学习曲线
•    设计良好的语言很难
•    构造成本
•    应用范围有限
没有银弹!
•    学习曲线
•    设计良好的语言很难
•    构造成本
•    应用范围有限
•    维护成本
没有银弹!
•    学习曲线
•    设计良好的语言很难
•    构造成本
•    应用范围有限
•    维护成本
•    可能被过度使用或者滥用
来看一个真实的例子
例子:Adorb
•  ADO Recordset Builder
•  将.NET对象结果集转化为VB ADO
   RecordSet
•  数据传输于页面展示


•    http://github.com/mingjin/Adorb
RecordsetClass rs = new RecordsetClass();

rs.Fields.Append("Id", DataTypeEnum.adInteger, 10, FieldAttributeEnum.adFldIsNullable,
Missing.Value);
rs.Fields.Append("Mon_Id", DataTypeEnum.adInteger, 10, FieldAttributeEnum.adFldIsNullable,
Missing.Value);
rs.Fields.Append("Reading_Date", DataTypeEnum.adDate, 10, FieldAttributeEnum.adFldIsNullable,
Missing.Value);
//…
rs.Open(Missing.Value, Missing.Value, CursorTypeEnum.adOpenStatic,
LockTypeEnum.adLockOptimistic, 1);

DataTable dt = monitoringService.ListMonitoringReadings(monId, siteId, departmentId,
maxNumberOfReadingsToReturn);

foreach (DataRow row in dt.Rows)
{
     rs.AddNew(Missing.Value, Missing.Value);
     rs.Fields[0].Value = row[0];
     rs.Fields[1].Value = row[1];
      rs.Fields[2].Value = row[2];
     //…
}

return rs;
RecordsetClass rs = new RecordsetClass();
                                                                                                "
rs.Fields.Append("Id", DataTypeEnum.adInteger, 10, FieldAttributeEnum.adFldIsNullable,
Missing.Value);
rs.Fields.Append("Mon_Id", DataTypeEnum.adInteger, 10, FieldAttributeEnum.adFldIsNullable,
Missing.Value);
rs.Fields.Append("Reading_Date", DataTypeEnum.adDate, 10, FieldAttributeEnum.adFldIsNullable,
Missing.Value);
//…
rs.Open(Missing.Value, Missing.Value, CursorTypeEnum.adOpenStatic,
LockTypeEnum.adLockOptimistic, 1);

DataTable dt = monitoringService.ListMonitoringReadings(monId, siteId, departmentId,
maxNumberOfReadingsToReturn);
                                                                                                "
foreach (DataRow row in dt.Rows)
{
     rs.AddNew(Missing.Value, Missing.Value);
     rs.Fields[0].Value = row[0];
     rs.Fields[1].Value = row[1];
      rs.Fields[2].Value = row[2];
     //…
}

return rs;
var"prodServEn4ty"="ProdServService.GetById(prodServId);"
var"builder"="new"RecordsetBuilder<Qual_ProdServEn4ty>();"
return"builder.From(prodServEn4ty).BuildHeadAddi4onWith(AddOwnerForHead)."
""""""""""""BuildRowAddi4onWith(AddOwnerForRow).Build();"
"
private"void"AddOwnerForRow(RecordsetClass"rs,"Qual_ProdServEn4ty"prodServEn4ty)"
{"
""""""""""""rs.Fields[OwnerName].Value"="GetUserNameById(prodServEn4ty.Owner);"
""""""""""""rs.Fields[OwnerId].Value"="prodServEn4ty.Owner;"
}"
"
private"sta4c"void"AddOwnerForHead(RecordsetClass"rs)"
{"
""""""""""""rs.Fields.Append(OwnerName,"DataTypeEnum.adVarWChar,"101,"
FieldA^ributeEnum.adFldIsNullable,"Missing.Value);"
""""""""""""rs.Fields.Append(OwnerId,"DataTypeEnum.adInteger,"0,"
FieldA^ributeEnum.adFldIsNullable,"Missing.Value);"
}"
var"prodServEn4ty"="ProdServService.GetById(prodServId);"
var"builder"="new"RecordsetBuilder<Qual_ProdServEn4ty>();"
return"builder.From(prodServEn4ty).BuildHeadAddi4onWith(AddOwnerForHead)."
""""""""""""BuildRowAddi4onWith(AddOwnerForRow).Build();"
"
private"void"AddOwnerForRow(RecordsetClass"rs,"Qual_ProdServEn4ty"prodServEn4ty)"
{"
""""""""""""rs.Fields[OwnerName].Value"="GetUserNameById(prodServEn4ty.Owner);"
""""""""""""rs.Fields[OwnerId].Value"="prodServEn4ty.Owner;"
}"
"
private"sta4c"void"AddOwnerForHead(RecordsetClass"rs)"
{"
""""""""""""rs.Fields.Append(OwnerName,"DataTypeEnum.adVarWChar,"101,"
FieldA^ributeEnum.adFldIsNullable,"Missing.Value);"
""""""""""""rs.Fields.Append(OwnerId,"DataTypeEnum.adInteger,"0,"
FieldA^ributeEnum.adFldIsNullable,"Missing.Value);"
}"
public"class"RecordsetBuilder<T>"{"
""""""""private"IEnumerable<T>"en44es;"
""""""""private"T"en4ty;"
""""""""private"Field[]"fields;"
""""""""private"Ac4on<RecordsetClass>"headBuilder;"
""""""""private"Ac4on<RecordsetClass>"headAddi4onBuilder;"
""""""""private"Ac4on<RecordsetClass>"bodyBuilder;"
""""""""private"Ac4on<RecordsetClass,"T>"rowBuilder;"
""""""""private"Ac4on<RecordsetClass,"T>"rowAddi4onBuilder;"
""""""""//…"
}"
                       public RecordsetClass Build()
                       {
                               var recordset = new RecordsetClass();
                               (headBuilder ?? BuildHead)(recordset);
                               (headAdditionBuilder ?? (x => { }))(recordset);
                               (bodyBuilder ?? BuildBody)(recordset);
                               Initialise(recordset);
                               return recordset;
                       }
public"class"RecordsetBuilder<T>"{"
""""""""private"IEnumerable<T>"en44es;"
""""""""private"T"en4ty;"
""""""""private"Field[]"fields;"
""""""""private"Ac4on<RecordsetClass>"headBuilder;"
""""""""private"Ac4on<RecordsetClass>"headAddi4onBuilder;"
""""""""private"Ac4on<RecordsetClass>"bodyBuilder;"
""""""""private"Ac4on<RecordsetClass,"T>"rowBuilder;"
""""""""private"Ac4on<RecordsetClass,"T>"rowAddi4onBuilder;"
""""""""//…"
}"
                       public RecordsetClass Build()
                       {
                               var recordset = new RecordsetClass();
               "               (headBuilder ?? BuildHead)(recordset);
                               (headAdditionBuilder ?? (x => { }))(recordset);
                               (bodyBuilder ?? BuildBody)(recordset);
                               Initialise(recordset);
                               return recordset;
                       }
public"sta4c"Field"As(this"string"str,"string"alias)""
{"
                                                                   Syntax"Sugar"
""""""""""""return"new"Field(str).As(alias);"
}"
"
public"sta4c"Field"As(this"En4tyField2"en4tyField2,"string"alias)"
{"
""""""""""""return"new"Field(en4tyField2).As(alias);"
}"


  public"sta4c"implicit"operator"Field(string"field)"
                                                               Implicit"
  {"
                                                              Conversion"
  """"""""""""return"new"Field(field);"
  }"
  "
  public"sta4c"implicit"operator"Field(En4tyField2"field)"
  {"
  """"""""""""return"new"Field(field);"
  }"
谢谢!

Weitere ähnliche Inhalte

Ähnlich wie 使用Dsl改善软件设计

Java DSL与动态代码生成技术的应用 (上集:DSL部分)
Java DSL与动态代码生成技术的应用 (上集:DSL部分)Java DSL与动态代码生成技术的应用 (上集:DSL部分)
Java DSL与动态代码生成技术的应用 (上集:DSL部分)悦 温
 
Linux binary Exploitation - Basic knowledge
Linux binary Exploitation - Basic knowledgeLinux binary Exploitation - Basic knowledge
Linux binary Exploitation - Basic knowledgeAngel Boy
 
R統計軟體 -安裝與使用
R統計軟體 -安裝與使用R統計軟體 -安裝與使用
R統計軟體 -安裝與使用Person Lin
 
改善Programmer生活的sql技能
改善Programmer生活的sql技能改善Programmer生活的sql技能
改善Programmer生活的sql技能Rack Lin
 
Spark introduction - In Chinese
Spark introduction - In ChineseSpark introduction - In Chinese
Spark introduction - In Chinesecolorant
 
開放原始碼 Ch2.4 app - oss - db (ver 1.0)
開放原始碼 Ch2.4   app - oss - db (ver 1.0)開放原始碼 Ch2.4   app - oss - db (ver 1.0)
開放原始碼 Ch2.4 app - oss - db (ver 1.0)My own sweet home!
 
.Net网络编程入门
.Net网络编程入门.Net网络编程入门
.Net网络编程入门magicshui
 
合久必分,分久必合
合久必分,分久必合合久必分,分久必合
合久必分,分久必合Qiangning Hong
 
D2_node在淘宝的应用实践_pdf版
D2_node在淘宝的应用实践_pdf版D2_node在淘宝的应用实践_pdf版
D2_node在淘宝的应用实践_pdf版Jackson Tian
 
前端样式开发演变之路
前端样式开发演变之路前端样式开发演变之路
前端样式开发演变之路Zhao Lei
 
N-layer design & development
N-layer design & developmentN-layer design & development
N-layer design & developmentXuefeng Zhang
 
Node.js在淘宝的应用实践
Node.js在淘宝的应用实践Node.js在淘宝的应用实践
Node.js在淘宝的应用实践taobao.com
 
Introduction to big data
Introduction to big dataIntroduction to big data
Introduction to big data邦宇 叶
 
Pegasus: Designing a Distributed Key Value System (Arch summit beijing-2016)
Pegasus: Designing a Distributed Key Value System (Arch summit beijing-2016)Pegasus: Designing a Distributed Key Value System (Arch summit beijing-2016)
Pegasus: Designing a Distributed Key Value System (Arch summit beijing-2016)涛 吴
 
Exadata那点事
Exadata那点事Exadata那点事
Exadata那点事freezr
 
NoSQL误用和常见陷阱分析
NoSQL误用和常见陷阱分析NoSQL误用和常见陷阱分析
NoSQL误用和常见陷阱分析iammutex
 

Ähnlich wie 使用Dsl改善软件设计 (20)

Java DSL与动态代码生成技术的应用 (上集:DSL部分)
Java DSL与动态代码生成技术的应用 (上集:DSL部分)Java DSL与动态代码生成技术的应用 (上集:DSL部分)
Java DSL与动态代码生成技术的应用 (上集:DSL部分)
 
Node分享 展烨
Node分享 展烨Node分享 展烨
Node分享 展烨
 
Linux binary Exploitation - Basic knowledge
Linux binary Exploitation - Basic knowledgeLinux binary Exploitation - Basic knowledge
Linux binary Exploitation - Basic knowledge
 
R統計軟體 -安裝與使用
R統計軟體 -安裝與使用R統計軟體 -安裝與使用
R統計軟體 -安裝與使用
 
改善Programmer生活的sql技能
改善Programmer生活的sql技能改善Programmer生活的sql技能
改善Programmer生活的sql技能
 
Spark introduction - In Chinese
Spark introduction - In ChineseSpark introduction - In Chinese
Spark introduction - In Chinese
 
開放原始碼 Ch2.4 app - oss - db (ver 1.0)
開放原始碼 Ch2.4   app - oss - db (ver 1.0)開放原始碼 Ch2.4   app - oss - db (ver 1.0)
開放原始碼 Ch2.4 app - oss - db (ver 1.0)
 
.Net网络编程入门
.Net网络编程入门.Net网络编程入门
.Net网络编程入门
 
合久必分,分久必合
合久必分,分久必合合久必分,分久必合
合久必分,分久必合
 
D2_node在淘宝的应用实践_pdf版
D2_node在淘宝的应用实践_pdf版D2_node在淘宝的应用实践_pdf版
D2_node在淘宝的应用实践_pdf版
 
前端样式开发演变之路
前端样式开发演变之路前端样式开发演变之路
前端样式开发演变之路
 
Jasmine
JasmineJasmine
Jasmine
 
N-layer design & development
N-layer design & developmentN-layer design & development
N-layer design & development
 
Node.js在淘宝的应用实践
Node.js在淘宝的应用实践Node.js在淘宝的应用实践
Node.js在淘宝的应用实践
 
Dpl in action
Dpl in actionDpl in action
Dpl in action
 
Why use MySQL
Why use MySQLWhy use MySQL
Why use MySQL
 
Introduction to big data
Introduction to big dataIntroduction to big data
Introduction to big data
 
Pegasus: Designing a Distributed Key Value System (Arch summit beijing-2016)
Pegasus: Designing a Distributed Key Value System (Arch summit beijing-2016)Pegasus: Designing a Distributed Key Value System (Arch summit beijing-2016)
Pegasus: Designing a Distributed Key Value System (Arch summit beijing-2016)
 
Exadata那点事
Exadata那点事Exadata那点事
Exadata那点事
 
NoSQL误用和常见陷阱分析
NoSQL误用和常见陷阱分析NoSQL误用和常见陷阱分析
NoSQL误用和常见陷阱分析
 

使用Dsl改善软件设计