Làm thế nào để cố ý gây ra thông báo cảnh báo trình biên dịch java tùy chỉnh?


83

Tôi sắp thực hiện một cuộc tấn công tạm thời xấu xí để giải quyết vấn đề chặn trong khi chúng tôi chờ tài nguyên bên ngoài được khắc phục. Ngoài việc đánh dấu nó bằng một bình luận đáng sợ lớn và một loạt FIXME, tôi muốn trình biên dịch đưa ra một thông báo cảnh báo rõ ràng như một lời nhắc nhở để chúng tôi không quên loại bỏ điều này. Ví dụ, một cái gì đó như:

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

Có cách nào tôi có thể gây ra cảnh báo trình biên dịch có chủ ý với thông báo do tôi chọn không? Không thực hiện được, điều dễ dàng nhất để thêm vào mã để đưa ra một cảnh báo hiện có, có lẽ là một thông báo trong một chuỗi trên dòng vi phạm để nó được in trong thông báo cảnh báo?

CHỈNH SỬA: Các thẻ không dùng nữa dường như không làm được gì đối với tôi:

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

Không có lỗi trình biên dịch hoặc thời gian chạy trong eclipse hoặc từ sun javac 1.6 (chạy từ tập lệnh ant) ​​và nó chắc chắn đang thực thi chức năng.


1
FYI: @Deprecated chỉ đưa ra cảnh báo trình biên dịch , không phải là lỗi trình biên dịch hoặc thời gian chạy. Mã chắc chắn sẽ chạy
BalusC

Hãy thử chạy trực tiếp với javac. Tôi nghi ngờ Ant đang ẩn một số đầu ra. Hoặc xem câu trả lời cập nhật của tôi bên dưới để biết thêm chi tiết.
Peter Recore

Câu trả lời:


42

Một kỹ thuật mà tôi đã thấy được sử dụng là gắn điều này vào kiểm thử đơn vị (bạn thực hiện kiểm thử đơn vị, phải không?). Về cơ bản, bạn tạo một bài kiểm tra đơn vị không thành công khi đã đạt được bản sửa lỗi tài nguyên bên ngoài. Sau đó, bạn nhận xét bài kiểm tra đơn vị đó để cho người khác biết cách hoàn tác vụ hack gnarly của bạn sau khi vấn đề được giải quyết.

Điều thực sự hấp dẫn về cách tiếp cận này là kích hoạt để hoàn tác vụ hack của bạn là một bản sửa lỗi của chính vấn đề cốt lõi.


2
Tôi đã nghe nói về điều này tại một trong những hội nghị No Fluff Just Stuff (không thể nhớ người trình bày là ai). Tôi nghĩ nó khá trơn. Tuy nhiên, tôi chắc chắn đề nghị những hội nghị đó.
Ngày Kevin

3
Tôi muốn xem một ví dụ về cách tiếp cận này
birgersp

Câu trả lời đã cũ 11 tuổi, nhưng tôi thậm chí còn tiến thêm một bước nữa: nhận xét các bài kiểm tra đơn vị là nguy hiểm. Tôi sẽ tạo một bài kiểm tra đơn vị đóng gói hành vi không mong muốn, theo cách đó khi cuối cùng nó được sửa, lời khen sẽ ngắt.
trung bình

86

Tôi nghĩ rằng một chú thích tùy chỉnh, sẽ được xử lý bởi trình biên dịch, là giải pháp. Tôi thường xuyên viết các chú thích tùy chỉnh để thực hiện mọi việc trong thời gian chạy, nhưng tôi chưa bao giờ cố gắng sử dụng chúng trong thời gian biên dịch. Vì vậy, tôi chỉ có thể cung cấp cho bạn những gợi ý về các công cụ bạn có thể cần:

  • Viết một loại chú thích tùy chỉnh. Trang này giải thích cách viết chú thích.
  • Viết một trình xử lý chú thích, xử lý chú thích tùy chỉnh của bạn để phát ra cảnh báo. Công cụ chạy các bộ xử lý chú thích như vậy được gọi là APT. Bạn có thể tìm thấy phần giới thiệu trên trang này . Tôi nghĩ những gì bạn cần trong APT API là AnnotationProcessorEnosystem, sẽ cho phép bạn phát ra cảnh báo.
  • Từ Java 6, APT được tích hợp vào javac. Đó là, bạn có thể thêm bộ xử lý chú thích trong dòng lệnh javac. Phần này của hướng dẫn sử dụng javac sẽ cho bạn biết cách gọi bộ xử lý chú thích tùy chỉnh của bạn.

