カスタムJavaコンパイラの警告メッセージを意図的に発生させる方法は?


83

外部リソースが修正されるのを待つ間、ブロッキングの問題を回避するために、醜い一時的なハックをコミットしようとしています。大きな恐ろしいコメントとたくさんのFIXMEでマークする以外に、コンパイラーにリマインダーとして明白な警告メッセージをスローさせたいので、これを取り除くことを忘れないでください。たとえば、次のようなものです。

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

選択したメッセージで意図的なコンパイラ警告を発生させる方法はありますか?それができない場合、既存の警告をスローするためにコードに追加する最も簡単なことは何ですか?おそらく、問題のある行に文字列内のメッセージがあり、警告メッセージに出力されますか?

編集:非推奨のタグは私にとって何もしていないようです:

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

Eclipseまたはsunjavac 1.6(antスクリプトから実行)でコンパイラーまたはランタイムエラーは発生せず、関数を確実に実行しています。


1
参考:@Deprecatedはコンパイラの警告のみを表示し、コンパイラまたはランタイムエラーは表示しません。コードは間違いなく実行されるはずです
BalusC 2009年

javacで直接実行してみてください。Antが出力を隠しているのではないかと思います。または、詳細については、以下の更新された回答を参照してください。
Peter Recore

回答:


42

私が使用しているのを見た1つの手法は、これを単体テストに結び付けることです(単体テストを行いますよね?)。基本的に、外部リソースの修正が達成されると失敗する単体テストを作成します。次に、そのユニットテストにコメントして、問題が解決したら、危険なハックを元に戻す方法を他の人に伝えます。

このアプローチで本当に巧妙なのは、ハッキングを元に戻すためのトリガーがコアの問題自体の修正であるということです。


2
これについては、No Fluff Just Stuffカンファレンスの1つで聞いた(プレゼンターが誰であったか思い出せない)。なめらかだと思いました。しかし、私は間違いなくそれらの会議をお勧めします。
ケビン日

3
このアプローチの例を見てみたい
birgersp 2017年

答えは11歳ですが、さらに一歩進んで、ユニットテストにコメントするのは危険です。望ましくない動作をカプセル化する単体テストを作成します。これにより、最終的に修正されたときに、コンパイルが中断されます。
avgvstvs

86

コンパイラによって処理されるカスタムアノテーションが解決策だと思います。実行時に何かを行うためにカスタムアノテーションを頻繁に作成しますが、コンパイル時にそれらを使用しようとしたことはありません。だから、私はあなたが必要とするかもしれないツールへのポインタをあなたに与えることができるだけです:

  • カスタムアノテーションタイプを記述します。このページでは、アノテーションの書き方を説明しています。
  • カスタム注釈を処理して警告を発する注釈プロセッサを作成します。このような注釈プロセッサを実行するツールは、APTと呼ばれます。このページで紹介を見つけることができます。APT APIに必要なのは、警告を発することができるAnnotationProcessorEnvironmentだと思います。
  • Java 6から、APTはjavacに統合されています。つまり、javacコマンドラインで注釈プロセッサを追加できます。javacマニュアルのこのセクションでは、カスタムアノテーションプロセッサを呼び出す方法について説明します。

この解決策が本当に実行可能かどうかはわかりません。時間があれば自分で実装してみます。

編集

ソリューションの実装に成功しました。そしてボーナスとして、私はJavaのサービスプロバイダー機能を使用してその使用を簡素化しました。実際、私のソリューションは、カスタムアノテーションとアノテーションプロセッサの2つのクラスを含むjarです。これを使用するには、このjarをプロジェクトのクラスパスに追加し、必要なものに注釈を付けます。これは私の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;
    }

}

結果のjarをサービスプロバイダーとして有効にするには、ファイルMETA-INF/services/javax.annotation.processing.Processorをjarに追加します。このファイルは、次のテキストを含む必要があるacsiiファイルです。

fr.barjak.hack_processor.Processor

3
+1、すばらしい研究です!これは間違いなくそれを行うための「正しい方法」であり(単体テストが実用的でない場合)、通常の警告よりも目立つという利点があります。
Yishai

1
javacは警告を発しますが、日食(?)では何も起こりません
fwonce 2012年

8
小さなノートは:上書きする必要はありませんinitし、設定envフィールドには-あなたが得ることができますProcessingEnvironmentからthis.processingEnv、それはだからprotected
Paul Bellora 2012年

この警告メッセージはIDE警告に表示されますか?
uylmz 2014

4
Eclipseでは、注釈処理はデフォルトでオフになっています。オンにするには、[プロジェクトのプロパティ]-> [Javaコンパイラ]-> [注釈処理]-> [注釈処理を有効にする]に移動します。次に、そのページの下に「ファクトリパス」と呼ばれるページがあります。このページでは、使用するプロセッサを搭載したjarを構成する必要があります。
Konstantin Komissarchik 2014

14

