【问题标题】:Using annotation to ensure that value returned by method is not discarded使用注解确保方法返回的值不被丢弃
【发布时间】:2011-04-06 13:29:33
【问题描述】:

String 在 Java 中是不可变的。从广义上讲,下面的 sn-p 是“错误的”。

String s = "hello world!";

s.toUpperCase(); // "wrong"!!

System.out.println(s); // still "hello world!"!!!

尽管这是“错误的”,但代码编译并运行,可能会让许多初学者感到困惑,他们必须要么被告知错误是什么,要么通过查阅文档自己找出答案。

阅读文档是理解 API 的重要组成部分,但我想知道这是否可以通过额外的编译时检查来补充。特别是,我想知道是否可以使用 Java 的注释框架来强制某些方法返回的值不被忽略。然后,API 设计者/库作者将在他们的方法中使用此注释来记录不应忽略哪些返回值。

一旦 API 补充了这个注解(或者可能是另一种机制),那么每当用户编写上述代码时,它就不会编译(或者这样做会发出严厉的警告)。

那么这可以做到吗,你将如何做这样的事情?


附录:动机

似乎很清楚,在一般情况下,Java应该允许忽略方法的返回值。 List.add (always true), System.setProperty (previous value) 等方法的返回值在大多数情况下都可以安全地忽略。

但是,也有许多方法的返回值应该被忽略。这样做几乎总是一个程序员错误,或者不是正确使用 API。其中包括:

  • 不可变类型(例如StringBigInteger 等)上的方法返回操作的结果,而不是改变调用它的实例。
  • 返回值是其行为的关键部分且不应被忽略的方法,但人们有时还是会这样做(例如 InputStream.read(byte[]) 返回读取的字节数,应该假设是数组的整个长度)

目前我们可以编写忽略这些返回值的代码,并让它们在没有警告的情况下编译和运行。静态分析检查器/错误查找器/样式执行器/等几乎可以肯定地将这些标记为可能的代码异味,但如果这可以由 API 本身强制执行,或许通过注释,这似乎是合适/理想的。

一个类几乎不可能确保它总是“正确”使用,但它可以做一些事情来帮助指导客户正确使用(参见:Effective Java 2nd Edition,Item 58: Use检查可恢复条件的异常和编程错误的运行时异常第 62 条:记录每个方法抛出的所有异常)。有一个注释可以强制客户端不忽略某些方法的返回值,并在编译时以错误或警告的形式由编译器强制执行,似乎符合这个想法。


附录 2:片段

以下是初步尝试,简明扼要地说明了我想要实现的目标:

@interface Undiscardable { }
//attachable to methods to indicate that its
//return value must not be discarded

public class UndiscardableTest {
     public static @Undiscardable int f() {
             return 42;
     }

     public static void main(String[] args) {
             f(); // what do I have to do so this generates
                  // compilation warning/error?

             System.out.println(f()); // this one would be fine!
     }
}

上面的代码编译并运行良好(as seen on ideone.com)。我怎样才能让它不是这样?如何将我想要的语义分配给@Undiscardable

