Kotlin DSL:理论与实践

开发应用程序测试不是令人愉快的经历。 该过程需要很长时间,需要大量的精力并且非常需要。 Kotlin语言提供了一套工具,使您可以轻松构建自己的面向问题的语言(DSL)。 当Kotlin DSL替代构建器和静态方法来测试资源计划模块时,这是一种经验,这使得从例行程序中添加新测试并支持旧测试变得很有趣。

在本文的过程中,我们将分析开发人员库中的所有主要工具,以及如何将它们组合起来以解决测试问题。 从设计理想测试到为基于Kotlin的资源计划系统启动最近似,最简洁和可理解的测试,我们将一路走来。

本文对练习工程师,那些认为Kotlin是舒适编写紧凑测试语言的人,以及那些希望在其项目中改进测试过程的人都非常有用。



本文基于IPoint Osipov( i_osipov )在JPoint会议上的演示。 进一步的叙述以他的名义进行。 Ivan在Haulmont担任程序员。 该公司的主要产品是CUBA,CUBA是用于开发企业和各种Web应用程序的平台。 尤其是,正在该平台上进行外包项目,其中最近有一个教育领域的项目,其中Ivan参与了为教育机构制定时间表的工作。 碰巧的是,在过去三年中,Ivan一直以某种方式与规划师合作,特别是在Haulmont,他们已经对该规划师进行了一年的测试。

对于那些想要运行示例的人- 保留指向GitHub的链接 。 在链接下,您会找到我们今天将解析,运行和编写的所有代码。 打开代码开始!



今天我们将讨论:

  • 什么是面向问题的语言;
  • 内置的面向问题的语言;
  • 为教育机构制定时间表;
  • 如何用Kotlin进行测试。

今天,我将详细讨论我们在该语言中拥有的工具,并向您展示一些示例,并且我们将从头至尾编写整个测试。 同时,我想更加客观,因此我将谈谈在开发过程中为自己确定的一些缺点。

让我们从讨论计划构建模块开始。 因此,进度表的构建分几个阶段进行。 每个步骤都需要分别测试。 您需要了解,尽管步骤不同,但我们有一个通用的数据模型。



这个过程可以表示如下:在输入处有一些具有通用模型的数据,在输出处有一个时间表。 验证数据,过滤数据,然后建立培训小组。 这是指教育机构时间表的主题领域。 基于构造的组和其他一些数据,我们进行了上课。 今天,我们仅讨论最后一个阶段-有关班级的安排。



关于测试调度程序的一些知识。

首先,您已经了解到,必须分别测试不同的阶段。 可以选择一个或多或少的标准测试开始过程:进行数据初始化,启动调度程序,检查此调度程序本身的结果。 有很多不同的业务案例需要涉及,并且需要考虑不同的情况,以便在制定计划时,这些情况也将持续存在。

模型有时可能很重,并且为了创建单个实体,有必要初始化五个其他实体,甚至更多。 因此,总的来说,可以获得大量的代码,我们为每个测试一次又一次地编写代码。 支持此类测试需要花费大量时间。 如果您想更新模型(有时会发生),那么更改的规模会影响测试。

让我们编写一个测试:



让我们编写最简单的测试,以便您大致了解图片。
考虑测试时首先想到的是什么? 也许这些是这类原始测试:创建一个类,在其中创建一个方法,并使用注释Test对其进行标记。 结果,我们使用了JUnit的功能,并初始化了一些数据,默认值,然后是测试特定的值,并对其余模型进行了相同的操作,最后创建了一个调度程序对象,将数据传输给它,我们开始,我们收到结果,然后检查它们。 或多或少的标准过程。 但是显然其中有代码重复。 首先想到的是将所有内容放入静态方法的能力。 既然有很多默认值,为什么不隐藏它呢?



这是减少重复的良好第一步。



看到这一点,您了解到我想使模型更紧凑。 在这里,我们有一个构建器模式,其中,在幕后某个地方初始化了默认值,而在那儿初始化了特定于测试的值。 情况越来越好,但是,我们仍在编写样板代码,并且每次都在重新编写。 想象一下200个测试-您必须写这三行200次。 显然,我希望以某种方式摆脱这种情况。 发展这个想法,我们达到了一定的极限。 因此,例如,我们可以为所有内容创建一个模式构建器。



