特定领域的语言使非开发人员领域的专家可以通过“可执行规范”直接为软件开发过程做出贡献。比散文用户故事或需求文档有用得多。
从软件架构师的角度来看,DSL允许业务逻辑与实现技术完全分离,从而避免了对遗留系统的陷阱。
与流行的看法相反,该方法在实践中有效,如本文通过一系列真实的故事所示,这些故事说明了公司如何从该方法中受益。
DSL可以用于系统工程,医疗保健以及金融,保险和工资单等更经典的业务领域。每种语言都不同,但是概念,基础结构和语言工具都相同。
通过代码生成实现软件开发的自动化是DSL的重要优势。但是还有其他一些功能,包括分析,优化,更重要的是,非开发者利益相关者更有意义地包含在软件创建过程中。
或者更确切地说,一个特定的未来已经来临。一种使用领域特定语言(DSL)来显着提高生产力,软件质量或体系结构灵活性的语言。但是,用吉布森(Gibson)来解释,这种方法并未得到广泛使用,因此被认为是未来的发展方向。或者也许根本不起作用。
在本文中,我讨论了在实际应用中成功使用的许多示例DSL。我亲自参与了其中许多人的开发,其他人则是由我认识和信任的人开发的。在本文中,我使用短文来讨论语言。论文的构成和一些细节都是虚构的,但对真实项目中发生的事情忠实。对于其中的几种语言,已经发表了更广泛,更系统的论文,我请读者参考每个轶事结尾处的那些论文。
本文是“传闻证据”的定义。我正在尝试使用这种样式,以试图尽可能降低标准,以使人们了解为什么人们使用DSL及其成功。显然,本文并不是关于DSL的最科学的论文。如果您想阅读更多关于这些相同主题的技术性讨论,建议您阅读论文``将建模和编程融合到面向语言的编程中,以了解建模和编程之间的关系1''。功能语言的设计和使用,以及从开发mbeddr中学到的经验教训:使用MPS进行语言工程的案例研究,以及使用MPS构建DSL的经验。
我将首先简要介绍DSL。如果您有使用DSL的经验,建议您跳过下一部分。
由于软件在越来越多样化的领域中起着核心作用,因此越来越多的人知道软件应该做什么与设计软件的人不同。因此,为了使软件正常工作,域专家的脑内容必须形式化为我们称为程序的可执行配方。传统方法是领域专家以或多或少的非结构化方式将其要求传达给程序员:在对话中,通过用户故事,如Word文档,Excel文件或Doors数据库。开发人员阅读此文本,尝试理解它,然后将其正确地实现为程序。形式化为代码的过程有助于发现一些歧义,不一致或不完整;但是最终,领域专家然后才使用完成的软件-或理想地编写Gherkin风格的验收测试-才进行最终验证。
特定领域的语言依赖于不同的方法。它们使领域专家可以直接指定软件的行为。从非结构化思维到可执行规范的转变发生在他们的大脑中。然后,以这种方式创建的可执行规范(或模型)会自动转换为" real"由软件工程师开发的机器的源代码。
这真的有效吗?它在某些条件下会发生。特别是,该语言必须适合非程序员使用。语言中的原语不应该是" computation"通用的。 -例如变量,条件,循环,函数,单子或类-而是特定于域,因此对用户有意义:决策表,处理步骤,税收规则或卫星遥测消息定义。语法应建立在域中使用的现有符号和约定(表格,符号,图表和文本)的基础上,而不仅仅是洋红色的关键字和花括号。从用户只能以非常有限的方式编写新抽象的意义上讲,DSL通常也不太灵活。虽然这对于通用语言来说是个问题,但对DSL来说则是一个加分项,因为它可以确保程序更简单,工具更易于分析并为其提供IDE支持。本文使用轶事来说明和解释以上权利要求。如果您对科学研究感兴趣,那么本文不适合您。而是由Mernik等人进行的这项调查。 (何时以及如何开发特定领域的语言)是一个很好的起点。
DSL并不是一个新概念,它已经存在了很长一段时间,如van Deursen和Klint(领域特定语言:带注释的参考书目)以及通用工具第7.5节(特定语言)所说明。但是,程序员主要使用它们来简化其任务-这些语言专用于技术领域。面向非程序员领域专家的DSL仅在最近10到15年才变得实用,这主要是因为开发这些语言的更好工具。 "更好"主要意味着更快。因为DSL从定义上来说拥有较小的用户群,所以与用于开发业务案例的通用语言相比,其开发工作量必须更少。语言工作台,为语言开发而优化的工具,使这一工作变得现实。语言工作台的一个特别重要的功能是能够重用(部分)语言定义,而无需一遍又一遍地重新开发+运算符。 DSL的第二个推动因素是能够混合使用多种符号样式,以确保该语言对于广泛领域的用户而言都是直观的。再次,支持这种情况的行业实力工具已经存在了大约十年。
因此,现在我们已经奠定了基础,让我们来看几个示例域以及它们为何成功引入了DSL。
彼得在DATEV工作,该公司为德国许多中小型公司计算工资单。系统根据员工每月支付的总薪水,计算他们必须支付的税金,欠医保的金额,公司车的扣除额,差旅费以及更多其他内容。这些计算基于许多规则,有些是系统的,有些则是随机的,这些规则是德国法律和法规几十年的发展所产生的。彼得(Peter)是业务分析师之一,他的工作是了解那些法律和法规并编写用于实现" implements"的代码。他们在DATEV系统中。
传统上,执行这些计算的软件已在DATEV客户的Windows PC上运行。但是现在,市场压力迫使DATEV将所有内容转移到云和浏览器中。 Jochen是该部门的架构师,负责新系统的技术问题,他的团队决定使用DDD和微服务以Java实现新版本。他们决定重写整个系统,因为应用程序逻辑与Windows实现的细节紧密相连,以至于很难(自动)提取并将其转换为可在云软件堆栈上运行的东西。
市场还需要关于如何打包计算逻辑的更大灵活性。例如,除了主要"计算所有内容"由税务顾问使用的DATEV应用程序,DATEV希望为消费者提供一个小型,独立的Web应用程序,以根据毛额和上述某些扣除额来计算其净工资。考虑到在计算的分析和实现中花费了很多精力,他们显然不想为每个新应用程序一次又一次地实现它。 Jochen部分基于从Windows软件中提取应用程序逻辑有多困难的经验,认识到使薪水逻辑与系统的技术方面完全脱钩至关重要。
另一方面,彼得真的不在乎所有这些技术问题。他在大学学习过工商管理,甚至不认为自己是一名程序员:我试图理解法律的细节,并将其系统化在需求文档中,如精心编写的散文,表格以及有时一点伪代码。"另一方面,Peter理解这种以这种方式表达计算的过程是乏味且容易出错的:它的效率如此之低。我很难说清楚,我无法真正测试我编写的内容,然后应该实现所有内容的开发人员误会了很多。 Peter意识到他必须变得更加精确,更加正式,才能使整个流程更高效,但是他确实不想处理云技术堆栈:“那会让我慢下来!” #34;
Peter指出了另外两件事:"我们的领域有两个主要的复杂性,这些复杂性不能用我在DATEV看到的编程语言直接处理。第一个是基本上所有数据都是时间数据。考虑以下示例,该示例计算员工薪水,他们的宗教信仰和特定月份的教堂税:
有趣的教堂税金(薪水:真实的,宗教的),宗教信仰:enum_enum,月份:int){ 如果虔诚Affil.isOneOf(天主教,新教徒) 然后薪水* 0.05否则0 }
这里的问题是薪水和虔诚的Affil不是简单的实数和枚举,而是每个时间序列,即使在我们计算的那个月内,值也可能发生变化:员工可能会在10月1日加薪,然后在10月1日辞职。 11月12日。
第二个问题是数据结构和计算算法通常(每年)定期更改,以反映不断变化的法规。但是DATEV在法律上有义务能够复制前几年的工资单。更糟糕的是,计算方式可能会在一段时间内发生变化。
语言DATEV DSL的主要目标是使Peter能够更好地完成工作。它使他可以指定完整的计算逻辑,也可以为其编写测试。他可以直接在IDE中执行这些测试。 "就像以这种方式运行单元测试的开发人员同事一样。这确实有助于我对自己的产品树立信心。"
该语言具有货币,日期和百分比的特殊类型和运算符,这些类型在他的领域中无处不在。下面的代码包含示例。该语言还支持用于复杂决策以及收集参考数据的表格符号。我知道,这只是语法,但是对于德国法律所表达的那些复杂的判决程序却有很大的不同。下一节给出了决策表的示例。
但是,最重要的是,该语言直接支持时间性和版本。上面给出的函数将编写如下:
ChurchTax的[每月]规则 使用e:员工{ result.taxValue:= alt | e.religiousAffil!.isOneOf(天主教,新教徒)=>工资的5%! | |否则=> 0欧元| }
注意!时间变量后的为ligiousAffil和salary。这将调用默认归约,这是一个运算符,它会自动从时间序列中创建一个值。创建此单个值的策略作为数据结构定义的一部分给出(此处未显示)。对于宗教信仰,它使用给定时间段内的最后一个值(如法律所述!),对于薪水,它使用加权平均值。 Apropos给定期间&#34 ;:由于规则被标记为每月,因此存在一个隐含参数期间,该期间与默认减少量一起使用。彼得:"我可以编写看起来非常直观的代码,并且临时性的东西发生在表面之下。
让我们看一下版本控制。该语言支持代码中版本的显式声明。数据结构和规则(例如上述结构)可以在新版本中覆盖。 Peter说,新版本相对于上一个版本的格式是否正确,由(特定于域的)静态类型系统检查,带有错误消息,我可以理解。然后,运行时将对不同时间点的计算规则执行一种多态调度,即使您例如每年计算所有12个月的教堂税,并且规则在年中更改。
彼得说,那很整齐。 "即使我在学习某种语言时有一点学习困难。但是,IDE,可理解的错误消息以及数据关系和版本的集成可视化确实有帮助。这是值得的!
还有建筑师约琛? "嗯,我真的很喜欢这种方法。首先,我得到了经过全面测试的应用程序逻辑。那些家伙甚至有一个覆盖率分析仪!实际上,我什至不再看它,我只是运行生成器,该生成器生成了一个插入到Spring服务中的JAR文件。应用程序逻辑独立于Java中的特定实现的事实也很有用:我们已将其中的部分生成到JavaScript中,以便在浏览器中进行验证。即使在开发该语言的过程中,该团队也将堆栈从JEE更改为Spring,并且很容易地将应用程序逻辑重新定位为目标。如何构建所有这些呢? Jochen再说一次:"构建语言,运行时和生成器的同事在开始时就对所有语言技术都有些挣扎,但最终,他们掌握了它。
学习将应用程序逻辑与技术分离是使用DSL的众所周知的原因,但值得再次探讨。为什么DATEV很难从旧版系统中提取域功能?实际上如此难,他们手动对所有内容进行了逆向工程,然后从头开始重新编写了所有内容?因为一旦您用任何通用语言对业务逻辑进行了编码,就会失去很多领域语义,以至于很难对其进行自动逆向工程。当然,您可以将Java转换为Kotlin或其他任何方式,但您不会赢回。干净域语义。因此,将业务与技术分离并尽可能清晰地编码域语义是至关重要的。这是您业务的资产! DSL最适合实现这种理想情况。
还很容易看到表格和其他非线性符号对于表达复杂的决策很有用。在这种情况下,DSL在帮助解决域,时间数据和版本控制等隐藏的复杂性方面的重要性并不那么明显。开发团队花了一些时间才发现这些是复杂性的主要来源,并找到了解决这些问题的好方法。
开发人员的传说也表明,领域专家很乐意在Word中不精确地表达自己的观点(如果以后出现问题,则应责怪开发人员)。尽管有这样的人存在(我已经见过他们),但还有其他类似Peter这样的人,他们看到了基于文档的方法的缺点,并愿意了解他们的需求是否得到了认真的对待,例如体现在DSL中。
绊脚石至少DATEV的业务程序员更关心细节(例如关键字名称),而不是我们认为我们低估了照顾这些细节的努力。它们还较少重视正交性和概念一致性,这意味着运行时的实现变得工作量更大。甚至在有工具支持的情况下,某些特别花哨的版本控制方法也难以理解,以至于我们用不那么灵活但更易于理解的替代方法代替了它们(使语言设计师感到沮丧的是他们提出了更优雅的方法。解决方案)。
相似的案例我们在商业领域中建立了以相同成功标准为指导的其他几种DSL。一种是计算德国公民(也包括DATEV)的年度纳税申报表。该语言还使用时态数据,但不需要相同的版本控制支持,因为每个日历年都是单独的,并且在git中进行分支就足够了。顶层结构基本上是一棵大树,类似于德国税法的术语层次结构。该语言还支持隐式每月计算以及优化的声明式规范。测试由解释器运行,并且对于最终部署,模型生成为C(对于内部旧版应用程序)和Java(对于云解决方案)。有趣的是,执行模型依赖于基于计算树中静态分析的依存关系的增量计算。该系统替代了已经使用了二十年的较旧的命令式DSL。
最近,我们收到了一封非常不错的电子邮件,部门主管在一段视频中发表了评论,其中茱莉亚(Julia)审查了彼得(Peter)创建的模型:
您好茱莉亚,彼得给了我您的视频,您在其中回顾了他的房地产评估模型。我为您的建设性方式而激动,这让我着迷,您无需阅读法律本身,就能清楚地理解彼得在这里表达的内容。
尽管到目前为止,我几乎看不到这种语言,但我认为这段视频给我留下了深刻的印象,使语言变得十分有趣,尤其是与法律保持一致的分层方法。尝试跟上您的思想,看看您如何重构层次结构以更好地与法律文本保持一致,这确实很有趣。
除了依法排列的层次结构外,我真正喜欢的是可以显式描述与法律相关的数据值,并在必要时甚至使用表。有趣的是,当您移动整个子树时,编辑器提供了多少功能,执行了哪些检查,以及如何找出在哪里使用了哪些值。
现在,我了解到是什么使您对这种语言如此热衷,我真的感到有自己开发这种语言的冲动。
确定政府如何收取税款和支付利益的法规当然足够复杂,以至于需要使用DSL。例如,在荷兰,公共利益支付由一个系统计算,该系统的计算核心来自基于DSL的模型。荷兰税务局具有对业务规则进行建模的历史,并且在过去几年中已将所有内容移至DSL和代码生成器。他们现在正在为税收制度做同样的事情。为此,他们建立了敏捷法律执行工厂,即ALEF。这些语言在很大程度上依赖于类似于散文的语法以及最终用户和调试器所指定的测试,该调试器将属性值覆盖在源代码上。
看到改进布鲁格
......