의도적으로 사용자 정의 Java 컴파일러 경고 메시지를 발생시키는 방법은 무엇입니까?


84

외부 리소스가 수정되기를 기다리는 동안 차단 문제를 해결하기 위해 추악한 임시 해킹을 저 지르려고합니다. 무시 무시한 댓글과 많은 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 또는 sun javac 1.6 (ant 스크립트에서 실행)에서 컴파일러 또는 런타임 오류가 없으며 확실히 함수를 실행하고 있습니다.


1
참고 : @Deprecated 는 컴파일러 또는 런타임 오류가 아닌 컴파일러 경고 만 제공합니다 . 코드는 확실히 실행되어야합니다
BalusC

javac로 직접 실행 해보십시오. Ant가 일부 출력을 숨기고 있다고 생각합니다. 또는 자세한 내용은 아래 업데이트 된 답변을 참조하십시오.
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의 서비스 제공 업체 기능을 사용하여 사용을 단순화했습니다. 사실, 내 솔루션은 커스텀 어노테이션과 어노테이션 프로세서의 두 가지 클래스를 포함하는 항아리입니다. 그것을 사용하려면 프로젝트의 클래스 경로에이 항아리를 추가하고 원하는대로 주석을 달아주세요! 이것은 내 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를 서비스 제공자로 사용하려면 jar에 파일 META-INF/services/javax.annotation.processing.Processor을 추가하십시오 . 이 파일은 다음 텍스트를 포함해야하는 acsii 파일입니다.

fr.barjak.hack_processor.Processor

3
+1, 훌륭한 연구! 이것은 확실히 "올바른 방법"이며 (단위 테스트가 실용적이지 않은 경우) 일반 경고보다 눈에 띄는 이점이 있습니다.
Yishai

1
javac의 경고를 방출,하지만 아무것도 이클립스 발생하지 (?)
fwonce

8
작은 노트 : 오버라이드 (override) 할 필요가 없습니다 init및 설정 env필드 - 당신은 얻을 수 ProcessingEnvironment에서 this.processingEnv그 이후 protected.
Paul Bellora 2012-06-28

이 경고 메시지가 IDE 경고에 표시됩니까?
uylmz

4
주석 처리는 Eclipse에서 기본적으로 꺼져 있습니다. 이를 켜려면 프로젝트 속성-> Java 컴파일러-> 주석 처리-> 주석 처리 활성화로 이동합니다. 그런 다음 해당 페이지 아래에는 사용할 프로세서가있는 jar를 구성해야하는 "Factory Path"라는 페이지가 있습니다.
Konstantin Komissarchik 2014

14

일부 빠르고 더러운 접근 방식 @SuppressWarnings은 의도적으로 잘못된 String인수가 있는 주석 을 사용하는 것일 수 있습니다 .

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

컴파일러에서 억제 할 특정 경고로 인식하지 않기 때문에 경고가 생성됩니다.

지원되지 않는 @SuppressWarnings ( "FIXME : 이것은 해킹이며 수정되어야합니다.")


4
필드 가시성 경고 또는 Lint 오류를 억제하는 데는 작동하지 않습니다.
IgorGanapolsky

2
아이러니는 산만하다.
cambunctious

12

하나의 좋은 해킹은 또 다른 가치가 있습니다 ... 저는 일반적으로 hacky 메서드에 사용되지 않는 변수를 도입하여 설명 된 목적을 위해 컴파일러 경고를 생성합니다.

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

이 미사용 변수는 (컴파일러에 따라) 다음과 같은 경고를 생성합니다 :

경고 : 로컬 변수 FIXMEtemporaryHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixed는 읽지 않습니다.

이 솔루션은 사용자 지정 주석만큼 좋지는 않지만 사전 준비가 필요하지 않다는 장점이 있습니다 (컴파일러가 이미 사용되지 않은 변수에 대한 경고를 발행하도록 구성되어 있다고 가정). 이 접근 방식은 단기 해킹에만 적합하다고 제안합니다. 오래 지속되는 해킹의 경우 사용자 지정 주석을 만드는 노력이 정당하다고 주장합니다.


사용하지 않는 변수 경고를 활성화하는 방법을 알고 있습니까? 명령 줄에서 Gradle을 사용하여 Android 용으로 빌드 중이며 사용하지 않는 변수에 대한 경고가 표시되지 않습니다. 이것이 어떻게 활성화 될 수 있는지 아십니까 build.gradle?
Andreas

@Andreas 죄송합니다. 해당 환경 / 툴체인에 대해 아무것도 모릅니다. 이 주제에 대한 질문이 아직없는 경우 질문을 고려할 수 있습니다.
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는 당신을 도울 것입니다 방법을 알고 있지만하지 않는 괜찮
아르 템 Zinnatullin


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.

나는 sun의 javac 1.6을 사용하고 있습니다. 단순한 메모가 아닌 정직한 경고를 원한다면 -Xlint 옵션을 사용하십시오. 아마도 그것은 Ant를 통해 제대로 스며들 것입니다.


@Deprecate로 컴파일러에서 오류가 발생하지 않는 것 같습니다. 예제 코드로 내 q를 편집하십시오.
pimlottc

1
흠. 귀하의 예는 더 이상 사용되지 않는 방법만을 보여줍니다. 방법을 어디에서 사용합니까? 경고가 표시되는 곳입니다.
Peter Recore

3
기록을 위해 @Deprecated클래스간에 만 작동합니다 (개인 메서드에는 쓸모가 없습니다).
npostavs

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

    마커 오류

다음은 주제를 시작하는 데 매우 도움이되는 블로그 게시물입니다.


작은 메모 : 아래 주석가가 지적한 것은 MarkerProcessorreference 이므로 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

@ThePyroEagle 두 오버로드 사이의 선택은 확실히 코딩 스타일에있어서 아주 작은 차이입니다.
Radiodef

그러나 이상적으로는 프로세서 JAR에 주석 프로세서를 포함하고 싶지 않습니까? 앞서 언급 한 방법을 사용하면 클래스 경로에 처리 된 주석이 필요하지 않으므로 해당 수준의 격리가 가능합니다.
ThePyroEagle

2

다음 은 주석에 대한 자습서를 보여 주며 하단에는 고유 한 주석을 정의하는 예가 나와 있습니다. 불행히도 튜토리얼을 빠르게 훑어 보면 javadoc에서만 사용할 수 있다고 말했습니다.

컴파일러에서 사용하는 주석 언어 사양 자체에 의해 사전 정의 된 주석 유형에는 @Deprecated, @Override 및 @SuppressWarnings의 세 가지가 있습니다.

따라서 실제로 할 수있는 일은 컴파일러가 인쇄하거나 해킹에 대해 알려주는 사용자 정의 태그를 javadocs에 넣는 @Deprecated 태그를 삽입하는 것입니다.


또한 컴파일러는 @Deprecated로 표시 한 메서드가 그렇게된다는 경고를 내 보냅니다.
Matt Phillips

1

IntelliJ를 사용하는 경우. Preferences> Editor> TODO로 이동하여 "\ bhack.b *"또는 다른 패턴을 추가 할 수 있습니다.

그런 다음 다음과 같은 의견을 남기면 // HACK: temporary fix to work around server issues

그런 다음 TODO 도구 창에서 편집하는 동안 정의 된 다른 모든 패턴과 함께 멋지게 표시됩니다.


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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.