使用注释确保不会丢弃方法返回的值

Java中的String是不可变的。 从广义上讲,以下片段是“错误的”。

 String s = "hello world!"; s.toUpperCase(); // "wrong"!! System.out.println(s); // still "hello world!"!!! 

尽管这是“错误的”,代码编译和运行,也许是许多初学者的困惑,他们必须被告知错误是什么,或者通过查阅文档找出自己。

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

一旦API补充了这个注释(或者可能是另一种机制),那么每当用户编写如上所述的代码时,它就不会编译(或者通过严厉的警告进行编译)。

那么这可以做到,你会怎么做这样的事情?


附录:动机

很明显,在一般情况下,Java 应该允许忽略方法的返回值。 List.add总是为 true ), System.setProperty (之前的值)等方法的返回值可能在大多数情况下可以安全地被忽略。

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

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

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

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


附录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! } } 

上面的代码编译并运行正常( 如ideone.com上所示 )。 我怎么能不这样做呢? 如何将我想要的语义分配给@Undiscardable

我不确定可行性 – 特别是以便携式方式 – 但是看看Adrian Kuhn的 Java ( GitHub代码 )中的Roman Numerals 。 他使用注释处理 Sun的javac私有API 通过访问源代码来将罗马数字文字添加到Java中以进行一些替换

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

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

并且不要错过Adrian的post中的以下资源:

你也许也喜欢

  • David Erni 的Java编译器黑客指南
  • Javac Hacker Resources ,链接集合
  • 如何重写断言,使它们无法关闭!

参考

  • 罗马数字,在我们的Java中
  • GitHub代码

相关问题

  • 插入Java编译器
  • 如何故意导致自定义java编译器警告消息?
  • 如何创建自定义注释并使用APT处理它?

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

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

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

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

  1. 该方法具有void返回类型;
  2. 该方法的结果用作另一个方法调用的参数; 要么
  3. 方法的结果分配给变量。

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

在一个坚果中:你想要一个像注释一样的@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 {} 

然而,为了让编译器完成工作,你必须做更多的工作。

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

我知道如何以一种干净的方式完成这项工作,我想在这里发布,同时尝试完成它:

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

      @AfterReturning(pointcut =“call(int Foo.m(int))”,return =“x”)
     public void doSomething(int x){...} 

    可用于。 返回值x将传递给您的方法。

  2. 然后,您的方法可以查看返回值的引用计数。 如果返回值为Garbadge Collected,它会被丢弃,您可以发出警告,例如http://java.dzone.com/articles/letting-garbage-collector-do-c

当然,我更倾向于对此进行注释和编译时支持,因为上述内容可能仅适用于测试环境,可能不适用于生产(由于其性能影响)。

任何评论,如果这可行吗?

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

虽然这似乎是个好主意,但我认为不是。 我们是否想要通知用户有关其糟糕做法的通知? 有很多产品可以查看代码,并在较小程度上告诉您何时(如Lint,Sonar甚至JavaDocs)做错(或不合适)。

如果您不同意图书馆作者所说的内容,我们现在应该使用@SuppressWarnings(“return-abandoned”)。

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

在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