Groovy™ 类型检查器
发布日期:2024-01-20 08:30PM
默认情况下,Groovy 在编译期间只执行最少的静态类型检查。这允许您编写利用语言中非常动态特性的脚本,例如在运行时添加方法。但是许多程序不使用这种动态特性,因此 Groovy 允许您在需要时,通过使用 Groovy 的静态类型检查特性,来增加编译器将执行的类型检查量。
然而,Groovy 不仅仅提供动态/静态(或开/关)类型检查功能,它还提供了类型检查扩展机制。事实证明,在许多情况下,您需要大部分类似 Java 的类型检查,但只需对少量动态行为进行轻微放松。相反,有时您需要比 Java 更强的检查。在这里,我们将介绍后一种情况的两个内置检查器。
-
RegexChecker 在 Groovy 4 中引入。它在使用 Groovy 的正则表达式运算符或调用 Java 正则表达式 API 时执行额外的检查。它会查找非法字符、未闭合的捕获组或范围、非法或未知语法以及许多通常在运行时发生的其他错误。使用该检查器可确保如果您的程序编译成功,您将不会收到运行时错误。
-
FormatStringChecker 在 Groovy 5 的 alpha 版本中可用。它检查各种 printf 和 format 方法。它会查找非法的精度规范、未知的格式转换、类型转换不匹配以及其他错误。使用该检查器时,如果您的程序编译成功,您应该不会收到此类错误。
这两个检查器都依赖于在代码的某个地方提供正则表达式或格式字符串。如果您将此类表达式存储在数据库或属性文件中,或其他地方,那么您就无能为力了。但请记住,该机制是可扩展的,因此只需几行(或可能几十行)代码即可添加更多支持。
一个例子
让我们看一个同时使用这两个检查器的示例。这个示例使用正则表达式从 Groovy 源代码仓库的第一个提交的日志消息中提取一些信息。为简单起见,我们将日志消息复制为字符串,但如果我们想自己从 git 中提取,Groovy 提供了很好的功能。
var firstCommitLog = 'Date: Thu Aug 28 18:48:39 2003 +0000' // (1)
@TypeChecked(extensions = 'groovy.typecheckers.RegexChecker') // (2)
def getMatcher(String text) {
text =~ /Date:\s*(\w{3})\s(\w{3})\s(\d{2})\s(\d{2}):(\d{2}):(\d{2})\s(\d{4})/
}
@TypeChecked(extensions = 'groovy.typecheckers.FormatStringChecker') // (3)
def displayInfo(String day, boolean afternoon, BigDecimal numYears) {
printf 'Day: %s, Afternoon: %x, Years ago: %3.1f', day, afternoon, numYears
}
var (_, day, month, date, hour, _, _, year) = getMatcher(firstCommitLog)[0] // (4)
var afternoon = hour.toInteger() >= 12
int monthNumber = DateTimeFormatter.ofPattern('MMM').parse(month)[MONTH_OF_YEAR]
var now = LocalDate.now()
var firstCommitDate = LocalDate.of(year.toInteger(), monthNumber, date.toInteger())
var numYears = (now - firstCommitDate) / 365 // (5)
displayInfo(day, afternoon, numYears)
-
第一个 git 提交消息中的一行
-
一个带有额外正则表达式检查的方法
-
一个带有额外格式字符串检查的方法
-
从第一次匹配中提取组件
-
计算自第一次提交以来的年数(浮点数)
当我们运行此脚本时,我们得到以下输出
Day: Thu, Afternoon: true, Years ago: 20.4
因此,我们知道第一次提交发生在 20 多年前的一个周四下午/晚上。
在这里,我们用 TypeChecked
注解了这两个相关方法,并明确列出了我们希望在每种情况下使用的 extensions
。Groovy 提供了编程的编译器配置和配置脚本,可能可以隐藏这些细节,但我们这里不再深入探讨。
让我们看看如果我们在脚本中犯了错误,会看到的一些编译时错误。
让我们在表达式的开头放置一个意外的闭合分组括号
[Static type checking] - Bad regex: Unmatched closing ')' )Date:\s*(\w{3})\s(\w{3})\s(\d{2})\s(\d{2}):(\d{2}):(\d{2})\s(\d{4})
或者在第一次重复时遗漏了闭合大括号
[Static type checking] - Bad regex: Unclosed counted closure near index 13 Date:\s*(\w{3)\s(\w{3})\s(\d{2})\s(\d{2}):(\d{2}):(\d{2})\s(\d{4}) ^
或者使用了错误的转义序列
[Static type checking] - Bad regex: Illegal/unsupported escape sequence near index 6 Date:\g*(\w{3})\s(\w{3})\s(\d{2})\s(\d{2}):(\d{2}):(\d{2})\s(\d{4}) ^
同样,我们可能会在显示收集到的信息时犯错。我们可能会弄错类型,例如这里将最后两个参数的顺序混淆了
[Static type checking] - IllegalFormatConversion: f != java.lang.Boolean printf 'Day: %s, Afternoon: %b, Years ago: %3.1f', day, numYears, afternoon
或者可能完全弄错了转换字符,例如使用 %y
表示年份,但没有这样的转换字符
[Static type checking] - UnknownFormatConversion: Conversion = 'y' printf 'Day: %s, Afternoon: %b, Years ago: %y', day, afternoon, numYears
或者可能要求布尔值用 0 填充
[Static type checking] - FormatFlagsConversionMismatch: Conversion = b, Flags = '0' printf 'Day: %s, Afternoon: %0b, Years ago: %3.1f', day, afternoon, numYears
查看文档以了解更多大约 30 种已检测到的错误类别。
结论
正则表达式和格式字符串是小众活动,因此您可能不经常需要此类检查。但是,您不总是使用这些功能的事实增加了犯错的可能性。拥有这些额外的检查器可以让您安心。请记住,这些只是使用 Groovy 可扩展类型检查的示例。为什么不为自己的场景添加额外的检查呢!玩得开心!