您可以从头开始创建一个调度程序,设置我们需要的所有值,开始调度,一切都很好。 如果您仔细查看此示例并进行详细分析,就会发现正在编写许多不必要的代码。 我想使测试更具可读性,以便您可以看一下并立即理解,而无需研究模式等。

因此,我们有一些不必要的代码。 简单的数学表明,比我们需要的字母多55%,我想以某种方式摆脱它们。



一段时间后,由于需要支持更多代码,因此对我们的测试的支持会变得更加昂贵。 有时,如果我们不付出任何努力,可读性要么有很多不足之处,要么是可以接受的,但是我们希望更好。 也许稍后我们将开始添加某种框架,库,以使测试更易于编写。 因此,我们提高了测试应用程序的入门水平。 在这里,我们已经有一个复杂的应用程序,其测试的进入水平非常重要,并且我们还在不断增加它。

完美测试


很高兴地说一切都很糟糕,但让我们考虑一下它会非常好。 我们想要得到的理想示例:



想象一下,有一个声明,其中我们说这是具有特定名称的测试,并且我们想使用空格来分隔名称中的单词,而不是CamelCase。 我们正在建立时间表,我们有一些数据,并检查了计划程序的结果。 由于我们主要使用Java进行工作,并且主应用程序的所有代码都是用这种语言编写的,因此我希望具有兼容的测试功能。 我想初始化对读者来说尽可能明显的数据。 我想初始化一些通用数据和我们需要的模型的一部分。 例如,创建学生,教师并描述他们何时有空。 这是我们的完美例子。

特定领域的语言




综观所有内容,它似乎开始看起来像某种面向问题的语言。 您需要了解它的含义和区别。 语言可以分为两种类型:通用语言(我们不断编写,解决任何任务并处理所有问题的语言)和面向问题的语言。 因此,例如,SQL帮助我们完美地从数据库中提取数据,而其他一些语言也帮助解决其他特定问题。



实现面向问题的语言的一种方法是嵌入式语言或内部语言。 这些语言是基于通用语言实现的。 也就是说,通用语言的几种构造形成了类似的基础-这就是我们在使用面向问题的语言时所使用的语言。 当然,在这种情况下,面向问题的语言就有机会使用来自通用语言的所有功能。



再次,看看我们的最佳示例,然后考虑选择哪种语言。 我们有三个选择。



第一种选择是Groovy。 一种出色的动态语言,已在构建面向问题的语言中证明了自己。 同样,您可以在Gradle中举一个构建文件的示例,我们许多人都在使用该文件。 还有Scala,它有大量的机会实施自己的东西。 最后是Kotlin,它也可以帮助我们构建一种面向问题的语言,今天将进行讨论。 我不想发动战争并将Kotlin与其他东西进行比较,但是,这仍然取决于您的良心。 今天,我将向您展示Kotlin在开发面向问题的语言方面的能力。 当您想比较一下并说某种语言更好时,您可以返回本文并轻松地看到不同之处。



Kotlin给我们提供了什么以开发面向问题的语言?

首先,它是一个静态类型,所有由此产生的结果。 在编译阶段,可以检测到很多问题,这可以节省很多时间,尤其是当您不想遇到与语法和编写测试有关的问题时。
然后,有一个来自Kotlin的很棒的类型推断系统。 这很妙,因为不需要一次又一次地编写任何类型,编译器会以爆炸的形式显示所有内容。

第三,为开发环境提供了出色的支持,这不足为奇,因为同一家公司是当今的主要开发环境,而Kotlin正是。
最后,在DSL内部,显然我们可以使用Kotlin。 以我的主观观点,支持DSL比支持实用程序类容易得多。 正如您将在后面看到的那样,可读性比构建器略好。 我的意思是“更好”:您所需要编写的语法要少一些-读取您的面向问题的语言的人可以更快地使用它。 最后,写自行车会更有趣! 但是实际上,实施面向问题的语言比学习一些新框架要容易得多。

