Як навмисно викликати спеціальне попереджувальне повідомлення компілятора Java?


83

Я збираюся здійснити потворний тимчасовий хак, щоб обійти проблему блокування, поки ми чекаємо виправлення зовнішнього ресурсу. Окрім того, щоб відзначити це великим страшним коментарем та купою 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 (працює з мультиплікаційного сценарію), і це точно виконує функцію.


1
FYI: @Deprecated дає тільки компілятор попередження , а не помилка компілятора або під час виконання. Код обов'язково повинен працювати
BalusC

Спробуйте запустити безпосередньо з javac. Я підозрюю, що Мураха приховує певний результат. Або перегляньте мою оновлену відповідь нижче, щоб дізнатися більше.
Peter Recore

Відповіді:


42

Один із прийомів, який я бачив, - це пов’язати це з модульним тестуванням (ти робиш юніт-тест, так?). В основному ви створюєте модульний тест, який не вдається після досягнення виправлення зовнішніх ресурсів. Потім ви коментуєте цей юніт-тест, щоб розповісти іншим, як скасувати ваш грубозлом, як тільки проблема буде вирішена.

Що насправді гладко у цьому підході, так це те, що тригером для скасування вашого злому є виправлення самої основної проблеми.


2
Я чув про це на одній з конференцій No Fluff Just Stuff (не пам’ятаю, хто був ведучим). Я думав, що це було досить гладко. Однак я безумовно рекомендую ці конференції.
Kevin Day

3
Я хотів би побачити приклад такого підходу
birgersp,

Відповідь 11 років, але я навіть зробив би крок далі: коментування модульних тестів небезпечно. Я б створив юніт-тест, який інкапсулює небажану поведінку, і таким чином, коли це врешті-решт виправляється, компіляція ламається.
avgvstvs

86

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

  • Напишіть власний тип анотації. На цій сторінці пояснюється, як написати анотацію.
  • Напишіть процесор анотацій, який обробляє вашу власну анотацію, щоб видавати попередження. Інструмент, який запускає такі процесори анотацій, називається APT. Вступ можна знайти на цій сторінці . Я думаю, що вам потрібно в APT API - це AnnotationProcessorEnvironment, який дозволить вам видавати попередження.
  • З Java 6 APT інтегрований у javac. Тобто ви можете додати процесор анотацій у командний рядок javac. Цей розділ посібника javac розповість вам, як викликати власний процесор анотацій.

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

Редагувати

Я успішно реалізував своє рішення. І як бонус я використав послугу провайдера послуг Java, щоб спростити його використання. Насправді, моє рішення - це jar, який містить 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, чудове дослідження! Це, безумовно, «правильний спосіб» зробити це (якщо одиничний тест не є практичним), і він має перевагу виділятись над звичайними попередженнями.
Yishai

1
javac видає попередження, але при затемненні нічого не відбувається (?)
fwonce

8
Невелика примітка: немає необхідності перевизначати initта встановлювати envполе - ви можете отримати ProcessingEnvironmentз, this.processingEnvоскільки воно є protected.
Paul Bellora

Чи буде це попереджувальне повідомлення видно в попередженнях IDE?
uylmz

4
Обробка анотацій за замовчуванням вимкнена в Eclipse. Щоб увімкнути його, перейдіть до Властивості проекту -> Компілятор Java -> Обробка анотацій -> Увімкнути обробку анотацій. Тоді під цією сторінкою є сторінка під назвою "Фабричний шлях", де вам потрібно буде налаштувати банки, які мають процесори, які ви хочете використовувати.
Костянтин Комісарчик

14

Деякий швидкий і не такий брудний підхід може бути використання @SuppressWarningsанотації із завідомо неправильним Stringаргументом:

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

Це створить попередження, оскільки воно не розпізнається компілятором як конкретне попередження для придушення:

Непідтримувані @SuppressWarnings ("FIXME: це хакерство, яке слід виправити.")


4
Це не спрацьовує на придушення попереджень про видимість поля або помилок ворсу.
Ігор Ганапольський

2
Іронія відволікає.
камбунктивний

12

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

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

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

ПОПЕРЕДЖЕННЯ: Локальна змінна FIXMEtemporaryHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixed ніколи не читається.

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


