88

在我们等待外部资源修复时,我将提交一个丑陋的临时黑客以解决阻塞问题。除了用一个可怕的评论和一堆 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 脚本运行)中没有编译器或运行时错误,它肯定在执行该函数。

4

11 回答 11

89

我认为由编译器处理的自定义注释是解决方案。我经常编写自定义注释来在运行时做事,但我从未尝试在编译时使用它们。因此,我只能为您提供有关您可能需要的工具的指示:

  • 编写自定义注释类型。本页介绍如何编写注释。
  • 编写一个注释处理器,处理您的自定义注释以发出警告。运行此类注释处理器的工具称为 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
于 2009-11-18T09:48:38.060 回答
42

我见过的一种技术是将其与单元测试联系起来(你单元测试,对吧?)。基本上,您创建一个单元测试,一旦外部资源修复完成,该单元测试就会失败。然后,您对该单元测试进行评论,以告诉其他人在问题解决后如何撤消您的粗糙破解。

这种方法的真正巧妙之处在于,撤消黑客攻击的触发器是对核心问题本身的修复。

于 2009-11-18T02:44:17.213 回答
15

一些快速且不那么肮脏的方法可能是使用@SuppressWarnings带有故意错误String参数的注释:

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

这将生成一个警告,因为编译器不会将其识别为要抑制的特定警告:

不支持 @SuppressWarnings("FIXME: 这是一个 hack,应该修复。")

于 2015-02-20T21:16:34.743 回答
12

一个好的 hack 值得另一个......我通常通过在 hacky 方法中引入一个未使用的变量来为所描述的目的生成编译器警告,因此:

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

这个未使用的变量将产生一个警告(取决于你的编译器)看起来像这样:

警告:永远不会读取局部变量 FIXMEtemporaryHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixed。

此解决方案不如自定义注释好,但它的优点是不需要提前准备(假设编译器已配置为对未使用的变量发出警告)。我建议这种方法只适用于短暂的黑客攻击。对于长期存在的黑客,我认为创建自定义注释的努力是合理的。

于 2011-07-21T17:43:33.990 回答
10

我写了一个用注解来做这件事的库:Lightweight Javac @Warning Annotation

用法很简单:

// some code...

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

编译器会在你的文本中抛出警告信息

于 2015-01-10T00:02:27.447 回答
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 中。

于 2009-11-17T23:30:23.340 回答
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();
    }
    

    标记错误

这是一篇博客文章,我发现这对开始这个主题很有帮助:


小提示:下面的评论者指出的是因为MarkerProcessorreferences 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

于 2015-04-26T18:12:16.770 回答
2

这里展示了一个关于注解的教程,并在底部给出了一个定义你自己的注解的例子。不幸的是,快速浏览本教程说这些仅在 javadoc 中可用...

编译器使用的注解 语言规范本身预定义了三种注解类型:@Deprecated、@Override 和@SuppressWarnings。

所以看起来你真正能做的就是抛出一个@Deprecated标签,编译器将打印出这个标签,或者在javadocs中放置一个自定义标签来讲述黑客攻击。

于 2009-11-17T23:57:08.640 回答
1

如果您使用的是 IntelliJ。您可以转到:Preferences>Editor>TODO 并添加“\bhack.b*”或任何其他模式。

如果您随后发表评论,例如// HACK: temporary fix to work around server issues

然后在 TODO 工具窗口中,它会与您定义的所有其他模式一起很好地显示,同时进行编辑。

于 2017-01-27T09:08:14.057 回答
0

你应该使用一个工具来编译,比如ant ou maven。例如,使用它,您应该在编译时定义一些任务,这些任务可能会产生一些关于您的 FIXME 标记的日志(如消息或警告)。

如果你想要一些错误,它也是可能的。就像在代码中留下一些 TODO 时停止编译(为什么不呢?)

于 2009-11-17T23:46:50.283 回答
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
于 2016-12-23T14:45:59.967 回答