Tôi không biết giải pháp này có thực sự khả thi hay không. Tôi sẽ cố gắng tự thực hiện khi có thời gian.

Biên tập

Tôi đã thực hiện thành công giải pháp của mình. Và như một phần thưởng, tôi đã sử dụng cơ sở cung cấp dịch vụ của java để đơn giản hóa việc sử dụng nó. Trên thực tế, giải pháp của tôi là một jar chứa 2 lớp: chú thích tùy chỉnh và bộ xử lý chú thích. Để sử dụng nó, chỉ cần thêm jar này trong classpath của dự án của bạn và chú thích bất cứ điều gì bạn muốn! Điều này đang hoạt động tốt ngay bên trong IDE của tôi (NetBeans).

Mã của chú thích:

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 {

}

Mã của bộ xử lý:

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

}

Để kích hoạt jar kết quả làm nhà cung cấp dịch vụ, hãy thêm tệp META-INF/services/javax.annotation.processing.Processorvào jar. Tệp này là tệp acsii phải chứa văn bản sau:

fr.barjak.hack_processor.Processor

3
+1, Nghiên cứu tuyệt vời! Đây chắc chắn là "cách đúng" để làm điều đó (nếu một thử nghiệm đơn vị không thực tế), và nó có lợi thế là nổi bật hơn và cao hơn các cảnh báo thông thường.
Yishai

1
javac phát ra một cảnh báo, nhưng không có gì xảy ra trong nhật thực (?)
fwonce

8
Lưu ý nhỏ: không cần ghi đè initvà đặt envtrường - bạn có thể lấy ProcessingEnvironmenttừ this.processingEnvđó protected.
Paul Bellora

Thông báo cảnh báo này có hiển thị trên các cảnh báo IDE không?
uylmz

4
Xử lý chú thích bị tắt theo mặc định trong Eclipse. Để bật tính năng này, hãy chuyển đến Thuộc tính dự án -> Trình biên dịch Java -> Xử lý chú thích -> Bật xử lý chú thích. Sau đó, bên dưới trang đó là một trang có tên là "Đường dẫn nhà máy", nơi bạn sẽ cần phải định cấu hình các lọ có bộ xử lý bạn muốn sử dụng.
Konstantin Komissarchik

14

Một số cách tiếp cận nhanh chóng và không quá bẩn, có thể là sử dụng @SuppressWarningschú thích với một Stringlập luận cố tình sai :

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

Điều này sẽ tạo ra một cảnh báo vì nó không được trình biên dịch nhận dạng là một cảnh báo cụ thể để loại bỏ:

@SuppressWarnings không được hỗ trợ ("FIXME: đây là một bản hack và cần được khắc phục.")


4
Nó không hoạt động trong việc ngăn chặn các cảnh báo hiển thị trường hoặc lỗi xơ vải.
IgorGanapolsky

2
Điều trớ trêu là mất tập trung.
ăn ngon miệng

12

Một bản hack tốt xứng đáng với một bản hack khác ... Tôi thường tạo cảnh báo trình biên dịch cho mục đích được mô tả bằng cách đưa vào một biến không được sử dụng trong phương thức hacky, do đó:

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

Biến không sử dụng này sẽ tạo ra một cảnh báo (tùy thuộc vào trình biên dịch của bạn) sẽ giống như sau:

CẢNH BÁO: Biến cục bộ FIXMEtemporaryHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixed không bao giờ được đọc.

Giải pháp này không đẹp bằng chú thích tùy chỉnh, nhưng nó có ưu điểm là không cần chuẩn bị trước (giả sử trình biên dịch đã được cấu hình để đưa ra cảnh báo cho các biến không sử dụng). Tôi đề nghị rằng cách tiếp cận này chỉ phù hợp với các bản hack tồn tại trong thời gian ngắn. Đối với các bản hack tồn tại lâu, tôi cho rằng nỗ lực tạo chú thích tùy chỉnh sẽ là chính đáng.