Чи знаєте ви, як увімкнути невикористані попередження змінних? Я будую для Android за допомогою Gradle з командного рядка, і я не отримую жодних попереджень щодо невикористаних змінних. Чи знаєте ви, як це можна ввімкнути build.gradle?
Андреас

@Andreas Вибачте, я нічого не знаю про те середовище / ланцюжок інструментів. Якщо на цю тему ще немає запитання SO, ви можете розглянути його.
WReach

10

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

Використання дуже просте:

// some code...

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

І компілятор викине попереджувальне повідомлення з вашим текстом


Будь ласка, додайте застереження, що ви є автором рекомендованої бібліотеки.
Paul Bellora

@PaulBellora не знаю, як це вам допоможе, але добре
Артем Зіннатуллін


5

як щодо позначення методу або класу як @Deprecated? документи тут . Зверніть увагу, що існує як @Deprecated, так і @deprecated - версія D з великої літери є анотацією, а нижня буква d - версія Javadoc. Версія Javadoc дозволяє вказати довільний рядок, що пояснює, що відбувається. Але компілятори не повинні видавати попередження, коли бачать його (хоча багато хто це робить). Анотація завжди повинна викликати попередження, хоча я не думаю, що ви можете додати до неї пояснення.

UPDATE ось код, який я щойно перевірив: 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.

Я використовую сонячний javac 1.6. Якщо ви хочете отримати чесне попередження, а не лише нотатку, використовуйте опцію -Xlint. Можливо, це правильно просочиться через Мураху.


Здається, я не отримую помилку від компілятора з @Deprecate; відредагуйте моє q з прикладом коду.
pimlottc

1
хм ваш приклад показує лише застарілий метод. де ви використовуєте метод? саме там з’явиться попередження.
Peter Recore

3
Для запису @Deprecatedпрацює лише в різних класах (тому для приватних методів це марно).
встановити

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)

Тоді процесор for @Annіснує в окремому JAR, який використовується під час компіляції JAR програми:

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

У цьому випадку AnnProcessorне вдалося би безпосередньо посилатися на тип @Ann, оскільки це створило б циклічну залежність JAR. Він міг би посилатися лише @Annна Stringім'я або TypeElement/ TypeMirror.


Це не зовсім найкращий спосіб написання процесорів анотацій. Зазвичай ви отримуєте тип анотації з Set<? extends TypeElement>параметра, а потім отримуєте анотовані елементи для даного раунду за допомогою getElementsAnnotatedWith(TypeElement annotation). Я також не розумію, чому ви завернули printMessageметод.
ThePyroEagle

@ThePyroEagle Вибір між двома перевантаженнями, безумовно, є досить невеликою різницею в стилі кодування.
Radiodef

Це, але в ідеалі, чи не хочете ви просто мати процесор анотацій у процесорі JAR? Використання раніше згаданих методів забезпечує такий рівень ізоляції, оскільки вам не потрібно мати оброблену анотацію в шляху до класу.
ThePyroEagle

2

Тут показано підручник з анотацій, а внизу - приклад визначення власних анотацій. На жаль, швидке скремінг підручника сказав, що вони доступні лише у javadoc ...

Анотації, що використовуються компілятором Існує три типи анотацій, які визначені самою специфікацією мови: @Deprecated, @Override та @SuppressWarnings.

Отож виглядає так, що все, що ви дійсно можете зробити, - це додати тег @Deprecated, який компілятор роздрукує, або розмістити власний тег у javadocs, який повідомляє про хакерство.


також компілятор видасть попередження, вказуючи, що метод, який ви позначили @Deprecated, є таким ... Він повідомляє користувачеві, який його порушує.
Matt Phillips

1

Якщо ви використовуєте IntelliJ. Ви можете перейти до: Налаштування> Редактор> TODO та додати "\ bhack.b *" або будь-який інший шаблон.

Якщо ви тоді зробите коментар, як // HACK: temporary fix to work around server issues

Потім у вікні інструменту TODO він буде добре відображатися разом із усіма іншими визначеними шаблонами під час редагування.


0

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

І якщо ви хочете помилок, це теж можливо. Як зупинити компіляцію, коли ви залишили трохи коду в коді (чому ні?)


Хак - це якнайшвидше запустити його, я зараз точно не
встигаю

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.