Как намеренно вызвать пользовательское предупреждающее сообщение компилятора Java?

84

Я собираюсь совершить уродливый временный взлом, чтобы обойти проблему с блокировкой, пока мы ждем, пока внешний ресурс будет исправлен. Помимо того, чтобы отметить это большим пугающим комментарием и кучей FIXME, я бы хотел, чтобы компилятор выдавал очевидное предупреждающее сообщение в качестве напоминания, чтобы мы не забыли удалить это. Например, что-то вроде:

[javac] com.foo.Hacky.java:192: warning: FIXME temporary hack to work around library bug, remove me when library is fixed!

Есть ли способ вызвать преднамеренное предупреждение компилятора с выбранным мной сообщением? В противном случае, что проще всего добавить в код, чтобы выдать существующее предупреждение, возможно, с сообщением в строке в строке с нарушением, чтобы оно было напечатано в предупреждающем сообщении?

EDIT: устаревшие теги, похоже, ничего не делают для меня:

/**
 * @deprecated "Temporary hack to work around remote server quirks"
 */
@Deprecated
private void doSomeHackyStuff() { ... }

Никаких ошибок компилятора или времени выполнения в eclipse или из sun javac 1.6 (выполняется из сценария ant), и он определенно выполняет функцию.

Pimlottc
источник
1
FYI: @Deprecated дает только предупреждение компилятора , а не ошибку компилятора или времени выполнения. Код обязательно должен запуститься
BalusC
Попробуйте запустить javac напрямую. Я подозреваю, что Ant скрывает какую-то информацию. Или см. Мой обновленный ответ ниже для получения более подробной информации.
Питер Рекор

Ответы:

42

Я видел, как использовался один метод - связать это с модульным тестированием (вы ведь проводите модульное тестирование, верно?). По сути, вы создаете модульный тест, который завершается ошибкой после достижения исправления внешнего ресурса. Затем вы комментируете этот модульный тест, чтобы рассказать другим, как отменить ваш грубый взлом, как только проблема будет решена.

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

Кевин Дэй
источник
2
Я слышал об этом на одной из конференций No Fluff Just Stuff (не могу вспомнить, кто был ведущим). Я думал, что это было довольно гладко. Тем не менее, я определенно рекомендую эти конференции.
Кевин Дэй
3
Я хотел бы увидеть пример такого подхода
birgersp 02
Ответу 11 лет, но я бы пошел еще дальше: комментировать модульные тесты опасно. Я бы создал модульный тест, который инкапсулирует нежелательное поведение, поэтому, когда оно в конечном итоге будет исправлено, комплимент будет нарушен.
avgvstvs
86

Думаю, выходом будет специальная аннотация, которая будет обработана компилятором. Я часто пишу собственные аннотации, чтобы делать что-то во время выполнения, но никогда не пробовал использовать их во время компиляции. Итак, я могу дать вам только указатели на инструменты, которые могут вам понадобиться:

  • Напишите собственный тип аннотации. На этой странице объясняется, как написать аннотацию.
  • Напишите обработчик аннотаций, который обрабатывает ваши аннотации для выдачи предупреждения. Инструмент, который запускает такие обработчики аннотаций, называется APT. Вы можете найти введение на этой странице . Я думаю, что в APT API вам понадобится AnnotationProcessorEnvironment, который позволит вам выдавать предупреждения.
  • Начиная с Java 6, APT интегрирован в javac. То есть вы можете добавить обработчик аннотаций в командную строку javac. Этот раздел руководства javac расскажет вам, как вызвать свой пользовательский обработчик аннотаций.

Я не знаю, реально ли это решение. Попробую реализовать сам, когда найду время.

редактировать

Я успешно реализовал свое решение. И в качестве бонуса я использовал возможность поставщика услуг Java, чтобы упростить его использование. Фактически, мое решение - это банка, содержащая 2 класса: пользовательскую аннотацию и обработчик аннотаций. Чтобы использовать его, просто добавьте эту банку в путь к классам вашего проекта и аннотируйте все, что хотите! Это нормально работает в моей среде IDE (NetBeans).

Код аннотации:

package fr.barjak.hack;

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

@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD, ElementType.PACKAGE, ElementType.PARAMETER, ElementType.TYPE})
public @interface Hack {

}

Код процессора:

package fr.barjak.hack_processor;

import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;