【问题讨论】:

  • 好的,我刚刚调查了一下@Nullable/NotNull注解,这在精神上似乎和我想做的很相似,所以这一定是可行的:jetbrains.com/idea/documentation/howto.html ("IntelliJ IDEA如果违反这些合同,会发出警告。”)
  • 这个链接可能有用:JDT-APT for Eclipse, with tutorials eclipse.org/jdt/apt/index.html
  • 不可丢弃是一个糟糕的名称选择。这些方法是幂等的。除了 Undiscardable 检查之外,如果编译器知道哪些方法是幂等的,它还可以优化一些 for 循环。
  • @emory: InputStream.read 不是幂等的。这不是真正的编译器优化,而是如何编写用户友好的 API。
  • @emory: InputStream.read(byte[]) 并不总是填满缓冲区。您不能丢弃返回的值,它会告诉您实际读取了多少字节。

标签: java annotations return-value api-design


【解决方案1】:

您还可以查看 jsr305,它定义了一个 @CheckReturnValue 注释。它与 findbugs 兼容,并在有人忘记处理返回值时生成警告。

Guavas Splitter 使用它: http://code.google.com/p/guava-libraries/source/browse/guava/src/com/google/common/base/Splitter.java

我必须说我喜欢可以指导静态代码分析的注解。

【讨论】:

  • 注解只适用于我的方法级别,你尝试过包级别吗?
  • 把它放在包级别还是没有任何意义?
【解决方案2】:

我不确定是否可行——尤其是便携方式——但请查看Adrian Kuhn 中的Roman Numerals, in our Java (GitHub code)。他使用 annotation processing AND Sun 的 javac 私有 API 将罗马数字文字添加到 Java 通过访问源代码进行一些替换

也许您可以使用类似的方法:

  • 在源代码中查找对带注释的方法的调用
  • 检查结果是否已分配(IMO 不容易)
  • 如果不是,则生成编译器警告

不要错过 Adrian 帖子中的以下资源:

你可能也喜欢

参考

相关问题

【讨论】:

  • "检查结果是否已分配(IMO 不容易)" - 我想这可以通过简单地检查具有@Undiscardable 返回值的方法在语法上是否为 ExpressionStatement 与否 (java.sun.com/docs/books/jls/third_edition/html/…)。如果是,则发出警告。
  • @polygenelubricants 你真的不需要检查assignment statement吗?那么foo(f())呢?
  • 1.扩展download.oracle.com/javase/6/docs/jdk/api/javac/tree/com/sun/… 2. 覆盖visitAssignment、visitMethodInvocation 以及其他一些
  • @emory Ahhh,很好,必须挖掘它。
  • @emory, @Pascal:我在想@Override visitExpressionStatement,检查它是否是对@Undiscardable 的方法调用。如果是这样,请发出警告。
【解决方案3】:

简而言之:您想要一个类似@Deprecated 的注解,它可以帮助编译器/IDE 在调用方法时发出警告/错误而不分配其结果?如果不修改 Java 源代码和编译器,就无法实现这一点。必须对特定方法进行注释,并且编译器必须知道它们。在不修改源代码和/或编译器的情况下,您最多可以创建一种 IDE 插件/设置,它可以识别这些情况并相应地生成错误/警告。


更新:您可以围绕它编写一个框架/插件,以相应地检查调用的方法和错误。您只想在运行时使用注释。您可以通过使用 @Retention (RetentionPolicy.RUNTIME) 注释注释来做到这一点。然后,您可以使用Method#getAnnotation() 来确定此注解是否可用。下面是这样一个框架如何完成这项工作的启动示例:

package com.example;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public class Test {

    public static void main(String[] args) throws Exception {
        if (Test.class.getMethod("f", new Class[0]).getAnnotation(Undiscardable.class) != null) {
            System.err.println("You should not discard the return value of f()!");
        } else {
            f();
        }

        System.out.println(f());
    }

    public static @Undiscardable int f() {
        return 42;
    }
}

@Retention(RetentionPolicy.RUNTIME)
@interface Undiscardable {}

不过,要让编译器完成这项工作,您还需要做更多的工作。

【讨论】:

  • 必须修改 API 类的源代码,是的,以包含这些注释。如果这确实可行,那么我认为 API 设计者/库作者可能会很乐意这样做,因为它有助于引导用户正确使用。不过,我不知道是不是这样。我正在查看java.lang.Override 的源代码,但我不知道它是如何工作的。
  • java.lang 注释是语法糖。编译器检查这些注释。另见JLS 9.6.1 - Predefined Annotation Types
  • @BalusC:也许一个更“简单”的问题是:是否有使用可执行的编译时语义编写自己的注释的教程?我认为@Nullable/NotNull 是我想做的事情的近亲,所以我可能应该朝那个方向看。
  • 如果不编写自己的编译器,您将无法做到这一点。 @Nullable 等等只是“纯”元数据注释。它们在运行时由框架/API 扫描(因此不在编译时!)。 java.lang 不仅仅是元数据。它们对编译器有特殊意义。
  • @BalusC:根据jetbrains.com/idea/documentation/howto.html,“如果违反了这些 [@NotNull/Nullable] 合同,IntelliJ IDEA 会警告您。” - 这似乎表明它实际上是在编译时处理的,我错了吗?
【解决方案4】:

在 Android 上,如果未使用返回值,您可以使用 @CheckResult 显示警告。

public class ImmutableObject {

    public final int value;

    public ImmutableObject(int value) {
        this.value = value;
    }

    @CheckResult
    public ImmutableObject addOne() {
        return new ImmutableObject(value + 1);
    }
}

这将发出警告:

ImmutableObject obj = new ImmutableObj();
obj.addOne();  // Warning here
ImmutableObject obj2 = obj.addOne();  // No warning

如果使用 RxJava,也可以使用@CheckReturnValue

【讨论】:

    【解决方案5】:

    您不需要定义注释。您可以在调用方法时定义规则:

    1. 该方法的返回类型为 void;
    2. 方法的结果被用作另一个方法调用的参数;或
    3. 方法的结果被赋值给一个变量。

    您可以实现一个执行此规则的处理器或实现一个执行此规则的 Checkstyle。

    【讨论】:

    • 不过,您不会希望使用 ALL 方法来执行此操作。大概只能选一些。因此注释。处理器是什么意思?注释处理器工具? (我猜不是因为你说不需要注释?)。
    • @polygenelubricants 我的意思是download.oracle.com/javase/6/docs/api/javax/annotation/…。 APT 不是被弃用了吗?我不想对所有方法都这样做 - 只有那些会让我绊倒的方法(例如 String.toUpperCase)。但遗憾的是,我无法控制这些方法的实现,也无法应用注释。所以对我来说,要么全有,要么全无。
    • @emory:是的,如果这个功能可以通过注释实现,那么应用它的责任在于 API 发布者/图书馆作者,而不是用户。我认为这是一个很好的想法,他们可能会自愿这样做,但我可能错了。
    • @emory:我完全不知道 APT 是否被弃用。这对我来说是一次全新的、令人兴奋的冒险。
    • @polygenelubricants Java 5 中的 Annotation Processing Tool (apt) 已弃用。注解处理肯定不行(你只是不需要aptjavac可以运行它们)。
    【解决方案6】:

    免责声明:实际上,我有同样的问题,但还没有完整的解决方案。但是:

    我有一个想法如何以一种干净的方式完成,我想在这里发布,同时尝试完成它:

    1. 可以在调用特定方法后使用 AspectJ 调用代码。例如

      @AfterReturning(pointcut="call(int Foo.m(int))", 返回="x")
      public void doSomething(int x){ ... }
      可用于。返回值 x 被传递给您的方法。
    2. 然后您的方法可以监视返回值的引用计数。如果返回值为 Garbadge Collected,则它已被丢弃,您可以发出警告,例如,请参阅, http://java.dzone.com/articles/letting-garbage-collector-do-c

    当然,我更喜欢注释和编译时支持,因为上面可能只适用于测试环境,可能不适用于生产环境(由于其性能影响)。

    如果这可行的话,有什么 cmets 吗?

    【讨论】:

      【解决方案7】:

      你有一个问题,问题是人们可能会错误地忘记使用方法的返回。通过使用注释,您是在告诉库编写者,他们必须负责提醒调用者不要丢弃某些方法的结果。

      虽然这似乎是个好主意,但我认为不是。我们是否希望通过向用户通知他们的不良做法来混淆代码?有很多产品可以查看代码并在您做错(或不受欢迎)的事情时告诉您,例如 Lint、Sonar 甚至 JavaDocs(在较小程度上)。

      如果您不同意库作者所说的话,我们现在是否应该使用 @SuppressWarnings("return-discarded")。

      虽然这可能有助于作为学习帮助,但我的观点更多的是关注点分离,而不是帮助新手程序员。类中的代码(和注释)应该与类的功能相关,而不是规定何时以及如何使用它的方法的策略。

      【讨论】:

        猜你喜欢
        • 2018-05-02
        • 2020-05-17
        • 2021-05-28
        • 1970-01-01
        • 2019-05-04
        • 1970-01-01
        • 1970-01-01
        • 2015-05-23
        • 1970-01-01
        相关资源
        最近更新 更多