原文地址:towardsdatascience.com/kotlin-the-…
原文作者:hinchman-amanda.medium.com/
发布时间:2018年10月11日-6分钟阅读
今天,我为我在KotlinConf上关于Kotlin、TornadoFX和元编程的演讲写了一个后续内容。在讲座中,我得出了交叉切割的一般化定义,并探讨了元编程的常见形式。我们探索了用Kotlin编写的TornadoFX功能,以研究该语言的功能特性。现在回想起来,我希望自己能更专注于谈论我的应用研究,所以我在这里谈一谈我走过的路和我要去的地方!
简要回顾一下
有三种常见的元编程形式。
- 向导--也被称为 "猴子打补丁",向导通常是用户生成的输入,它只限于HTML或XML等标记语言。Android Studio的设计功能就是向导的一个很好的例子。向导对用户的输入容易出错,使得它成为元编程的最低形式。
- 面向方面的编程(Aspect-Oriented Programming)--AOP是一个想法,在这个想法中,你把关注点拉出来,进入一个更高的抽象层次,以便把其他关注点编织成一个更兼容的共存体。AOP通常仅限于报告元数据,缺乏动态属性。
- 特定领域语言--传统的DSL是一种完成特定任务的语言,但放弃了与该特定类无关的东西。你可以用SQL操作数据集,但你不能用它们创建整个应用程序。由于DSL通常与Java、C、Kotlin等通用语言不同,所以结果是DSL通常存储在不同的文件或字符串字面中,造成大量的开销。
巧合的是,Kotlin包含了这三种形式的能力,甚至改进了目前每种类型作为功能范式的不足之处。通过内部DSL和重构泛型,Kotlin找到了一种方法来克服JVM堆栈中的限制,这些限制会阻止函数等功能支柱成为一流的公民。
Kotlin在函数式和面向对象的范式之间转换,但作为一种静态类型的语言,在试图完成真正的元编程方面存在一些已确定的挑战,而其他静态类型的语言是无法完成的。
- 真正的元编程并不尊重封装。
- 真正的元编程产生的代码不需要编译,所以没有类型检查。
说到这里,我尝试了一些我不太确定是真的、真的、勇敢还是真的、真的、愚蠢的事情。
毕竟,它可能并不愚蠢。
开发中的测试
软件质量保证包括监测用于确保质量的软件工程过程和方法的一种手段。当然,这个过程的一部分是为写好的代码编写测试,并确保产品从用户的角度按照预期工作。
当我意识到我需要UI测试时,我有点惭愧,我自己从来没有接受过JUnit测试的培训。但后来我也意识到,还有很多其他的工程师也在回避测试,而且在实践中,很多策略最终会比回归测试的帮助更麻烦。
大约一年前,我为销售人员写了一个程序化的向导,为潜在的客户生成现场演示,因为,我很懒。巧合的是,这个项目是一种猴子修补的形式!
TornadoFX-Dnd-TilesFX在这里是开源的
我最后写了自己的拖放功能,因为我不知道如何序列化我的自定义对象;问题是我很难调试我的微观管理事件,而且我意识到我需要测试用户界面。另一个问题?我不知道如何写UI测试。我想--如果我用元编程来为我生成这些测试会怎么样?
于是我开始询问其他的开发者和QA工程师。
- 什么是一个好的测试?
- 什么是坏的测试?
我收到了数以百计的答案--从不同的框架到不同的策略到不同的风格。
TornadoFX-Suite。不仅仅是应用研究
TornadoFX-Suite一开始只是生成TornadoFX UI测试。但它的意义不止于此。如果这个项目可以被多个人用于多个框架--我们是否可以收集这些数据并使用机器学习来找到这些答案?
让我们看看这个项目现在的样子,看看它的作用。
TornadoFX-Suite是开源的,可以在这里找到
你会注意到,这个应用程序检测UI输入--从那里,kscripting将被实施以生成这些测试。
检测UI输入
这是该项目迄今为止最难克服的障碍。使用kastree(Kotlin编译器的一个包装器),我能够将我扫描的Kotlin文件分解成抽象语法树(AST)。我使用AST的原因是,我们能够保持代码分析的不可知性,以便在未来框架中使用。在创建所需的映射时,我遇到了一个现实生活中的元编程挑战--在语法分析中,当你的静态系统关心你必须铸造的类型时,很难递归地映射潜在的无限树(包括高度和广度)。
最后遇到了一个已确定的挑战,真正的元编程和静态类型语言(如@kotlin)不能一起玩--能够递归地检查AST分解而不关心类型系统。
特别是,为Kotlin语言分解属性被证明是困难的。一个属性可能是一个集合,一个变量,一个独立于类的函数,或者一个成员。这些项目中的每一个都可能在访问级别、类型上有进一步的分类,并可能在这些属性中包含额外的属性。
我并不是说这是最好的解决方案。我甚至没有说这是一个好的方案。但我找到了一个! 将这些AST翻译成JSON对象/数组,使铸造明显更容易以一种更不可知的方式进行递归。
private fun detectLambdaControls(node: JsonObject, className: String) {
val root = node.get("expr")
if (root.asJsonObject.get("lambda") != null) {
val rootName = root.asJsonObject
.get("expr").asJsonObject
.get("name").asString
// TornadoFX specific
addControls<INPUTS>(rootName, className)
// get elements in lambda
val lambda = root.asJsonObject.get("lambda").asJsonObject
val elements: JsonArray = lambda.get("func").asJsonObject
.get("block").asJsonObject
.get("stmts") as JsonArray
elements.forEach {
detectLambdaControls(it.asJsonObject, className)
}
}
}
性能差吗?当然是的。这个实现很糟糕吗?是的,它是。但它有用吗?如果它能工作,那就不傻。
因此,我们有这些细分。我怎么知道我应该关心哪些元素?这真的很容易用Kotlin枚举类来定制每个框架。
enum class INPUTS {
Form, TextField, DateField, Button, Action,
RadioButton, ToggleButton, ComboButton, Checkbox,
Item, Paginator, PasswordField
}
好了,我们有需要注意的关键字。我们可以用我们的枚举类来循环浏览我们的控件属性集合。
private inline fun <reified T : Enum<T>> addControls(control: String, className: String) {
enumValues<T>().forEach { it ->
// if a control is a control we care about
if (control == (it.name)) {
if (detectedViewControls.containsKey(className)) {
... // add control to existing class collection
} else {
... // create a new class collection and add control to it
}
}
}
}
这是POC,我们可以在任何框架中检测所需的控件。我们正在努力创建一个不可知的解析系统,最终,当它长大到足以处理这样的责任时,让它存在于一个自己的库中。
下一步是什么?
行动是影响环境的东西。
这就是测试的作用。
测试是一个探索环境的积极过程,确定它对各种条件的反应。
测试人员从这些相互作用中学习。
因此,如果我们谈论的是一个 "做测试 "的生成性机器学习工具,根据定义,它必须学习。它必须通过采取行动并能够处理来自这些行动的反馈来做到这一点。我们可以从创建生成的用户输入的排列组合开始,这些排列组合可以覆盖人类可能永远没有能力覆盖的情况,但这样我们就会增加测试许多东西的时间。如果我们把这些排列组合缩短为有价值的测试呢?
从元编程中自然发展出来的是机器学习。
我们可以从用户那里收集数据--什么测试是为什么产生的,什么测试最初通过/失败了,有多少用户为项目做出了贡献?从那里,可能有可能检测出已知有问题的组件,并为其提供更智能的测试。我们甚至可以找到一些数据,表明什么才是好的UI/UX。我甚至认为我在这里还没有触及到表面。
你有兴趣做出贡献吗?我经常在Kotlin Slack上寒暄,当然,我也可以在twitter和github上发言。该项目可以在这里找到。
通过www.DeepL.com/Translator(免费版)翻译