JetBrains 的@Contract 注解
JetBrains' @Contract annotation
org.jetbrains.annotations.Contract
注释是如何工作的?
IntelliJ IDEA 如何支持它?
首先,我应该说这个注解只是给IDEA用来检查可能的错误。 Java 编译器几乎会完全忽略它(它会出现在已编译的工件中,但没有任何效果)。话虽如此...
注释的目的是描述方法将遵守的契约,这有助于 IDEA 捕获可能调用此方法的方法中的问题。所讨论的合同是一组 semi-colon 分开的条款,每个条款都描述了一个输入和一个保证发生的输出。因果由 ->
分隔,并描述当您向方法提供 X 时,Y 将 总是 结果的情况。输入被描述为一个comma-separated列表,描述了多个输入的情况。
可能的输入是 _
(任何值)、null
、!null
(not-null)、false
和 true
,以及可能的输出将 fail
添加到此列表。
因此,例如,null -> false
表示,提供 null
输入,结果是 false
布尔值。 null -> false; !null -> true
对此进行扩展说 null
将 始终 return false
而非 null
值将 always return true 等。最后,null -> fail
表示如果您向该方法传递空值,该方法将抛出异常。
以multiple-argument为例,null, !null -> fail
表示在two-argument方法中,如果第一个参数为null,第二个参数不为null,则抛出异常,保证。
如果该方法不改变对象的状态,而只是 return 一个新值,那么您应该将 pure
设置为 true。
注释的所有支持和识别值的 official documentation specifies the formal grammar。
通俗地说:
- 一份合同可以有 1 个或多个与之关联的条款
- 子句是总是
[args] -> [effect]
- args是1个或多个约束条件,定义为
any | null | !null | false | true
- 效果只是一种约束或
fail
让我们 运行 通过一个简单的例子 - 我最喜欢的例子之一是 "Whatever you pass into this method, it will throw an exception."
@Contract("_-> fail")
public void alwaysBreak(Object o) {
throw new IllegalArgumentException();
}
在这里,我们使用 _
或 "any" 来表示无论我们将什么传递给此方法,我们都将抛出异常。
如果我们撒谎说这个方法将无条件地 return true
怎么办?
@Contract("_-> true")
public void alwaysBreak(Object o) {
throw new IllegalArgumentException();
}
IntelliJ 对此提出了一些警告。
当我们处于 void[=65= 时,我们说我们在 returning 一个布尔值也很(显然)不高兴] 方法...
您会发现自己想要使用 @Contract
的主要时间是:
- 你要保证你return是真还是假
- 您想保证您 return 一个 non-null 给定约束的值
- 您想明确表示您可以 return 给定约束
空值
- 您想明确表示您将在给定约束条件下抛出异常
这并不是说 @Contract
是完美的;离得很远。它不能在某些上下文中进行非常深入的分析,但是在您的代码库中使用它可以让您的工具免费进行此类分析。
How does the org.jetbrains.annotations.Contract
annotation work?
虽然前面的答案提供了信息,但我认为它们没有解决您问题中的操作词 "work"。也就是说,他们没有解释 IntelliJ 在幕后做了什么来实现他们的注释,以便您可以从头开始轻松构建自己的注释。
如果您浏览下面的源代码,您可能会认为对于像 @NotNull
这样听起来简单的东西来说,它似乎有点过于复杂(或者至少是冗长)。我同意你的看法,这也是我通常避免使用 @Contract
类子句的原因之一,这些子句不是 "plain and simple"(例如 @NotNull
),而是直接 JavaDoc my prereqs。
我 通常 不鼓励使用复杂的合同注释——尽管我可能会因为跳过这趟时髦的新列车而受到讨厌——这里有一些原因为什么:
- 注释可以很复杂——例如在它们自己的定义中有多个嵌套注释 and/or a "grammar",外观为 Turing completeness。通过掩盖 obscurity/abstraction 层背后错误的真正罪魁祸首并且未能生成最初预期的警告,这可能会导致在编译时产生错误的信心。
- 与我之前的观点相似但不同的是,注解通常会在少量关键字中向开发人员隐藏大量逻辑,从而产生人类难以理解的代码and/or 不寻常难以调试的行为。
- 应用程序配置通常是 seen masquerading as annotations. Have a look at the Spring framework。
- 定义契约的语法大体上(恕我直言)非常难看,而且很像 Makefile。例如,看一眼 JetBrains 的一些注释定义和支持文件 scattered across its repo。注意到众多的 XML 文件和大量的自引用了吗?我很难称之为编写和支持的乐趣,特别是考虑到注释在 Android 和更大的 Java 社区之间来回引领的不断发展的性质。
需要考虑的一些问题:
- 当注释的源代码接近它所注释的源代码的复杂性时,它是否走得太远了?
- 下面的代码段真的比在运行时检查 null 并使用堆栈跟踪记录异常好得多吗?包括类似的东西会迫使您的用户通读、理解并可能修复定义您的注释的另一组依赖项。
从 yet another lengthy post 中借用的关于合同语义的内容我认为只会进一步服务于我的观点:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.annotation.Nonnull;
import javax.annotation.meta.TypeQualifierDefault;
/**
* This annotation can be applied to a package, class or method to indicate that the class fields,
* method return types and parameters in that element are not null by default unless there is: <ul>
* <li>An explicit nullness annotation <li>The method overrides a method in a superclass (in which
* case the annotation of the corresponding parameter in the superclass applies) <li> there is a
* default parameter annotation applied to a more tightly nested element. </ul>
* <p/>
* @see
*/
@Documented
@Nonnull
@TypeQualifierDefault(
{
ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR,
ElementType.FIELD,
ElementType.LOCAL_VARIABLE,
ElementType.METHOD,
ElementType.PACKAGE,
ElementType.PARAMETER,
ElementType.TYPE
})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNullByDefault
{
}
你应该在什么时候使用什么合同?
我建议坚持使用其意图 crystal - 从他们的名字中可以清楚地看出的那种,并避免那些有自己的一套语义和类似语言定义的。
一个要使用的例子——尽管有前一段——是@NotNull
,但将其限制在所有对象参数必须为空的情况下。
要避免的排序示例 是 Android 和 IntelliJ 的 @Contract(...)
。虽然我确实喜欢他们的 IDE,但他们的注释细节非常复杂,最终变成了更多问题和平台不兼容的根源,让我追查(不可否认,几乎 100% 的时间是由于我自己在编写新合同时的无知,但为什么做对这么难?)
总结/结论
注释是一个很好的想法,显然是由查看 "codify" 他们的文档的程序员产生的。我觉得他们最近做得太过分了,将文档变成了带有语义的代码,这导致了一些严重的难题和尴尬的情况。更糟糕的是,他们有时无法检测到他们自己的实现中出现的问题,从而给人一种错误的编译时安全感。坚持非常简单,避免任何看起来像不是 Java 的语言(这是您最初打算编写的语言)。
进一步阅读
这个简短的列表混合了来自 Whosebug 和网络的最重要的 (w/optimism!) 来源,我认为它们说明了我的一些观点。
排名不分先后:
- Java Annotations are a Big Mistake
- Annotations, Annotations, Everywhere
- Arguments against annotations
- Are annotations bad?
- An Annotation Nightmare
- Evil Annotations
毕竟,我才意识到我可能仍然未能完整地解决您最初的问题:)
org.jetbrains.annotations.Contract
注释是如何工作的?
IntelliJ IDEA 如何支持它?
首先,我应该说这个注解只是给IDEA用来检查可能的错误。 Java 编译器几乎会完全忽略它(它会出现在已编译的工件中,但没有任何效果)。话虽如此...
注释的目的是描述方法将遵守的契约,这有助于 IDEA 捕获可能调用此方法的方法中的问题。所讨论的合同是一组 semi-colon 分开的条款,每个条款都描述了一个输入和一个保证发生的输出。因果由 ->
分隔,并描述当您向方法提供 X 时,Y 将 总是 结果的情况。输入被描述为一个comma-separated列表,描述了多个输入的情况。
可能的输入是 _
(任何值)、null
、!null
(not-null)、false
和 true
,以及可能的输出将 fail
添加到此列表。
因此,例如,null -> false
表示,提供 null
输入,结果是 false
布尔值。 null -> false; !null -> true
对此进行扩展说 null
将 始终 return false
而非 null
值将 always return true 等。最后,null -> fail
表示如果您向该方法传递空值,该方法将抛出异常。
以multiple-argument为例,null, !null -> fail
表示在two-argument方法中,如果第一个参数为null,第二个参数不为null,则抛出异常,保证。
如果该方法不改变对象的状态,而只是 return 一个新值,那么您应该将 pure
设置为 true。
注释的所有支持和识别值的 official documentation specifies the formal grammar。
通俗地说:
- 一份合同可以有 1 个或多个与之关联的条款
- 子句是总是
[args] -> [effect]
- args是1个或多个约束条件,定义为
any | null | !null | false | true
- 效果只是一种约束或
fail
让我们 运行 通过一个简单的例子 - 我最喜欢的例子之一是 "Whatever you pass into this method, it will throw an exception."
@Contract("_-> fail")
public void alwaysBreak(Object o) {
throw new IllegalArgumentException();
}
在这里,我们使用 _
或 "any" 来表示无论我们将什么传递给此方法,我们都将抛出异常。
如果我们撒谎说这个方法将无条件地 return true
怎么办?
@Contract("_-> true")
public void alwaysBreak(Object o) {
throw new IllegalArgumentException();
}
IntelliJ 对此提出了一些警告。
当我们处于 void[=65= 时,我们说我们在 returning 一个布尔值也很(显然)不高兴] 方法...
您会发现自己想要使用 @Contract
的主要时间是:
- 你要保证你return是真还是假
- 您想保证您 return 一个 non-null 给定约束的值
- 您想明确表示您可以 return 给定约束 空值
- 您想明确表示您将在给定约束条件下抛出异常
这并不是说 @Contract
是完美的;离得很远。它不能在某些上下文中进行非常深入的分析,但是在您的代码库中使用它可以让您的工具免费进行此类分析。
How does the
org.jetbrains.annotations.Contract
annotation work?
虽然前面的答案提供了信息,但我认为它们没有解决您问题中的操作词 "work"。也就是说,他们没有解释 IntelliJ 在幕后做了什么来实现他们的注释,以便您可以从头开始轻松构建自己的注释。
如果您浏览下面的源代码,您可能会认为对于像 @NotNull
这样听起来简单的东西来说,它似乎有点过于复杂(或者至少是冗长)。我同意你的看法,这也是我通常避免使用 @Contract
类子句的原因之一,这些子句不是 "plain and simple"(例如 @NotNull
),而是直接 JavaDoc my prereqs。
我 通常 不鼓励使用复杂的合同注释——尽管我可能会因为跳过这趟时髦的新列车而受到讨厌——这里有一些原因为什么:
- 注释可以很复杂——例如在它们自己的定义中有多个嵌套注释 and/or a "grammar",外观为 Turing completeness。通过掩盖 obscurity/abstraction 层背后错误的真正罪魁祸首并且未能生成最初预期的警告,这可能会导致在编译时产生错误的信心。
- 与我之前的观点相似但不同的是,注解通常会在少量关键字中向开发人员隐藏大量逻辑,从而产生人类难以理解的代码and/or 不寻常难以调试的行为。
- 应用程序配置通常是 seen masquerading as annotations. Have a look at the Spring framework。
- 定义契约的语法大体上(恕我直言)非常难看,而且很像 Makefile。例如,看一眼 JetBrains 的一些注释定义和支持文件 scattered across its repo。注意到众多的 XML 文件和大量的自引用了吗?我很难称之为编写和支持的乐趣,特别是考虑到注释在 Android 和更大的 Java 社区之间来回引领的不断发展的性质。
需要考虑的一些问题:
- 当注释的源代码接近它所注释的源代码的复杂性时,它是否走得太远了?
- 下面的代码段真的比在运行时检查 null 并使用堆栈跟踪记录异常好得多吗?包括类似的东西会迫使您的用户通读、理解并可能修复定义您的注释的另一组依赖项。
从 yet another lengthy post 中借用的关于合同语义的内容我认为只会进一步服务于我的观点:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.annotation.Nonnull;
import javax.annotation.meta.TypeQualifierDefault;
/**
* This annotation can be applied to a package, class or method to indicate that the class fields,
* method return types and parameters in that element are not null by default unless there is: <ul>
* <li>An explicit nullness annotation <li>The method overrides a method in a superclass (in which
* case the annotation of the corresponding parameter in the superclass applies) <li> there is a
* default parameter annotation applied to a more tightly nested element. </ul>
* <p/>
* @see
*/
@Documented
@Nonnull
@TypeQualifierDefault(
{
ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR,
ElementType.FIELD,
ElementType.LOCAL_VARIABLE,
ElementType.METHOD,
ElementType.PACKAGE,
ElementType.PARAMETER,
ElementType.TYPE
})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNullByDefault
{
}
你应该在什么时候使用什么合同?
我建议坚持使用其意图 crystal - 从他们的名字中可以清楚地看出的那种,并避免那些有自己的一套语义和类似语言定义的。
一个要使用的例子——尽管有前一段——是@NotNull
,但将其限制在所有对象参数必须为空的情况下。
要避免的排序示例 是 Android 和 IntelliJ 的 @Contract(...)
。虽然我确实喜欢他们的 IDE,但他们的注释细节非常复杂,最终变成了更多问题和平台不兼容的根源,让我追查(不可否认,几乎 100% 的时间是由于我自己在编写新合同时的无知,但为什么做对这么难?)
总结/结论
注释是一个很好的想法,显然是由查看 "codify" 他们的文档的程序员产生的。我觉得他们最近做得太过分了,将文档变成了带有语义的代码,这导致了一些严重的难题和尴尬的情况。更糟糕的是,他们有时无法检测到他们自己的实现中出现的问题,从而给人一种错误的编译时安全感。坚持非常简单,避免任何看起来像不是 Java 的语言(这是您最初打算编写的语言)。
进一步阅读
这个简短的列表混合了来自 Whosebug 和网络的最重要的 (w/optimism!) 来源,我认为它们说明了我的一些观点。
排名不分先后:
- Java Annotations are a Big Mistake
- Annotations, Annotations, Everywhere
- Arguments against annotations
- Are annotations bad?
- An Annotation Nightmare
- Evil Annotations
毕竟,我才意识到我可能仍然未能完整地解决您最初的问题:)