Bạn có biết cách bật cảnh báo biến không sử dụng không? Tôi đang xây dựng cho Android với Gradle từ dòng lệnh và tôi không nhận được bất kỳ cảnh báo nào cho các biến không sử dụng. Bạn có biết làm thế nào điều này có thể được kích hoạt trong build.gradle?
Andreas

@Andreas Xin lỗi, tôi không biết gì về môi trường / chuỗi công cụ đó. Nếu chưa có câu hỏi SO về chủ đề này, bạn có thể cân nhắc hỏi một câu hỏi.
WReach

10

Tôi đã viết một thư viện thực hiện việc này với các chú thích: Lightweight Javac @Warning Annotation

Cách sử dụng rất đơn giản:

// some code...

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

Và trình biên dịch sẽ đưa ra thông báo cảnh báo với văn bản của bạn


Vui lòng thêm tuyên bố từ chối trách nhiệm rằng bạn là tác giả của thư viện được đề xuất.
Paul Bellora

@PaulBellora không biết nó sẽ giúp bạn như thế nào, nhưng không sao
Artem Zinnatullin


5

làm thế nào về việc đánh dấu phương thức hoặc lớp là @Deprecated? tài liệu ở đây . Lưu ý rằng có cả @Deprecated và @deprecated - phiên bản D viết hoa là chú thích và d viết thường là phiên bản javadoc. Phiên bản javadoc cho phép bạn chỉ định một chuỗi tùy ý giải thích những gì đang xảy ra. Nhưng các trình biên dịch không bắt buộc phải phát ra cảnh báo khi nhìn thấy nó (mặc dù nhiều người làm như vậy). Chú thích phải luôn gây ra cảnh báo, mặc dù tôi không nghĩ rằng bạn có thể thêm giải thích cho nó.

CẬP NHẬT đây là mã tôi vừa thử nghiệm với: Sample.java chứa:

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

SampleCaller.java chứa:

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

khi tôi chạy "javac Sample.java SampleCaller.java", tôi nhận được kết quả sau:

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

Tôi đang sử dụng javac 1.6 của sun. Nếu bạn muốn một cảnh báo trung thực với lòng tốt thay vì chỉ là một ghi chú, hãy sử dụng tùy chọn -Xlint. Có lẽ điều đó sẽ thấm qua Ant đúng cách.


Tôi dường như không gặp lỗi từ trình biên dịch với @Deprecate; chỉnh sửa q của tôi với mã ví dụ.
pimlottc 18/11/09

1
hmm. ví dụ của bạn chỉ hiển thị phương pháp không dùng nữa. bạn sử dụng phương pháp ở đâu? đó là nơi cảnh báo sẽ hiển thị.
Peter Recore

3
Đối với bản ghi, @Deprecatedchỉ hoạt động trên các lớp (vì vậy nó vô dụng với các phương thức private).
npostavs

4

Chúng tôi có thể làm điều này với các chú thích!

Để nêu ra lỗi, hãy sử dụng Messagerđể gửi tin nhắn với Diagnostic.Kind.ERROR. Ví dụ ngắn gọn:

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

Đây là một chú thích khá đơn giản mà tôi đã viết chỉ để kiểm tra điều này.

@MarkerChú thích này cho biết mục tiêu là một giao diện đánh dấu:

package marker;

import java.lang.annotation.*;

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

Và bộ xử lý chú thích gây ra lỗi nếu nó không phải là:

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

Ví dụ, đây là những cách sử dụng chính xác của @Marker:

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

Nhưng những cách sử dụng @Markernày sẽ gây ra lỗi trình biên dịch:

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

    lỗi đánh dấu

Đây là một bài đăng trên blog mà tôi thấy rất hữu ích khi bắt đầu về chủ đề này:


Lưu ý nhỏ: điều mà người bình luận bên dưới chỉ ra là vì MarkerProcessortham chiếu Marker.classnên nó phụ thuộc vào thời gian biên dịch. Tôi đã viết ví dụ trên với giả định rằng cả hai lớp sẽ đi trong cùng một tệp JAR (giả sử,marker.jar ), nhưng điều đó không phải lúc nào cũng có thể.