我将再次提醒您到GitHub链接 ,如果您想进一步编写演示,则可以进入该链接并从中获取代码。

在Kotlin上设计理想


让我们继续设计理想,但是已经在Kotlin。 看一下我们的例子:



并且我们将分阶段开始对其进行重建。

我们有一个测试,它可以在Kotlin中变成一个函数,可以使用空格来命名。



我们将用Test批注对其进行标记,可以从JUnit获得该批注。 在Kotlin中,您可以使用缩写形式编写函数,并通过=消除函数本身多余的花括号。

时间表我们变成一个块。 由于我们仍然在Kotlin工作,因此许多设计也会发生同样的事情。



让我们继续其余的。 花括号再次出现,我们不会摆脱它们,但至少尝试使之更接近我们的示例。 通过构造具有空间的构造,我们可以以某种方式完善自身并使其与众不同,但在我看来,最好采用通常的方法来封装处理过程,但总的来说,这对于用户而言是显而易见的。



我们的学生变成了一个块,我们在其中使用属性,方法,并且我们将继续与您进行分析。



最后是老师。 这里我们有一些嵌套块。



在下面的代码中,我们继续进行检查。 我们需要检查与Java语言的兼容性-是的,Kotlin与Java兼容。



Kotlin的DSL开发兵工厂




让我们继续我们拥有的工具列表。 在这里,我带来了一个平板电脑,也许它列出了用Kotlin开发面向问题的语言所需的一切。 您可以不时回到她身边,刷新她的记忆。

下表显示了针对问题的语法和该语言中可用的常用语法的一些比较。

科特林的Lambdas


val lambda: () -> Unit = { }

