使用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# #      PythonRuby# Groovy#  Fortran#         C#
不够? 缺什么?
不够? 缺什么?
少即是多更抽象、更内聚的DSL或许是解决方案
DSL早已充斥我们周围
graphviz                                                  LINQant                  rake                             Hibern...
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""...
定义      a"computer"programming"language"of""       limited"expressiveness"focused"on"a""                par4cular"domain""...
定义      a"computer"programming"language"of""       limited"expressiveness"focused"on"a""                par4cular"domain""...
定义      a"computer"programming"language"of""       limited"expressiveness"focused"on"a""                par4cular"domain""...
让我们点杯星巴克拿铁
例子:咖啡订单DSL•  星巴克咖啡订单“一杯超大杯(Venti)、低咖(half-caf)、   脱脂(non-fat)、无奶沫、不加鲜奶油(no   whip)的拿铁(latte)”•  传统的实现
上面例子中的问题•  依赖于技术实现API•  代码与我们向服务员下单的描述不匹配•  阅读性不佳,难以验证
DSL style•  更好地展现了代码意图•  利用了Ruby特性 –  无类型声明 –  无括号参数列表 –  元编程支持
基于Java的咖啡订单DSLAPI 方式CoffeeOrder order = new Latte(VENTI,  HALF_CAF, NONFAT_MILK);CoffeeOrder coffee = order.prepare(false)...
其他的展现形式?•  XML<Order type=“Latte”>  <caffeine>half</caffeine>  <milk>nonfat</milk>  <foam>false</form>  <whip>false</whip>...
DSL的两种类型外部DSL                    内部DSL•  与宿主语言无关               •  以宿主语言进行编写•  执行需要编译器或者             •  基于约定地使用宿主   解释器    ...
外部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 Sof...
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()  ...
模型与构造器       Expression Builder                    Semantic Model       (fluent)                              (push-button...
相同的问题,不同的DSLcomputer()         computer();  .processor()       processor();    .cores(2)          cores(2);    .i386()    ...
函数序列computer();             •  一系列函数(功能)调用   processor();                        •  调用全局函数可能比较不便     cores(2);            ...
方法链computer()                    •   .processor()    .cores(2)       •     .i386()  .disk()                •     .size(150...
嵌套函数computer(     processor(        •        cores(2),                            •  Processor.Type.i386   •         size(...
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.adFldIsNull...
RecordsetClass rs = new RecordsetClass();                                                                                 ...
var"prodServEn4ty"="ProdServService.GetById(prodServId);"var"builder"="new"RecordsetBuilder<Qual_ProdServEn4ty>();"return"...
var"prodServEn4ty"="ProdServService.GetById(prodServId);"var"builder"="new"RecordsetBuilder<Qual_ProdServEn4ty>();"return"...
public"class"RecordsetBuilder<T>"{"""""""""private"IEnumerable<T>"en44es;"""""""""private"T"en4ty;"""""""""private"Field[]...
public"class"RecordsetBuilder<T>"{"""""""""private"IEnumerable<T>"en44es;"""""""""private"T"en4ty;"""""""""private"Field[]...
public"sta4c"Field"As(this"string"str,"string"alias)""{"                                                                  ...
谢谢!
使用Dsl改善软件设计
使用Dsl改善软件设计
Upcoming SlideShare
Loading in …5
×

使用Dsl改善软件设计

1,244 views

Published on

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
1,244
On SlideShare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
9
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

使用Dsl改善软件设计

  1. 1. 使用DSL改善软件设计 金明
  2. 2. 关于我•  金明•  ThoughtWorks高级咨询师•  InfoQ中文站主编•  爱好敏捷、编程、读书、体育运动•  联系方式 –  金明i@weibo –  mingjin@twitter
  3. 3. 聊聊软件设计那些事儿...
  4. 4. 为什么我们的Java程序里面那么多XML文件?
  5. 5. 为什么我们的Java程序里面那么多XML文件?为什么我们需要面向切面编程(AOP)?
  6. 6. 为什么我们的Java程序里面那么多XML文件?为什么我们需要面向切面编程(AOP)?为什么大家对Ruby on Rails仍然津津乐道?
  7. 7. 为什么我们的Java程序里面那么多XML文件?为什么我们需要面向切面编程(AOP)?为什么大家对Ruby on Rails仍然津津乐道?面向对象编程的下一个“革命”是什么?
  8. 8. 长期以来,我们一直使用通用目的型语言进行软件设计
  9. 9. 但是,我们还缺少什么?
  10. 10. 上下文The context
  11. 11. Subject Matter Experts,难于与领域专家、需求人员沟通 Business analysts...
  12. 12. 表达性
  13. 13. 通用目的型 编程语言 还不够! C##Erlang# # C++#Java# # PythonRuby# Groovy# Fortran# C#
  14. 14. 不够? 缺什么?
  15. 15. 不够? 缺什么?
  16. 16. 少即是多更抽象、更内聚的DSL或许是解决方案
  17. 17. DSL早已充斥我们周围
  18. 18. graphviz LINQant rake Hibernate Query Language regular expressions DSL早已充斥我们周围SQL make JMock expectations FIT struts-config.xml rails validations CSS
  19. 19. SQL
  20. 20. ^[w-.]+@([w-]){2,4}$
  21. 21. Visual!
  22. 22. DSL•  Domain Specific Language•  领域专用语言•  “两天以后”的DSL例子 –  2.days.from.today
  23. 23. 定义 a"computer"programming"language"of"" limited"expressiveness"focused"on"a"" par4cular"domain"""""""""""""""""""""""""""""""""""""""""""""""""""""""5"Mar4n"Fowler"
  24. 24. 定义 a"computer"programming"language"of"" limited"expressiveness"focused"on"a"" par4cular"domain"""""""""""""""""""""""""""""""""""""""""""""""""""""""5"Mar4n"Fowler"
  25. 25. 定义 a"computer"programming"language"of"" limited"expressiveness"focused"on"a"" par4cular"domain"""""""""""""""""""""""""""""""""""""""""""""""""""""""5"Mar4n"Fowler"
  26. 26. 定义 a"computer"programming"language"of"" limited"expressiveness"focused"on"a"" par4cular"domain"""""""""""""""""""""""""""""""""""""""""""""""""""""""5"Mar4n"Fowler"
  27. 27. 让我们点杯星巴克拿铁
  28. 28. 例子:咖啡订单DSL•  星巴克咖啡订单“一杯超大杯(Venti)、低咖(half-caf)、 脱脂(non-fat)、无奶沫、不加鲜奶油(no whip)的拿铁(latte)”•  传统的实现
  29. 29. 上面例子中的问题•  依赖于技术实现API•  代码与我们向服务员下单的描述不匹配•  阅读性不佳,难以验证
  30. 30. DSL style•  更好地展现了代码意图•  利用了Ruby特性 –  无类型声明 –  无括号参数列表 –  元编程支持
  31. 31. 基于Java的咖啡订单DSLAPI 方式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();
  32. 32. 其他的展现形式?•  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”
  33. 33. DSL的两种类型外部DSL 内部DSL•  与宿主语言无关 •  以宿主语言进行编写•  执行需要编译器或者 •  基于约定地使用宿主 解释器 语言语法的子集•  可以是图形化的 •  有时也被称为嵌入式 DSL或者流畅接口 ThoughtWorks 2008
  34. 34. 外部DSL•  需要构造解析器以处理特定的语法•  sql, make files, xml config files, regular expressions
  35. 35. 优势•  语法不受限制•  运行时执行
  36. 36. 不足•  一开始可能简单,但可能变得丑陋和复杂•  构造解析器比较困难•  缺乏工具的支持
  37. 37. 内部DSL•  在宿主语言之上进行扩展•  Rake, gradle, Jmock Expectations, Xspecs
  38. 38. 优势•  不必编写、调试一门新语言•  宿主语言自身强大的能力•  IDE等工具的支持更好
  39. 39. 不足•  受宿主语言的特性限制
  40. 40. 语言工作台•  Language Workbench•  提供了图形界面的、元建模以及/或者代码 生成,以定义和使用DSL –  Microsoft DSL tool for Visual Studio –  Intentional Software –  Eclipse EMF –  JetBrains Meta Programing System
  41. 41. DSL•  使用更具表达性语言而非通用目的型语言•  在开发人员与业务专家之间共享共同的理 解•  业务专家可以帮助设计应用程序的业务逻 辑•  避免技术代码噪音掩盖了业务逻辑代码•  清晰地将业务逻辑与应用程序代码分离•  业务规则主导开发
  42. 42. DSL的优势•  增加开发生产率 –  清晰的代码更易于演进 –  易于达成,但影响相对较小•  易于与领域专家交流 –  定义系统行为的通用语言 –  易于阅读胜于易于编写 –  难以达成,但影响很大
  43. 43. 创建DSL•  由底向上(Framework Out) –  基于框架或者库定义DSL•  由顶向下(Language In) –  与使用者一起设计DSL –  再驱动DSL实现与其语义模型•  遗留系统打补丁(Patch Legacy) –  为遗留系统自动生成其“拙劣”的代码
  44. 44. 内部DSL的几种形式
  45. 45. computer() .processor() .cores(2) .i386() .disk() .size(150) .disk() .size(75) .speed(7200) .sata() .end();
  46. 46. 模型与构造器 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()
  47. 47. 相同的问题,不同的DSLcomputer() 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();
  48. 48. 函数序列computer(); •  一系列函数(功能)调用 processor(); •  调用全局函数可能比较不便 cores(2); •  全局的解析状态 processorType(i386); •  需要上下文变量 disk(); diskSize(150); disk(); diskSize(75); diskSpeed(7200); diskInterface(SATA);
  49. 49. 方法链computer() •  .processor() .cores(2) •  .i386() .disk() •  .size(150) .disk() .size(75) •  .speed(7200) .sata() •  .end();
  50. 50. 嵌套函数computer( processor( •  cores(2), •  Processor.Type.i386 •  size(75) ), •  disk( size(150) •  ), disk( size(75), speed(7200), Disk.Interface.SATA ) );
  51. 51. DSL最佳实践•  启发式思考最理想的结果 –  你将会站在问题域的角度进行思考 –  引出更好的DSL•  朝着理想结果不断重构•  善假于物(Rake例子)
  52. 52. 测试,测试,再测试!•  编写DSL是一件复杂的事情•  使用DSL则应该简单 –  否则你肯定犯错了•  测试所有的细节部分•  3种类型的测试 –  测试模型本身 –  测试DSL到模型的转换 –  测试使用DSL的特定脚本•  非常重要,因为你将来会进行修改
  53. 53. 问题域•  尽可能保持其精简•  不要试图覆盖完整的问题域•  推荐使用一组更为特定的DSL•  利用元编程
  54. 54. 没有银弹!
  55. 55. 没有银弹!•  学习曲线
  56. 56. 没有银弹!•  学习曲线•  设计良好的语言很难
  57. 57. 没有银弹!•  学习曲线•  设计良好的语言很难•  构造成本
  58. 58. 没有银弹!•  学习曲线•  设计良好的语言很难•  构造成本•  应用范围有限
  59. 59. 没有银弹!•  学习曲线•  设计良好的语言很难•  构造成本•  应用范围有限•  维护成本
  60. 60. 没有银弹!•  学习曲线•  设计良好的语言很难•  构造成本•  应用范围有限•  维护成本•  可能被过度使用或者滥用
  61. 61. 来看一个真实的例子
  62. 62. 例子:Adorb•  ADO Recordset Builder•  将.NET对象结果集转化为VB ADO RecordSet•  数据传输于页面展示•  http://github.com/mingjin/Adorb
  63. 63. 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;
  64. 64. 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;
  65. 65. 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);"}"
  66. 66. 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);"}"
  67. 67. 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; }
  68. 68. 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; }
  69. 69. 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);" }"
  70. 70. 谢谢!

×