いくつかの迅速でそれほど汚くないアプローチは@SuppressWarnings、意図的に間違ったString引数で注釈を使用することかもしれません:

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

これは、コンパイラによって抑制すべき特定の警告として認識されないため、警告を生成します。

サポートされていない@SuppressWarnings( "FIXME:これはハックであり、修正する必要があります。")


4
フィールドの可視性の警告やリントエラーの抑制には機能しません。
IgorGanapolsky 2016

2
皮肉は気が散る。
cambunctious

12

1つの良いハックは別の価値があります...私は通常、ハッキーメソッドに未使用の変数を導入することによって、説明された目的のためにコンパイラ警告を生成します。

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

この未使用の変数は、(コンパイラーによっては)次のような警告を生成します。

警告:ローカル変数FIXMEtemporaryHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixedが読み取られることはありません。

このソリューションはカスタムアノテーションほど優れていませんが、事前の準備が不要であるという利点があります(コンパイラーが未使用の変数に対して警告を発行するように既に構成されている場合)。このアプローチは、短期間のハッキングにのみ適していることをお勧めします。長期にわたるハッキングの場合、カスタムアノテーションを作成する努力は正当化されると私は主張します。


未使用の変数警告を有効にする方法を知っていますか?コマンドラインからGradleを使用してAndroid向けにビルドしていますが、未使用の変数に関する警告は表示されません。これをで有効にする方法を知っています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 2015年

@PaulBelloraはそれがどのように役立つかわかりませんが、大丈夫です
Artem Zinnatullin 2015年


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();
     }
}

「javacSample.javaSampleCaller.java」を実行すると、次の出力が得られます。

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

私はsunのjavac1.6を使用しています。メモだけでなく、正直な善意の警告が必要な場合は、-Xlintオプションを使用してください。たぶんそれはAntを通して適切に浸透するでしょう。


@Deprecateを使用してコンパイラからエラーが発生することはないようです。サンプルコードでqを編集します。
pimlottc 2009年

1
うーん。あなたの例は非推奨のメソッドのみを示しています。どこでその方法を使いますか?ここに警告が表示されます。
Peter Recore

3
ちなみに、@Deprecatedクラス間でのみ機能します(したがって、プライベートメソッドには役に立ちません)。
npostavs 2012年

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)

その場合、循環JAR依存関係が作成されるためAnnProcessor、のタイプを@Ann直接参照することはできません。これは、あくまでも参考にできるようになる@AnnことでString名またはTypeElement/ TypeMirror


これは、注釈プロセッサを作成するための最良の方法ではありません。通常、Set<? extends TypeElement>パラメータから注釈タイプを取得してから、を使用して特定のラウンドの注釈付き要素を取得しgetElementsAnnotatedWith(TypeElement annotation)ます。また、なぜprintMessageメソッドをラップしたのかわかりません。
ThePyroEagle 2016年

@ThePyroEagle 2つのオーバーロードのどちらを選択するかは、コーディングスタイルのわずかな違いです。
radiodef 2016年

理想的には、プロセッサJARに注釈プロセッサを入れたいだけではないでしょうか。前述のメソッドを使用すると、クラスパスに処理済みのアノテーションを含める必要がないため、そのレベルの分離が可能になります。
ThePyroEagle 2016年

2

ここに注釈に関するチュートリアルを示し、下部に独自の注釈を定義する例を示します。残念ながら、チュートリアルをざっと見てみると、これらはjavadocでしか利用できないとのことです...

コンパイラーが使用するアノテーション言語仕様自体によって事前定義されているアノテーション・タイプには、@ Deprecated、@ Override、および@SuppressWarningsの3つがあります。

したがって、実際にできることは、コンパイラが出力する@Deprecatedタグを挿入するか、ハッキングについて通知するカスタムタグをjavadocsに配置することだけであるように見えます。


また、コンパイラは、@ Deprecatedでマークしたメソッドがそうだという警告を発します...それはユーザーにそれがどの問題を引き起こしているのかを知らせます。
マットフィリップス

1

IntelliJを使用している場合。[設定]> [エディター]> [TODO]に移動して、「\ bhack.b *」またはその他のパタ​​ーンを追加できます。

その後、次のようなコメントをすると // HACK: temporary fix to work around server issues

次に、TODOツールウィンドウに、編集中に他のすべての定義済みパターンと一緒にきれいに表示されます。


0

コンパイルには、ant oumavenのようなツールを使用する必要があります。これを使用すると、コンパイル時にいくつかのタスクを定義する必要があります。これにより、たとえば、FIXMEタグに関するログ(メッセージや警告など)が生成される可能性があります。

また、エラーが必要な場合は、それも可能です。コードにTODOを残したときにコンパイルを停止するように(なぜですか?)


ハックはそれをできるだけ早く動作させることです、私は今ビルドシステムを変更する時間が正確にはありません:)しかし将来のために考えるのは良いことです...
pimlottc 2009年

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.