Использование аннотации, чтобы гарантировать, что значение, возвращаемое методом, не будет отброшено

String в Java неизменяем. Следующий фрагмент, вообще говоря, «неправильный».

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. К ним относятся такие вещи, как:

  • Методы для неизменяемых типов (например, String, BigInteger и т. д.), которые возвращают результат операций вместо изменения экземпляра, для которого они вызываются.
  • Методы, возвращаемое значение которых является важной частью его поведения, и их не следует игнорировать, но люди все равно иногда это делают (например, InputStream.read(byte[]) возвращает количество прочитанных байтов, которое НЕ следует считать равной всей длине массива)

В настоящее время мы можем писать коды, которые игнорируют эти возвращаемые значения, чтобы они компилировались и запускались без предупреждения. Средства проверки статического анализа/обнаружители ошибок/исполнители стилей/и т. д. почти наверняка могут пометить их как возможные запахи кода, но было бы уместно/идеально, если бы это могло быть принудительно реализовано самим API, возможно, с помощью аннотаций.

Для класса почти невозможно гарантировать, что он всегда используется «правильно», но есть вещи, которые он может сделать, чтобы направить клиентов к правильному использованию (см.: Effective Java 2nd Edition, Item 58: Используйте отмеченные исключения для исправимые условия и исключения во время выполнения для ошибок программирования и Пункт 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!
     }
}

Приведенный выше код компилируется и работает нормально (как показано на ideone.com). Как я могу сделать это не так? Как я могу назначить семантику, которую я хочу @Undiscardable?


person polygenelubricants    schedule 01.09.2010    source источник
comment
Хорошо, я только что немного изучил аннотацию @Nullable/NotNull, и это похоже по духу на то, что я хочу сделать, так что это должно быть выполнимо: jetbrains.com/idea/documentation/howto.html (IntelliJ IDEA предупреждает вас, если эти соглашения нарушаются.)   -  person polygenelubricants    schedule 01.09.2010
comment
Эта ссылка может быть полезна: JDT-APT для Eclipse с учебными пособиями eclipse.org/jdt/ apt/index.html   -  person polygenelubricants    schedule 01.09.2010
comment
Undiscardable — плохой выбор имени. Эти методы являются идемпотентными. В дополнение к вашей проверке Undiscardable компилятор мог бы оптимизировать некоторые циклы for, если бы знал, какие методы являются идемпотентными.   -  person emory    schedule 01.09.2010
comment
@emory: InputStream.read не идемпотент. На самом деле речь идет не об оптимизации компилятора, а о том, как написать удобный для пользователя API.   -  person polygenelubricants    schedule 01.09.2010
comment
@polygenlubricants есть вариант использования для отказа от некоторого InputStream.read. если вас интересуют только последние байты потока, вам все равно придется читать первые байты. Если вам не нужны первые байты, почему бы не отбросить их?   -  person emory    schedule 01.09.2010
comment
@emory: InputStream.read(byte[]) не всегда заполняет буфер. Вы не должны отбрасывать возвращаемое значение, которое говорит вам, сколько байтов было фактически прочитано.   -  person polygenelubricants    schedule 01.09.2010


Ответы (7)


Вы также можете проверить jsr305, он определяет @CheckReturnValue. Он совместим с findbugs и генерирует предупреждение, когда кто-то забывает обработать возвращаемое значение.

Guavas Splitter использует его: http://code.google.com/p/guava-libraries/source/browse/guava/src/com/google/common/base/Splitter.java

Должен сказать, что я люблю аннотации, которыми можно руководствоваться при статическом анализе кода.

person jontejj    schedule 08.03.2012
comment
У меня аннотация работает только на уровне метода, вы пробовали на уровне пакета? - person Christophe Roussy; 16.12.2013
comment
На уровне пакетов ставить нет смысла или? - person jontejj; 06.04.2020

Я не уверен в осуществимости — особенно в переносном виде — но взгляните на Римские цифры в нашей Java (код GitHub) от Адриана Куна. Он использовал обработку аннотаций И частный API Sun javac для добавления литералов римских цифр в Java путем посещения исходного кода для замены.

