干货 | 实现一个属于你的“语言”-携程Kotlin DSL开发与实践

2019-09-16 18:12:19 浏览数 (1)

每一个DSL,都是一定意义上专有的语言,这篇文章希望能够用浅显易懂的方式,将Kotlin DSL的应用与实践经验分享给大家。希望对你有所启发,能够构建一门属于自己的专有“语言”。

一、简介

DSL(domain specific language),即领域专用语言:专门解决某一特定问题的计算机语言。由于它是以简洁的形式进行表达,整体上直观易懂,使得调用代码和读代码的成本都得以降低,即使是不懂编程语言的一般人都可以进行使用,所以近年来频频被提起,颇受关注。

DSL分为外部DSL和内部DSL。

DSL:在主程序设计语言之外,用一种单独的语言表示领域专有语言。可以是定制语法,或者遵循另外一种语法,如XML、JSON。

内部DSL:通常是基于通用编程语言实现,具有特定的风格,如 iOS 的依赖管理组件 CocoaPods 和 Android 的主流编译工具 Gradle。

这里主要分享在Kotlin中构建使用DSL。

二、应用

Kotlin DSL的应用广泛,包括gradle编写、编写js、html、SQL等。下面列举几个使用场景:

2.1 Trip.com支付网络封装实践

在编写网络代码时,出现频率最高的就是request配置和大篇幅的response回调处理,那么这两部分的代码该如何优化?在Trip.com支付中利用kotlin DSL对网络进行二次封装,针对以上问题进行解决。

定义request配置,使得最终在做request配置时更为简洁:

代码语言:javascript复制

定义回调模版,解决以下问题:部分网络请求,我们不关心结果,或者不关心onFailed的场景,避免掉这部分的冗余代码:

代码语言:javascript复制

预定义扩展函数

代码语言:javascript复制

最终调用

代码语言:javascript复制

在定义DSL的过程中需要权衡冗余度、自由度、可扩展性。上面给出的伪代码消除了重复的模版代码,减少代码冗余,同时也做到自由选择配置项,有一定的自由度和可扩展性。

2.2 海外支付SDK DSL构建项目实践

众所周知Android studio中是使用groovy编写gradle脚本,而groovy由于是动态语言,不可避免的存在一个问题,就是代码提示不够智能,我们在使用groovy时往往需要配合文档进行编写;而kotlin是一种静态语言,使用它编写gradle脚本则可以有比较好的智能提示体验。

在Gradle5.0中,官方提供可以选择在项目中生成Groovy或者kotlin DSL构建脚本,并进一步的优化代码自动完成、重构和其他 IDE 辅助功能,为使用Kotlin DSL的 IDE 用户带来了极大的便利。

可见gradle官方也在努力将kotlin DSL推向大家视野中。

在我们最近的海外支付SDK中,采用该种方式构建项目, 部分gradle代码如下:

代码语言:javascript复制

可以看到使用kotlin编写和groovy编写区别不大,所以即使我们要将现有工程中的groovy脚本重写为kotlin脚本,工作量也不会过大。

以上种种都表明Kotlin DSL相对于groovy的优势非常明显,那么我们是不是应该立马开始改造现有的项目?

答案是“否”,因为它目前存在一个致命的缺陷,在首次编译项目时比groovy DSL慢很多,大项目中这一点会被放大,所以大家在上手之前需要慎重权衡利弊。

目前我们在海外支付SDK中利用kotlin DSL构建大约在17s,利用groovy DSL构建大约在16s,时间上来说几乎没有区别,所以小型项目推荐尝试使用!

相信在不久的未来kotlin DSL可以解决这个问题,那么利用kotlin DSL构建项目势必会成为趋势。

代码语言:javascript复制

2.3 Anko

Anko库包括Anko Commons、Anko Layouts、Anko SQLite、Anko Coroutines,这些都是使用kotlin DSL编写,这里主要介绍Anko Layouts。

在写Android布局时,我们都习惯性的使用XML进行编写,但是可以考虑丢下冗长的XML写法,尝试使用Anko Layout来实现。

XML写法:

代码语言:javascript复制

Anko Layout写法:

代码语言:javascript复制

实际上前文提到过,XML本质上也是一种DSL,但是明显使用Anko Layout风格更加简单、也更加灵活。

XML编写后,我们需要findViewById找到控件,再对控件进行操作、赋值;Anko Layout编写过程中,可以在布局中就直接做显示隐藏、赋值操作等,同时这种写法也有类型安全、空安全、代码复用性强的优势。

Anko Layout由于是直接在kt文件中编写控件,那么它相对于xml来说,还有一个优势,即:减少了XML格式的解析过程,从而实现CPU资源和电量的节省。

XML的执行流程:

Anko Layout执行流程:

Anko库实际上是用kotlin对相关类做了一层扩展包装,基于这一点,它的局限性也体现在于会增加包大小,在使用之前可以根据项目评估一下是否适合引入Anko库。

2.4、创建一个自己的DSL

Kotlin DSl的优势这么多,那么如何自定义一个DSL?

kotlin的扩展函数、高阶函数、lambda表达式、中缀调用、invoke 约定和函数小括号省略等特性,使得Kotlin编写DSL尤为顺畅,我们可以使用这些特性来实现自己的“领域特定语言”。这里给一个简单的示例:

定义Trip、Department类

代码语言:javascript复制
data class Trip(var name: String? = "", var address: String? = "", var departments: List<Department>? = mutableListOf(), var city: List<String>? = mutableListOf(), var culture: String? = "")
代码语言:javascript复制

定义中间类,主要是为了实现直接DSL方式添加department的效果

代码语言:javascript复制

创建trip的DSL写法

代码语言:javascript复制
代码语言:javascript复制

最终调用效果:

代码语言:javascript复制

result结果:

代码语言:javascript复制
Trip(name=Trip, address=上海市长宁区金钟路968号凌空SOHO, departments=[Department(name=机票, nameEn=flight), Department(name=酒店, nameEn=hotel), Department(name=火车票, nameEn=train)], culture=Customer、Teamwork、Respect、Integrity、Partner)
代码语言:javascript复制

一个简单的Kotlin DSL就这样实现了,通过封装成结构化的 API 达到了直观易懂、最终调用时代码量减少的效果。即使是一个非kotlin开发人员也可以理解以上格式的含义,完成“Trip”对象的配置使用。

三、写在最后

1)Kotlin编写完的DSL整体简洁直观,调用代码和读代码的成本都得以降低,在生产项目中可以稳定使用。

2)DSL是通过简化语言中的元素,降低使用者的负担,使用者需要按照既定的规范进行编写。所以我们需要提供完善使用文档,以保证接入者学习成本降低。

3)在我们编写的DSL应用范围越来越大时,已有DSL往往满足不了现有的需求,我们仍然需要对DSL进行补充,所以在定义自己的DSL时需要评估后期开发维护效率,注意其可扩展性。

0 人点赞