JSpecify 发布首个版本 JSpecify 1.0.0,其使命是为 JVM 语言定义一组通用的注解类型,用于改善静态分析和语言互操作性。
达成共识的项目和团对包括:
OpenJDK
EISOP
PMD
Android、Error Prone、Guava(谷歌)
Kotlin、IntelliJ(JetBrains)
Azure SDK(微软)
Sonar
Spring
参与 JSpecify 的成员们承诺,他们的项目将在源代码层面保持向后兼容性。这意味着他们不会重命名 jar 包中的注解、移动它们或做出其他导致更新后代码编译失败的变更。换句话说,依赖 JSpecify 是安全的,它不会以任何破坏应用程序代码的方式做出变更。
这个首发版本专注于引入类型使用注解,用于指明静态类型的空值状态。主要用途预计将集中在变量、参数和返回值上,也适用于任何需要在概念上追踪空值情况的场景。
然而,某些应用场景可能未通过“概念意义”验证——例如,你仍然无法实现类似 Foo extends @Nullable Bar 这样的语法。
空值问题与空值检查器的相关背景
空值问题一直是 Java 生态系统中的一个备受争议的话题。开发者应该尽可能免受 NPE 的影响,因为目前可能导致 NPE 的大量用例应该在构建时被排除。
然而,这个充满希望的理念在实践中遭遇了一些挑战,尤其是由于 Java 现有的一些限制:
由来已久,在很大程度上早于编程语言中的非空值概念。
对向后兼容性的承诺。
因此,要以一种既一致又不会破坏现有代码的方式将空值意识融入到编程语言中变成了一项极为艰巨的挑战。
尽管如此,还是有人做过尝试——最著名的可能是 JSR 305,它持续了好几年,但最终在 2012 年进入休眠状态,未能实现全面标准化的目标。
在此之后,很多公司和项目继续开发并使用他们自己的空值注解实现。其中一些项目主要是为了解决他们自己的用例,而其他一些则致力于提供更广泛的适用于 Java 应用程序的解决方案。
值得一提的是 Kotlin,它将空值意识直接融入到类型系统中。Kotlin 做了一些假设,例如,String 表示一个值不能为 null,如果要表示可空值,开发者需要明确使用 String?。这在很大程度上取得了成功,至少只要程序是用纯 Kotlin 编写的。
Java 开发者有时候也会表达他们“对 Kotlin 空值处理功能的渴望”,但正如 Kotlin 文档明确指出的那样,这里有许多微妙之处,特别是当与 Java 代码互操作时。
目前,在纯 Java 领域,大多数项目在处理空值问题时都是将空值状态视为静态分析工具(构建时检查器)处理的附加信息。通过添加额外的注解让检查器减少或消除在运行时可能出现的 NPE。
不同项目对应该提供多大程度的“健壮性保证”有着不同的理念。健壮性并非没有成本——它可能导致更复杂的代码、更大的迁移工作量以及需要处理的误报数量增加,所以不是每个开发者都追求全面而彻底的健壮性保证。
JSpecify 标志着一种统一各种方法的尝试,初始目标相对谦逊——1.0.0 版本只定义了四个注解:
@Nullable
@NonNull
@NullMarked
@NullUnmarked
前两个可用于特定的类型使用(例如参数定义或方法返回值),后两个则有更广泛的应用范围。它们为 Java 类型系统提供了一种声明式的空值意识。
一个常见的用例是在你需要添加空值信息的模块、包或类型声明上使用 @NullMarked 注解。这可以大大减少工作量,因为它指明所有未明确标注的类型使用都是非空的。然后,需要可空值的特殊情形可以明确地标注为 @Nullable。
目的是让最终用户采用(或继续使用)其中一个参与 JSpecify 的项目——不同的项目有不同的成熟度。例如,IntelliJ 支持 JSpecify 注解,但还不完全支持泛型。
还有一个参考检查器可供使用,但它主要是为参与 JSpecify 的项目设计的,不是直接面向应用程序开发者。
还有一个问答深入探讨了更深层次的设计和技术细节。
InfoQ 采访了 JSpecify 的关键开发者之一——Juergen Hoeller(Spring Framework 项目负责人)。
InfoQ:请介绍一下你在 JSpecify 项目中参与空值处理的情况。
Hoeller:我和 Sebastien Deleuze 一起设计 Spring Framework 5 中的空值处理,因此参与了 JSpecify。
InfoQ:你能否介绍一下 JSpecify 在你的项目中的应用方式,以及它对项目的重要性体现在哪些方面?
Hoeller:Spring Framework 及其产品组合中的很多项目在 2017 年采用了默认非空的设计原则,这在很大程度上是受到了 Kotlin 的启发,但也为基于 Java 的项目(结合 IntelliJ IDEA 和像 NullAway 这样的工具)提供了显著的好处。
这在我们的公共 API 中有所体现,应用程序开发者可以推敲参数和返回值的空值状态。它也深入到我们的内部代码库中,帮助我们验证空值假设和断言是否与实际代码流程保持一致。这对我们自身的内部设计和维护工作带来了极大的益处。
虽然 JSR 305 注解在工具中得到了广泛的应用,但从未正式完成。Spring 自己的注解使用 JSR 305 作为元注解,以提高工具的可发现性,从而迅速获得了 IntelliJ IDEA 和 Kotlin 的支持。
我们参与 JSpecify 源于我们对现有注解的不满,我们希望为注解语义和工具支持提供一个战略性的路径,让我们能够在遵循严格空安全性的同时演进我们的 API 和代码库。
InfoQ:发布 1.0.0 版本花了 6 年时间。你认为是什么导致花了这么长时间?
Hoeller:尽管在 Spring 中,我们对 JSpecify 的应用主要集中在参数、返回值以及字段值上,看起来相当直接,但整个问题域本身是相当复杂的。JSpecify 的目标是支持泛型,这被证明是一个特别棘手的任务。JSpecify 1.0 考虑到了众多利益相关者的要求,要达成广泛共识并不容易。
InfoQ:如果 JSpecify 取得成功,未来会怎样发展?
Hoeller:首先,JSpecify 完全有能力取代 JSR 305,成为工具以及开源框架和库通用注解的新标准。在 JDK 17 等当前广泛使用的基线上获得这样的支持至关重要,因为开源生态系统的大部分将在未来的许多年里继续依赖这样的基线。即使在接下来的几年中使用更新版本的 Java 和 Kotlin,基于 JDK 17 的框架和库仍将被广泛使用,JSpecify 可以随着它们的需求而演进。
InfoQ:JSpecify 接下来需要解决哪些问题?
Hoeller:从 Spring 的角度来看,我们迫切需要官方对元注解的支持,以便我们能够使用 JSpecify 的元注解而非 JSR 305 元注解来声明我们自己的空值注解。
InfoQ:感谢接受采访!
原文链接:
https://www.infoq.com/news/2024/08/jspecify-java-nullability/
评论 1 条评论