@SupportedAnnotationTypes("fr.barjak.hack.Hack")
public class Processor extends AbstractProcessor {

    private ProcessingEnvironment env;

    @Override
    public synchronized void init(ProcessingEnvironment pe) {
        this.env = pe;
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (!roundEnv.processingOver()) {
            for (TypeElement te : annotations) {
                final Set< ? extends Element> elts = roundEnv.getElementsAnnotatedWith(te);
                for (Element elt : elts) {
                    env.getMessager().printMessage(Kind.WARNING,
                            String.format("%s : thou shalt not hack %s", roundEnv.getRootElements(), elt),
                            elt);
                }
            }
        }
        return true;
    }

}

Чтобы включить полученную банку в качестве поставщика услуг, добавьте файл META-INF/services/javax.annotation.processing.Processorв банку. Этот файл представляет собой файл acsii, который должен содержать следующий текст:

fr.barjak.hack_processor.Processor
баржак
источник
3
+1, отличное исследование! Это определенно «правильный способ» сделать это (если модульный тест нецелесообразен), и он имеет то преимущество, что выделяется среди обычных предупреждений.
Ишай
1
javac выдает предупреждение, но в eclipse ничего не происходит (?)
fwonce
8
Небольшое примечание: нет необходимости переопределять initи устанавливать envполе - вы можете получить ProcessingEnvironmentот, this.processingEnvпоскольку оно protected.
Paul Bellora
Будет ли это предупреждающее сообщение отображаться в предупреждениях IDE?
uylmz 02
4
Обработка аннотаций в Eclipse по умолчанию отключена. Чтобы включить его, перейдите в Свойства проекта -> Компилятор Java -> Обработка аннотаций -> Включить обработку аннотаций. Затем под этой страницей находится страница «Factory Path», где вам нужно будет настроить jar-файлы с процессорами, которые вы хотите использовать.
Константин Комиссарчик
14

Некоторый быстрый и не такой грязный подход может заключаться в использовании @SuppressWarningsаннотации с заведомо неправильным Stringаргументом:

@SuppressWarnings("FIXME: this is a hack and should be fixed.")

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

Неподдерживаемый @SuppressWarnings («FIXME: это взлом, и его следует исправить.»)

Luchostein
источник
4
Он не работает при подавлении предупреждений о видимости поля или ошибок ворса.
Игорь Ганапольский
2
Ирония отвлекает.
ругательство
12

Один хороший хакер заслуживает другого ... Я обычно генерирую предупреждения компилятора для описанной цели, вводя неиспользуемую переменную в хакерский метод, таким образом:

/**
 * @deprecated "Temporary hack to work around remote server quirks"
 */
@Deprecated
private void doSomeHackyStuff() {
    int FIXMEtemporaryHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixed;
    ...
}

Эта неиспользуемая переменная выдаст предупреждение, которое (в зависимости от вашего компилятора) будет выглядеть примерно так:

ВНИМАНИЕ! Локальная переменная FIXMEintageHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixed никогда не читается.

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

WReach
источник
Вы знаете, как включить предупреждения о неиспользуемых переменных? Я создаю для Android с помощью Gradle из командной строки, и я не получаю никаких предупреждений о неиспользуемых переменных. Вы знаете, как это можно включить build.gradle?
Андреас
@Andreas Извините, я ничего не знаю об этой среде / инструментальной цепочке. Если по этому поводу еще нет ТАК-вопроса, вы можете задать его.
WReach
10

Я написал библиотеку, которая делает это с аннотациями: Lightweight Javac @Warning Annotation

Использование очень простое:

// some code...

@Warning("This method should be refactored")
public void someCodeWhichYouNeedAtTheMomentButYouWantToRefactorItLater() {
    // bad stuff going on here...
}

И компилятор выдаст предупреждающее сообщение с вашим текстом

Артем Зиннатуллин
источник
Добавьте заявление об отказе от ответственности, что вы являетесь автором рекомендованной библиотеки.
Пол Беллора
@PaulBellora не знаю, как это вам поможет, но ладно
Артем Зиннатуллин
2
Благодаря! См. Meta.stackexchange.com/questions/57497/…
Пол Беллора
5

