【问题标题】:JetBrains' @Contract annotationJetBrains 的 @Contract 注释
【发布时间】:2016-04-09 19:08:47
【问题描述】:

org.jetbrains.annotations.Contract 注释是如何工作的? IntelliJ IDEA 如何支持它?

【问题讨论】:

    标签: java intellij-idea jetbrains-ide


    【解决方案1】:

    org.jetbrains.annotations.Contract 注释是如何工作的?

    虽然前面的答案提供了丰富的信息,但我认为它们并未解决您问题中的“工作”一词。也就是说,他们没有解释 IntelliJ 在幕后做了什么来实现他们的注释,以便您可以轻松地从头开始构建自己的注释。

    如果您看一下下面的源代码,您可能会认为对于像@NotNull 这样听起来很简单的东西来说,它似乎有点过于复杂(或至少是冗长的)。我同意你的看法,这也是我通常避免使用@Contract 之类不是“简单明了”的子句(如@NotNull)的原因之一,而是直接使用JavaDoc 我的先决条件。

    一般不鼓励使用复杂的合同注释——尽管我可能会因为跳过这趟时髦的新火车而受到憎恨——这里有一些原因:

    • 注释可能很复杂——例如在自己的定义和/或具有Turing completeness 外观的“语法”中具有多个嵌套注释。这可能会导致在编译时产生错误的自信感,因为这会掩盖隐藏/抽象层背后的错误真正罪魁祸首,并且无法生成最初预期的警告。
    • 与我之前的观点类似但不同,注释通常会在少数关键字中向开发人员隐藏大量逻辑,从而产生难以理解的人类代码和/或难以调试的异常行为.
    • 应用配置通常是seen masquerading作为注解。看看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 https://stackoverflow.com/a/9256595/14731
     */
    @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
    {
    }
    

    您应该在什么时候使用什么合约?

    我建议坚持使用其意图从其名称中一目了然的那种,并避免使用那些具有自己的语义和类似语言定义的那种。

    一个使用的示例——尽管有上一段——是@NotNull,但将其限制在所有对象参数必须为空的情况下。

    需要避免的例子是 Android 和 IntelliJ 的 @Contract(...)。虽然我确实喜欢他们的 IDE,但他们的注释细节非常复杂,最终变成了我追查的更多问题和平台不兼容的根源(诚然,这是由于我几乎 100% 的时间都对编写新合约的无知造成的,但是为什么做对了这么难?)

    总结/结论

    注释显然是程序员希望“编纂”其文档的一个好主意。我觉得他们最近走得太远了,将文档变成了带有语义的代码,这导致了一些严重的难题和尴尬的情况。更糟糕的是,他们有时会因为未能检测到他们自己的实现中出现的问题而给人一种编译时安全的错误感觉。坚持非常简单,避免使用任何看起来不是 Java 的语言(这是您最初打算编写的语言)。

    进一步阅读

    这个简短的列表混合了来自 StackOverflow 和网络的主要关键(带有乐观主义!)资源,我认为这些资源说明了我的一些观点。

    不分先后:

    毕竟,我才意识到我可能仍然未能完整地解决您最初的问题:)

    【讨论】:

    • 虽然这是一个值得讨论的话题,但这篇文章没有回答有关@Contract 工作原理的问题。实际上,与其他答案不同,它根本没有解决实际提出的问题。我建议把它变成一篇博客文章或其他东西。
    • @MatthewRead 是的,但我确实已经提到过……请参阅我帖子的最后一行。另外,我的观点是,这个问题需要深入讨论,而且没有简单的答案。
    【解决方案2】:

    official documentation 指定注释的所有支持和识别值的formal grammar

    通俗地说:

    • 一份合同可以有 1 个或多个与之关联的条款
    • 子句总是 [args] -&gt; [effect]
    • Args是1个或多个约束,定义为any | null | !null | false | true
    • 效果只是一个约束或fail

    让我们来看一个简单的例子——我最喜欢的一个例子是,“无论你传入这个方法,它都会抛出一个异常。”

    @Contract("_-> fail")
    public void alwaysBreak(Object o) {
        throw new IllegalArgumentException();
    }
    

    在这里,我们使用_ 或“any”来表示无论我们向该方法传递什么,我们都会抛出异常。

    如果我们撒谎说这个方法会无条件返回true呢?

    @Contract("_-> true")
    public void alwaysBreak(Object o) {
        throw new IllegalArgumentException();
    }
    

    IntelliJ 对此提出了一些警告。

    当我们在 void 方法中时,我们说我们正在返回一个布尔值,这也(显然)令人不安......


    您会发现自己想要使用@Contract 的主要时间是:

    • 您要保证返回真或假
    • 您要保证在给定约束条件下返回非空值
    • 您要明确表示可以在给定约束条件下返回空值
    • 您要明确表示在给定约束条件下会引发异常

    这并不是说@Contract 是完美的;离得很远。它不能在某些情况下进行非常深入的分析,但是在您的代码库中拥有它可以让您的工具免费进行此类分析。

    【讨论】:

      【解决方案3】:

      首先,我应该说这个注释仅供IDEA用来检查可能的错误。 Java 编译器将几乎完全忽略它(它将在编译的工件中,但没有效果)。说了这么多……

      注解的目的是描述该方法将遵守的契约,这有助于 IDEA 捕获可能调用此方法的方法中的问题。有问题的合同是一组分号分隔的条款,每个条款都描述了一个输入和一个保证发生的输出。因果用-&gt; 分隔,描述当你向方法提供X 时,Y 将总是 结果的情况。输入被描述为一个逗号分隔的列表,描述了多个输入的情况。

      可能的输入是 _(任何值)、null!null(非空)、falsetrue,可能的输出会将 fail 添加到此列表中。

      例如,null -&gt; false 表示,如果输入 null,则结果是 false 布尔值。 null -&gt; false; !null -&gt; true 对此进行了扩展,表示null始终返回false,非null 值将始终返回true,等等。最后,@ 987654335@ 表示如果传递一个空值,该方法将抛出异常。

      对于多参数示例,null, !null -&gt; fail 表示,在双参数方法中,如果第一个参数为 null 而第二个参数不为 null,则保证会抛出异常。

      如果方法不改变对象的状态,只是返回一个新值,那么你应该将pure设置为true。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-06-22
        • 1970-01-01
        • 1970-01-01
        • 2021-05-13
        • 1970-01-01
        • 2019-06-03
        • 1970-01-01
        相关资源
        最近更新 更多