Ví dụ: giả sử có một JAR ứng dụng với các lớp sau:

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

Sau đó, bộ xử lý cho @Anntồn tại trong một JAR riêng biệt, được sử dụng trong khi biên dịch ứng dụng JAR:

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

Trong trường hợp đó, AnnProcessorsẽ không thể tham chiếu @Anntrực tiếp loại của vì nó sẽ tạo ra một phụ thuộc JAR vòng tròn. Nó sẽ chỉ có thể tham chiếu @Annbằng Stringtên hoặc TypeElement/ TypeMirror.


Đó không phải là cách tốt nhất để viết bộ xử lý chú thích. Bạn thường lấy loại chú thích từ Set<? extends TypeElement>tham số và sau đó lấy các phần tử chú thích cho vòng đã cho bằng cách sử dụng getElementsAnnotatedWith(TypeElement annotation). Tôi cũng không hiểu tại sao bạn gói printMessagephương pháp.
ThePyroEagle

@ThePyroEagle Sự lựa chọn giữa hai quá tải chắc chắn là một sự khác biệt khá nhỏ trong phong cách viết mã.
Radiodef

Đó là nhưng lý tưởng nhất, bạn sẽ không muốn chỉ có bộ xử lý chú thích trong bộ xử lý JAR? Sử dụng các phương pháp đã đề cập trước đây cho phép mức độ cô lập đó vì bạn không cần phải có chú thích đã xử lý trong đường dẫn phân nhánh.
ThePyroEagle

2

Ở đây hiển thị hướng dẫn về chú thích và ở dưới cùng, nó đưa ra một ví dụ về cách xác định chú thích của riêng bạn. Thật không may khi đọc lướt nhanh hướng dẫn nói rằng những thứ đó chỉ có sẵn trong javadoc ...

Chú thích được sử dụng bởi trình biên dịch Có ba loại chú thích được xác định trước bởi chính đặc tả ngôn ngữ: @Deprecated, @Override và @SuppressWarnings.

Vì vậy, có vẻ như tất cả những gì bạn thực sự có thể làm là ném vào một thẻ @Deprecated mà trình biên dịch sẽ in ra hoặc đặt một thẻ tùy chỉnh trong javadocs cho biết về vụ hack.


cũng như trình biên dịch sẽ phát ra một cảnh báo nói rằng phương pháp bạn đánh dấu bằng @Deprecated là như vậy ... Nó sẽ cho người dùng biết đó là phương pháp vi phạm nào.
Matt Phillips

1

Nếu bạn đang sử dụng IntelliJ. Bạn có thể đi tới: Preferences> Editor> TODO và thêm "\ bhack.b *" hoặc bất kỳ mẫu nào khác.

Nếu sau đó bạn đưa ra nhận xét như // HACK: temporary fix to work around server issues

Sau đó, trong cửa sổ công cụ TODO, nó sẽ hiển thị độc đáo, cùng với tất cả các mẫu đã xác định khác của bạn, trong khi chỉnh sửa.


0

Bạn nên sử dụng một công cụ để biên dịch, chẳng hạn như ant ou maven. Với nó, bạn nên xác định một số tác vụ tại thời điểm biên dịch có thể tạo ra một số nhật ký (như tin nhắn hoặc cảnh báo) về thẻ FIXME của bạn, chẳng hạn.

Và nếu bạn muốn một số lỗi, nó cũng có thể. Giống như dừng biên dịch khi bạn đã để lại một số VIỆC CẦN LÀM trong mã của mình (tại sao không?)


Hack là để làm cho nó làm việc càng sớm càng tốt, tôi không chính xác có thời gian để thay đổi hệ thống xây dựng ngay bây giờ :) Nhưng tốt để suy nghĩ về tương lai ...
pimlottc

0

Để có bất kỳ cảnh báo nào xuất hiện, tôi nhận thấy rằng các biến không sử dụng và @SuppressWarnings tùy chỉnh không hoạt động với tôi, nhưng một diễn viên không cần thiết đã làm:

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

Bây giờ khi tôi biên dịch:

$ javac -Xlint:all Example.java
ExampleTest.java:12: warning: [cast] redundant cast to String
        String s = (String) "Hello!";
                   ^
1 warning
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.