Возможно, вы могли бы использовать аналогичный подход к:

  • найти вызовы вашего аннотированного метода в исходном коде
  • check if the result is assigned (won't be easy IMO)
    • generate a compiler warning if not

И не пропустите следующие ресурсы из поста Адриана:

Вам также может понравиться

Ссылка

Похожие вопросы

person Pascal Thivent    schedule 01.09.2010
comment
проверить, присвоен ли результат (это будет непросто, IMO) - я думал, что это можно сделать, просто проверив, является ли метод с возвращаемым значением @Undiscardable грамматически ExpressionStatement или нет (java.sun.com/docs/books/jls/ Third_edition/html /). Если это так, то поднимите предупреждение. - person polygenelubricants; 01.09.2010
comment
1. Расширьте download.oracle.com/javase/6/docs/jdk/api/javac/tree/com/sun/ 2. Переопределить visitAssignment, visitMethodInvocation и, возможно, некоторые другие - person emory; 01.09.2010
comment
@emory Аааа, хорошо, надо вникнуть. - person Pascal Thivent; 01.09.2010
comment
@emory, @Pascal: я подумал @Override visitExpressionStatement, проверьте, не является ли это вызовом метода для @Undiscardable. Если да, поднимите предупреждение. - person polygenelubricants; 01.09.2010
comment
@polygenelubricants Понятно. Это должно сработать. Возможно, вы могли бы поместить что-то в visitMethodInvocation, чтобы защититься от отдаленной возможности того, что один из них не находится в ExpressionStatement. - person emory; 01.09.2010

В заключение: вы хотели бы иметь @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 {}

Тем не менее, чтобы заставить компилятор выполнять эту работу, вам нужно проделать немного больше работы.

person BalusC    schedule 01.09.2010
comment
Да, исходный код класса API необходимо изменить, чтобы включить эти аннотации. Если это действительно возможно, то я думаю, что разработчики API/авторы библиотек, вероятно, с радостью сделали бы это в любом случае, поскольку это помогает направлять пользователей к правильному использованию. Хотя я понятия не имею, так ли это. Я смотрю на исходный код java.lang.Override и понятия не имею, как это работает. - person polygenelubricants; 01.09.2010
comment
Аннотации java.lang — это синтаксический сахар. Компилятор проверяет эти аннотации. См. также JLS 9.6.1 — предопределенные типы аннотаций. . - person BalusC; 01.09.2010
comment
@BalusC: Может быть, более важным вопросом будет: есть ли учебник по написанию собственной аннотации с принудительной семантикой времени компиляции? Я думаю, что @Nullable/NotNull являются близкими родственниками того, чем я хочу заниматься, так что мне, вероятно, следует искать в этом направлении. - person polygenelubricants; 01.09.2010
comment
Вы не можете сделать это, не написав свой собственный компилятор. @Nullable и так далее — это просто аннотации метаданных. Они сканируются фреймворком/API во время выполнения (таким образом, не во время компиляции!). java.lang — это больше, чем просто метаданные. Они имеют особое значение для компилятора. - person BalusC; 01.09.2010
comment
@BalusC: согласно этому jetbrains.com/idea/documentation/howto.html IntelliJ IDEA предупредит вас, если эти [@NotNull/Nullable] контракты будут нарушены. - кажется, это указывает на то, что на самом деле он обрабатывается во время компиляции, я ошибаюсь? - person polygenelubricants; 01.09.2010
comment
Аннотации @polygenelubricants могут обрабатываться во время компиляции (javac -processor {procesor.qualifiedName}), во время выполнения (путем отражения) или в обоих случаях. - person emory; 01.09.2010
comment
@poly: IntelliJ и Eclipse не используют javac. @emory: Это хорошо, я тоже видел ссылки в ответе Паскаля. - person BalusC; 01.09.2010

На 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.

person Nicolas    schedule 24.09.2018

Вам не нужно определять аннотацию. Вы можете определить правило при вызове метода:

  1. метод имеет возвращаемый тип void;
  2. результат метода используется в качестве аргумента для вызова другого метода; или
  3. результат метода присваивается переменной.

Вы можете реализовать процессор, который применяет это правило, или реализовать контрольный стиль, который применяет это правило.

person emory    schedule 01.09.2010
comment
Однако вы не захотите делать это с помощью методов ALL. Наверное, только некоторые. Отсюда и аннотация. И что вы имеете в виду под процессором? Инструмент обработки аннотаций? (Наверное, нет, поскольку вы сказали, что аннотация не нужна?). - person polygenelubricants; 01.09.2010
comment
@polygenelubricants Я имел в виду download.oracle.com/ javase/6/docs/api/javax/аннотация/. Разве APT не устарел? Я бы не хотел делать это со всеми методами — только с теми, которые меня сбивают с толку (например, String.toUpperCase). Но, к сожалению, я не контролирую реализацию этих методов и не могу применить аннотацию. Так что для меня все или ничего. - person emory; 01.09.2010
comment
@emory: Да, если эта функциональность возможна с помощью аннотаций, ответственность за ее применение лежит на издателях API / авторах библиотек, а не на пользователях. Я думаю, что это достаточно хорошая идея, что они, вероятно, сделали бы это добровольно, но я могу ошибаться. - person polygenelubricants; 01.09.2010
comment
@emory: я абсолютно не знаю, устарел ли APT или нет. Это совершенно новое и захватывающее приключение для меня. - person polygenelubricants; 01.09.2010
comment
@polygenelubricants Инструмент обработки аннотаций (apt) из Java 5 устарел. Annotation Processing точно нет (просто apt не нужны, javac их можно запустить). - person Pascal Thivent; 01.09.2010
comment
@polygenelubricants Я ошибался в том, что APT устарел. Я знаю, что задача Ant APT — ant.apache.org/manual/Tasks/apt. html - содержит проблемный язык. Для этой задачи требуется Java 1.5. Это может работать в более поздних версиях, но это не может быть подтверждено до тех пор, пока эти версии не будут выпущены. Имейте в виду, что инструмент Apt является нестабильной частью среды JDK, поэтому в будущих версиях он может радикально измениться. В частности, он, вероятно, устарел в JDK 6, который может запускать процессоры аннотаций как часть javac. независимо от того, объявлен ли apt устаревшим, javac поддерживает обработку аннотаций. - person emory; 01.09.2010

Отказ от ответственности: на самом деле, у меня тот же вопрос, но еще не полное решение. НО:

У меня есть идея, как это можно сделать чистым способом, которую я хочу опубликовать здесь, пытаясь это сделать:

  1. One may use AspectJ to invoke code after a specific method has been called. For example

    @AfterReturning(pointcut=“call(int Foo.m(int))”, returning=”x”)
    public void doSomething(int x){ ... }
    could be used. The return value x is passed to your method.

  2. Затем ваш метод может отслеживать счетчик ссылок возвращаемого значения. Если возвращаемое значение Garbadge Collected, оно было выброшено, и вы можете выдать предупреждение, см., например, http://java.dzone.com/articles/letting-garbage-collector-do-c

Конечно, я бы предпочел поддержку аннотации и времени компиляции для этого, поскольку вышеизложенное, возможно, подходит только для среды тестирования и, возможно, не для производства (из-за влияния на производительность).

Любые комментарии, если это может работать?

person Christian Fries    schedule 29.06.2013

У вас есть проблема, и проблема в том, что люди могут по ошибке забыть использовать возвраты методов. Используя аннотации, вы сообщаете разработчику библиотеки, что он должен нести ответственность за напоминание своим вызывающим объектам о том, что не следует отбрасывать результаты определенных методов.

Хотя это кажется хорошей идеей, я так не думаю. Хотим ли мы загромождать код уведомлениями пользователей об их плохой практике? Существует множество продуктов, которые смотрят на код и сообщают вам, когда вы делаете что-то неправильное (или нежелательное), например, Lint, Sonar и даже в меньшей степени JavaDocs.

Что, если вы не согласны с тем, что сказал автор библиотеки, должны ли мы теперь использовать @SuppressWarnings("return-discarded").

Хотя это может быть полезно в качестве учебного пособия, моя точка зрения больше связана с разделением задач, чем с помощью начинающих программистов. Код (и аннотации) в классе должен быть связан с функцией класса, а не устанавливать политику того, когда и как использовать его методы.

person David Newcomb    schedule 26.09.2016