как насчет того, чтобы пометить метод или класс как @Deprecated? документы здесь . Обратите внимание, что есть как @Deprecated, так и @deprecated - заглавная версия D - это аннотация, а строчная d - версия javadoc. Версия javadoc позволяет вам указать произвольную строку, объясняющую, что происходит. Но компиляторы не обязаны выдавать предупреждение при его просмотре (хотя многие это делают). Аннотации всегда должны вызывать предупреждение, хотя я не думаю, что вы можете добавить к ней пояснения.

ОБНОВЛЕНИЕ вот код, который я только что тестировал: Sample.java содержит:

public class Sample {
    @Deprecated
    public static void foo() {
         System.out.println("I am a hack");
    }
}

SampleCaller.java содержит:

public class SampleCaller{
     public static void main(String [] args) {
         Sample.foo();
     }
}

когда я запускаю "javac Sample.java SampleCaller.java", я получаю следующий результат:

Note: SampleCaller.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.

Я использую Sun javac 1.6. Если вы хотите получить честное предупреждение, а не просто примечание, используйте параметр -Xlint. Возможно, это должным образом проникнет в Ant.

Питер Рекор
источник
Я не получаю сообщения об ошибке компилятора с @Deprecate; отредактируйте мой q с помощью примера кода.
pimlottc
1
хм. ваш пример показывает только устаревший метод. где вы используете метод? вот где появится предупреждение.
Питер Рекор
3
Для записи, @Deprecatedработает только между классами (поэтому он бесполезен для частных методов).
npostavs 06
4

Мы можем сделать это с помощью аннотаций!

Чтобы вызвать ошибку, используйте Messagerдля отправки сообщения с помощью Diagnostic.Kind.ERROR. Краткий пример:

processingEnv.getMessager().printMessage(
    Diagnostic.Kind.ERROR, "Something happened!", element);

Вот довольно простая аннотация, которую я написал, чтобы проверить это.

Эта @Markerаннотация указывает на то, что целью является интерфейс маркера:

package marker;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Marker {
}

И обработчик аннотаций вызывает ошибку, если это не так:

package marker;

import javax.annotation.processing.*;
import javax.lang.model.*;
import javax.lang.model.element.*;
import javax.lang.model.type.*;
import javax.lang.model.util.*;
import javax.tools.Diagnostic;
import java.util.Set;

@SupportedAnnotationTypes("marker.Marker")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public final class MarkerProcessor extends AbstractProcessor {

    private void causeError(String message, Element e) {
        processingEnv.getMessager()
            .printMessage(Diagnostic.Kind.ERROR, message, e);
    }

    private void causeError(
            Element subtype, Element supertype, Element method) {
        String message;
        if (subtype == supertype) {
            message = String.format(
                "@Marker target %s declares a method %s",
                subtype, method);
        } else {
            message = String.format(
                "@Marker target %s has a superinterface " +
                "%s which declares a method %s",
                subtype, supertype, method);
        }

        causeError(message, subtype);
    }

    @Override
    public boolean process(
            Set<? extends TypeElement> annotations,
            RoundEnvironment roundEnv) {

        Elements elementUtils = processingEnv.getElementUtils();
        boolean processMarker = annotations.contains(
            elementUtils.getTypeElement(Marker.class.getName()));
        if (!processMarker)
            return false;

        for (Element e : roundEnv.getElementsAnnotatedWith(Marker.class)) {
            ElementKind kind = e.getKind();

            if (kind != ElementKind.INTERFACE) {
                causeError(String.format(
                    "target of @Marker %s is not an interface", e), e);
                continue;
            }

            if (kind == ElementKind.ANNOTATION_TYPE) {
                causeError(String.format(
                    "target of @Marker %s is an annotation", e), e);
                continue;
            }

            ensureNoMethodsDeclared(e, e);
        }

        return true;
    }

    private void ensureNoMethodsDeclared(
            Element subtype, Element supertype) {
        TypeElement type = (TypeElement) supertype;

        for (Element member : type.getEnclosedElements()) {
            if (member.getKind() != ElementKind.METHOD)
                continue;
            if (member.getModifiers().contains(Modifier.STATIC))
                continue;
            causeError(subtype, supertype, member);
        }

        Types typeUtils = processingEnv.getTypeUtils();
        for (TypeMirror face : type.getInterfaces()) {
            ensureNoMethodsDeclared(subtype, typeUtils.asElement(face));
        }
    }
}