让我们从Kotlin拥有的最基本的砖开始-这些是lambda。
今天,对于lambda类型,我将仅表示功能类型。 Lambda表示如下:( ( ) ->

我们在花括号的帮助下初始化lambda,在其中可以编写一些将被调用的代码。 也就是说,事实上,lambda只是将其自身隐藏起来。 运行这样的lambda看起来像一个函数调用,只是括号。



如果要传递某种参数,首先必须在类型中对其进行描述。
其次,我们可以使用它可以使用的默认标识符,但是,如果这不适合我们,我们可以设置自己的参数名称并使用它们。



同时,我们可以跳过此参数的使用,并使用下划线以免产生标识符。 在这种情况下,要忽略标识符,可能根本不写任何内容,但是在通常情况下,对于多个参数,会提到“ _”。



如果我们要传递多个参数,则需要显式定义其标识符。



最后,如果我们尝试将lambda传递给某个函数并在其中运行,将会发生什么。 它的初始近似值如下所示:我们有一个函数,将lambda放在大括号中,如果在Kotlin中将lambda写为最后一个参数,则可以将其排除在这些括号之外。



如果括号中没有任何东西,我们可以将其卸下。 熟悉Groovy的人应该对此很熟悉。



这在哪里适用? 绝对无处不在。 也就是说,我们已经讨论过的非常大括号,我们使用它们,这些就是非常小的lambda。



现在让我们看一下lambda的变体之一,我将它们称为带有上下文的lambda。 您会发现其他一些名称,例如带有接收器的lambda,它们在声明类型时与普通lambda不同,如下所示:在左侧,我们添加了一些上下文类,它可以是任何类。



这是为了什么 这是必要的,以便在lambda中我们可以访问this关键字-这是恰好关键字,它告诉我们我们的上下文,即链接到lambda的某个对象。 因此,例如,我们可以创建一个lambda来输出一些字符串,自然地,我们将使用字符串类来声明上下文,这样的lambda的调用将如下所示:







如果要将上下文作为参数传递,也可以这样做。 但是,我们无法完全传达上下文,也就是说,带有上下文的lambda需要引起注意! -是的,是的。 如果我们开始将带有上下文的lambda传递给某种方法会怎样? 在这里,我们再次查看我们的exec方法:



将其重命名为Student方法-没有任何变化:



因此,我们逐渐转向我们的构造,即学生构造,在大括号下隐藏了所有初始化。



让我们弄清楚。 我们有某种学生功能,需要一个带有学生上下文的lambda。



显然,我们需要上下文。



在这里,我们创建一个对象并在其上运行此lambda。



结果,我们还可以在启动lambda之前初始化一些默认值,因此我们封装了该函数所需的所有内容。



因此,我们可以在lambda内访问this关键字-这就是为什么可能存在具有上下文的lambda的原因。



自然,我们可以摆脱这个关键字,并且有机会编写这样的结构。



同样,如果我们不仅拥有专有的方法,而且还拥有一些方法,我们也可以调用它们,这看起来很自然。



申请书


代码中所有这些lambda都是上下文lambda。 有大量上下文,它们以一种或另一种方式相交,使我们能够构建面向问题的语言。



总结lambdas-我们有普通的lambdas,在上下文中也有,可以使用它们。



经营者


Kotlin具有有限的运算符集,您可以使用约定和operator关键字覆盖这些运算符。

让我们看一下老师及其辅助功能。 假设我们说老师每周一从上午8点开始工作1个小时。 我们还想说,除了这一小时之外,它从13.00开始工作1个小时。 我想用+运算符表达这一点。 怎么办呢?



有一些可用性方法可以接受带有AvailabilityTable上下文的lambda。 这意味着有一个称为的类,并且在该类中声明了monday方法。 此方法返回DayPointer因为 您需要将我们的操作员附加到某些东西上。



让我们找出什么是DayPointer。 这是指向某位教师的工作量表的指针,并且工作日程如期进行。 我们还有一个时间函数,它将以某种方式将某些行转换为整数索引:在Kotlin中,我们有一个IntRange类。

左边是DayPointer ,右边是时间,我们想将它们与+运算符组合在一起。 为此,您可以在DayPointer类中创建我们的运算符。 它将采用Int类型的一系列值并返回DayPointer以便我们可以DayPointer链接DSL。
现在,让我们看一下从DSL开始的所有关键设计。 它的实现略有不同,现在我们将解决它。
Kotlin在语言中内置了一个单例概念。 为此,使用了object关键字而不是class关键字。 如果我们在单例内部创建方法,则可以以无需再次创建此类实例的方式访问它。 我们仅将其称为类中的静态方法。



如果查看反编译的结果(即在开发环境中,单击工具-> Kotlin->显示Kotlin字节码->反编译),则可以看到以下单例实现:



这只是一个普通的类,这里没有超自然现象发生。
另一个有趣的工具是invoke语句。 想象一下,我们有一个类A,有它的实例,并且我们想运行该实例,也就是在该类的对象上调用括号,并且可以invoke操作符来做到这一点。



实际上,括号允许我们调用invoke方法并具有一个运算符修饰符。 如果我们将带有上下文的lambda传递给此运算符,则将得到这样的构造。



每次创建实例都是另一项活动,因此我们可以结合以前和当前的知识。

让我们创建一个单例,将其称为Schedule,在其中我们将声明invoke操作符,在其中我们将创建一个上下文,并且它将接受带有我们在此处创建的上下文的lambda。 事实证明,这是进入DSL的一个入口点,结果,我们得到了相同的构造-大括号的时间表。



好吧,我们谈到了日程安排,让我们来看看我们的检查。
我们有老师,我们已经制定了某种时间表,并且我们想检查一下该老师在某节课的某一天的时间表中是否有一些我们可以使用的对象。



我想使用方括号并以一种看起来像访问数组的方式访问我们的日程表。



可以使用运算符:get / set:



在这里,我们没有做任何新的事情,只是遵循约定。 对于set运算符,我们需要另外将值传递给我们的方法:



因此,用于阅读的方括号变成了get,而通过其分配的方括号变成了set。

演示:对象,运算符


您可以在此处阅读更多文本或观看视频 。 该视频的开始时间明确,但未指定结束时间-原则上,一旦开始,您可以在文章结尾之前观看。

为了方便起见,我将在文字中直接简要介绍视频的本质。

让我们编写一个测试。 我们有一些调度对象,如果通过ctrl + b进行实现,那么我们将看到我之前讨论的所有内容。



在schedule对象内部,我们要初始化数据,然后执行一些检查,然后在数据内部,我们要说:

  • 我们学校早上八点开始营业;
  • 有一些项目我们将为其制定时间表;
  • 有些老师描述了某种无障碍环境;
  • 有一个学生;
  • 原则上,对于一个学生,我们只需要说他正在学习某个特定学科。



这里原则上体现了Kotlin和面向问题的语言的缺点之一:很难解决我们之前创建的某些对象。 在本演示中,我将所有内容都指示为索引,即rus是索引0,数学是索引2。老师自然也可以领导一些事情。 他不仅上班,还从事某些工作。 对于本文的读者来说,我想提供更多的寻址选择,您可以创建唯一的标记并将实体存储在Map中,并在其中需要访问时,可以始终通过标记找到它。 继续拆卸DSL。

在这里,应该注意的是:首先,我们有+运算符,对于它的实现,我们还可以看到实际上有DayPointer类,该类有助于我们使用运算符将​​所有这些东西绑定在一起。

并且由于我们可以访问上下文,因此开发环境告诉我们,在上下文中,通过this关键字,我们可以访问某些集合,并且我们将使用它。



也就是说,我们有一系列事件。 该事件封装了一组属性,例如:有一个学生,一个老师,他们在什么课上遇到的那天。



我们将继续编写测试。



在这里,我们再次使用get运算符;实现起来并不容易,但是我们可以做到。



实际上,我们只是遵循协议,因此我们可以使用此设计。
让我们回到演示文稿,继续关于Kotlin的对话。 我们希望在Kotlin上实施检查,然后经历了以下事件:



事件本质上是4个属性的封装集合。 我想将此事件分解为一组属性,例如元组。 在俄语中,这样的构造称为多重声明 (我只能找到这样的翻译)或解构声明 ,它的工作方式如下:



如果其中一个人不熟悉此功能,则它的工作方式如下:您可以接受该事件,并在使用该事件的地方使用括号将其分解为一组属性。



之所以有效,是因为我们有一个componentN方法,也就是说,由于我们在类之前编写了data修饰符,因此它是由编译器生成的方法。



随之而来的还有大量其他方法。 我们对componentN方法感兴趣,该方法是根据主要构造函数的参数列表中列出的属性生成的。



如果没有data修饰符,则必须手动编写一个将执行相同操作的运算符。





因此,我们有一些componentN方法,它们分解成这样的调用:



本质上,它是几种方法调用之间的语法糖。

我们已经讨论过一些可用性表,实际上,我欺骗了您。 它发生了。 不存在avaiabilityTable ,它不是自然的,但是有一个布尔值矩阵。



不需要其他类:您可以采用布尔值矩阵并将其重命名以更加明显。 这可以使用所谓的typealiastypealias来完成。 不幸的是,我们没有从中获得任何额外的奖励,这只是重命名。 如果采用可用性并将其重命名为布尔值矩阵,则什么都不会改变。 该代码既有效,也将起作用。

让我们看一下老师,这正是这种可访问性,然后谈论他:



我们有一位老师,并使用了可用性方法(您是否还没有失去推理的线索?:-)。 他来自哪里? 也就是说,教师是具有班级的某种实体,这是业务代码。 并且没有其他方法。



由于扩展功能而出现此方法。 我们将一些可以在此类对象上运行的其他函数固定在类上。
如果我们将一些lambda传递给此函数,然后在现有属性上运行它,那么一切都很好-实现中的Availability方法将初始化Availability属性。 您可以摆脱这一点。 我们已经知道了invoke操作符,它可以附加到类型上,并同时是扩展函数。 如果您将lambda传递给此运算符,则就在this关键字上,我们可以运行此lambda。 结果,当我们与老师一起工作时,可访问性是老师的财产,而不是其他任何方法,并且这里没有任何麻烦。



另外,可以为可为空的类型创建扩展函数。 这很好,因为如果存在具有可空类型且包含空值的变量,则我们的函数已经准备就绪,并且不会脱离NullPointer。 在此函数内部,它可以为null,并且需要进行处理。



扩展功能概述:您需要了解仅可以访问该类的公共API,并且该类本身不会进行任何修改。 扩展功能由变量的类型决定,而不是由实际类型决定。 此外,具有相同签名的班级成员将被优先处理。 您可以为一个类创建一个扩展函数,但可以在完全不同的类中编写它,并且在该扩展函数内部可以同时访问两个上下文。 事实证明,上下文是交叉的。 最后,这是一个很好的机会,可以将操作人员普遍带到我们想要的任何地方。



下一个工具是infix函数。 开发商手中的另一把重锤。 为什么危险? 您看到的是代码。 这样的代码可以用Kotlin编写,不要这样做! 请不要这样做。 尽管如此,这种方法还是不错的。 因此,可以消除所有嘈杂的语法中的点,括号,我们正试图从这些语法中尽可能地使代码更简洁。



如何运作? 让我们举一个简单的例子-一个整数变量。 让我们为其创建一个扩展函数,我们将其命名为shouldBeEqual,它将执行某些操作,但这并不有趣。 如果我们在其左侧添加infix修饰符,就足够了。 您可以去除圆点和方括号,但是有一些细微差别。



在此基础上,仅实现并固定了数据和断言构造。


让我们弄清楚。 我们有一个SchedulingContext-调度启动的一般上下文。 有一个数据函数可返回此计划的结果。 同时,我们创建扩展函数和infix函数断言,这将启动一个lambda来检查我们的值。



有一个主题,对象和动作,您需要以某种方式将它们连接起来。 在这种情况下,使用大括号执行数据的结果就是主题。 我们传递给断言方法的lambda是一个对象,而断言方法本身是一个动作。 所有这些似乎结合在一起。



说到函数中缀,重要的是要了解这是摆脱嘈杂语法的一步。 但是,我们必须有此操作的主题和对象,并且需要使用infix修饰符。 您可以理解,可能只有一个参数-即零个参数不能为,两个不能为三个。 例如,您可以将lambda传递给此函数,并以这种方式获得以前从未见过的构造。

让我们继续下一个演示。 最好观看视频,而不要阅读文本。



现在一切都准备就绪:您看到的函数中缀,您看到的函数的扩展,解构声明已准备就绪。

让我们回到演示文稿,这里我们将在构建面向问题的语言时转到一个相当重要的点-您应该考虑的是上下文控制。



在某些情况下,我们可以使用DSL并直接在其中使用它,但是我们不想这样做。 我们的用户(可能是经验不足的用户)将数据写入数据内部,这没有任何意义。 我们想以某种方式禁止他这样做。

在Kotlin 1.1版之前,我们必须执行以下操作:为了响应我们在SchedulingContext有一个数据方法的事实,我们不得不在DataContext创建另一个数据方法,我们接受一个lambda(尽管没有实现),我们应该标记该方法注解@Deprecated并告诉编译器不要对此进行编译。 您会看到此方法启动-不进行编译。 使用这种方法,当我们编写无意义的代码时,我们甚至会得到一些有意义的消息。



在Kotlin 1.1版本之后, @DslMarker了一个很棒的注释@DslMarker 。 需要此注释来标记派生的注释。 反过来,有了它们,我们将标记出面向问题的语言。 对于每种面向问题的语言,您可以创建一个标记@DslMarker注释,并将其挂在所需的每种上下文中。 不再需要编写必须禁止编译的其他方法-所有这些都可以使用。 未编译。

但是,当我们使用业务模型时,就有一种这样的特殊情况。 它通常是用Java编写的。有一个上下文,有一个注释需要标记为上下文。您认为该方法中学生的背景是什么?上课Student这是我们业务模式的一部分,Kotlin不存在。





我们也想以某种方式控制这种情况,因为在这种情况下,可以使用以下设计:在学生内部创建学生。我不想使您建立任何不正确的关联,但是我们要禁止它,这是错误的。



我们有三个选择。

  1. 创建一个对我们的学生负责的整体环境。我们称它为StudentContext。我们描述那里的所有属性,然后在此基础上创建一个学生。有点疯狂-正在编写一堆代码,可能比生产过程还要多。
  2. – , , . . StudentContext , IStudent . , Student, IStudent StudentContext. DslMarker , .
  3. : deprecated . , . , . extension-, . .



因此,即使在此级别上,您也可以控制上下文,但需要规避一些限制。



关于上下文控制的概述。保护您的用户免于错误。显然,用户不会犯一些错误,因为这很明显,但是仍然需要控制它。而且,实施这种控制不需要花费很多金钱和时间。使用@DslMarker批注来标记自己的批注。在无法使用@DslMarker批注的情况下,请使用@Deprecated批注,这将帮助您绕过那些尚无法使用的情况。

因此,上下文控制演示:





缺点和问题


首先,复用DSL部分。今天,您已经看到使用DSL创建的寻址实体可能会出现问题。有多种方法可以解决此问题,但是建议事先考虑一下,以便为此制定计划。

假设您有一段代码,而您只是想重复一段代码,例如,在一个循环中能够创建学生,很多次相同的学生或任何其他实体。怎么做?您可以使用for循环-不是最佳选择。您可以在DSL内部创建其他方法,这将是一个更好的解决方案,但是,您将必须在DSL级别上解决此类问题。请注意this关键字和it参数的默认命名。幸运的是,使用Kotlin版本的1.2.20插件,我们可以在开发环境中直接看到提示。格雷码告诉我们正在使用的上下文或上下文。

嵌套可能是个问题。您已经构建了漂亮的DSL,但是模型的初始化变得越来越深,因此,您经常使用水平滚动而不是垂直滚动。建议在默认实现下隐藏默认值。仅仅需要学生的用户不希望了解任何培训计划,或者只是想创建一个没有详细信息的学生,甚至不希望表示姓名。尝试缩短语法。例如,指定一些默认值,传递一个空的lambda,等等。

最后是文档。以我的主观观点,针对您的面向问题的语言的最佳文档不仅仅是此DSL实例的数量。当您拥有Kotlin基座时,这太好了,这是一个很好的奖励。但是,如果DSL用户不知道可以使用哪些设计,那么他和Kotlin坞站将无处可寻。你有没有经历过?刚开始编写Gradle文件时,您并不了解其中的内容,因此需要一些示例。您不会对任何上下文有任何疑问,您只需要示例,这是DSL新用户可以使用的最佳文档。



请不要将DSL放在所有的裂缝中。当您拥有此工具时,我真的很想这样做。我想说,让我们在这里(也许在这里和这里)创建DSL。首先,这是一项忘恩负义的工作。其次,仍然希望将其应用于目的地。真正帮助您解决问题的地方。
最后,学习Kotlin。探索这种语言带来的新功能的可能性,以使您的代码更简洁,更短,更紧凑,更易于阅读。当您再次返回测试时(例如,您添加了一些东西,需要对其进行测试),您会感到非常高兴,因为DSL尽可能紧凑且舒适,并且创建十二个DSL毫无问题。学生。仅需几行即可完成。

培养“猫”作为一部著名电影的英雄。我认为,起初将Kotlin引入您的项目作为测试会比较容易。这是检查语言,尝试并查看其功能的好机会。在这样的战场上,即使什么也没有用,您仍然可以使用它。
最后,预先设计DSL。今天,我展示了一些完美的例子,我们分阶段开发了一种面向问题的语言。如果您预先设计DSL,它最终将变得容易得多,不会进行10次重塑,也不必担心上下文会以某种方式重叠并且在逻辑上紧密相连。只需预先设计DSL-当您知道我今天告诉您的设计集时,就可以很容易地在一张纸上完成设计。

最后,是沟通联系。我叫Ivan Osipov,电报:@ivan_osipov,Twitter:@_osipov_,Habr:i_osipov。我将等待您的评论。

分钟的广告。 JPoint — , 19-20 - Joker 2018 — Java-. . , .

Source: https://habr.com/ru/post/zh-CN416725/


All Articles