Например, это правильное использование @Marker:

  • @Marker
    interface Example {}
    
  • @Marker
    interface Example extends Serializable {}
    

Но такое использование @Markerвызовет ошибку компилятора:

  • @Marker
    class Example {}
    
  • @Marker
    interface Example {
        void method();
    }
    

    ошибка маркера

Вот запись в блоге, которую я нашел очень полезной для начала работы по этой теме:


Небольшое примечание: комментатор ниже указывает на то, что из-за MarkerProcessorссылок Marker.classон зависит от него во время компиляции. Я написал приведенный выше пример с предположением, что оба класса будут находиться в одном файле JAR (скажем, marker.jar), но это не всегда возможно.

Например, предположим, что существует JAR приложения со следующими классами:

com.acme.app.Main
com.acme.app.@Ann
com.acme.app.AnnotatedTypeA (uses @Ann)
com.acme.app.AnnotatedTypeB (uses @Ann)

Тогда процессор для @Annсуществует в отдельном JAR, который используется при компиляции JAR приложения:

com.acme.proc.AnnProcessor (processes @Ann)

В этом случае AnnProcessorневозможно будет ссылаться на тип @Annнапрямую, потому что это создаст циклическую зависимость JAR. Можно будет ссылаться только @Annпо Stringимени или TypeElement/ TypeMirror.

Radiodef
источник
Это не совсем лучший способ написания обработчиков аннотаций. Обычно вы получаете тип аннотации из Set<? extends TypeElement>параметра, а затем получаете аннотированные элементы для данного раунда с помощью getElementsAnnotatedWith(TypeElement annotation). Я тоже не понимаю, зачем вы завернули printMessageметод.
ThePyroEagle
@ThePyroEagle Выбор между двумя перегрузками, несомненно, является довольно небольшой разницей в стиле кодирования.
Radiodef
В идеале, разве вы не хотели бы иметь процессор аннотаций в JAR-файле процессора? Использование ранее упомянутых методов позволяет достичь такого уровня изоляции, поскольку вам не нужно иметь обработанную аннотацию в пути к классам.
ThePyroEagle
2

Здесь показано руководство по аннотациям, а внизу приведен пример определения ваших собственных аннотаций. К сожалению, беглый просмотр учебника показал, что они доступны только в javadoc ...

Аннотации, используемые компилятором. Существует три типа аннотаций, которые предопределены самой спецификацией языка: @Deprecated, @Override и @SuppressWarnings.

Таким образом, похоже, что все, что вы действительно можете сделать, это добавить тег @Deprecated, который компилятор распечатает, или поместит настраиваемый тег в javadocs, который сообщает о взломе.

Мэтт Филлипс
источник
также компилятор выдаст предупреждение о том, что метод, который вы помечаете с помощью @Deprecated, таков ... Он сообщит пользователю, какой из них оскорбляет.
Мэтт Филлипс
1

Если вы используете IntelliJ. Вы можете перейти в: «Настройки»> «Редактор»> «TODO» и добавить «\ bhack.b *» или любой другой шаблон.

Если вы затем сделаете комментарий вроде // HACK: temporary fix to work around server issues

Затем в окне инструмента TODO он будет красиво отображаться вместе со всеми другими заданными вами шаблонами во время редактирования.

BARJ
источник
0

Вы должны использовать инструмент для компиляции, например ant ou maven. С его помощью вы должны определить некоторые задачи во время компиляции, которые могут создавать журналы (например, сообщения или предупреждения) о ваших тегах FIXME.

И если вам нужны ошибки, это тоже возможно. Например, остановить компиляцию, когда вы оставили в коде несколько TODO (почему бы и нет?)

Лиода
источник
Хакер состоит в том, чтобы заставить его работать как можно скорее, у меня точно нет времени, чтобы изменить систему сборки прямо сейчас :) Но хорошо подумать о будущем ...
pimlottc
0

Чтобы вообще появилось какое-либо предупреждение, я обнаружил, что неиспользуемые переменные и пользовательские @SuppressWarnings у меня не работают, но работает ненужное приведение:

public class Example {
    public void warn() {
        String fixmePlease = (String)"Hello";
    }
}

Теперь, когда я компилирую:

$ javac -Xlint:all Example.java
ExampleTest.java:12: warning: [cast] redundant cast to String
        String s = (String) "Hello!";
                   ^
1 warning
Энди Валаам
источник