Ich bin dabei, einen hässlichen temporären Hack zu begehen, um ein Blockierungsproblem zu umgehen, während wir darauf warten, dass eine externe Ressource behoben wird. Abgesehen davon, dass ich es mit einem großen, beängstigenden Kommentar und einer Reihe von FIXMEs markiert habe, möchte ich, dass der Compiler eine offensichtliche Warnmeldung als Erinnerung ausgibt, damit wir nicht vergessen, dies herauszunehmen. Zum Beispiel so etwas wie:
[javac] com.foo.Hacky.java:192: warning: FIXME temporary hack to work around library bug, remove me when library is fixed!
Gibt es eine Möglichkeit, eine absichtliche Compiler-Warnung mit einer Nachricht meiner Wahl auszulösen? Wenn dies nicht der Fall ist, was ist am einfachsten zum Code hinzuzufügen, um eine vorhandene Warnung auszulösen, möglicherweise mit einer Nachricht in einer Zeichenfolge in der betreffenden Zeile, damit sie in der Warnmeldung gedruckt wird?
EDIT: Veraltete Tags scheinen nichts für mich zu tun:
/**
* @deprecated "Temporary hack to work around remote server quirks"
*/
@Deprecated
private void doSomeHackyStuff() { ... }
Keine Compiler- oder Laufzeitfehler in Eclipse oder Sun Javac 1.6 (ausgeführt von Ant Script), und die Funktion wird definitiv ausgeführt.
quelle
Antworten:
Eine Technik, die ich gesehen habe, besteht darin, dies in Unit-Tests einzubinden (Sie machen Unit-Tests, oder?). Grundsätzlich erstellen Sie einen Komponententest, der fehlschlägt, sobald der Fix für externe Ressourcen erreicht ist. Dann kommentieren Sie diesen Komponententest, um anderen zu sagen, wie Sie Ihren knorrigen Hack rückgängig machen können, sobald das Problem behoben ist.
Was an diesem Ansatz wirklich klug ist, ist, dass der Auslöser für das Rückgängigmachen Ihres Hacks eine Lösung des Kernproblems selbst ist.
quelle
Ich denke, dass eine benutzerdefinierte Anmerkung, die vom Compiler verarbeitet wird, die Lösung ist. Ich schreibe häufig benutzerdefinierte Anmerkungen, um Dinge zur Laufzeit zu erledigen, aber ich habe nie versucht, sie zur Kompilierungszeit zu verwenden. Daher kann ich Ihnen nur Hinweise auf die Tools geben, die Sie möglicherweise benötigen:
Ich weiß nicht, ob diese Lösung wirklich praktikabel ist. Ich werde versuchen, es selbst zu implementieren, wenn ich etwas Zeit finde.
Bearbeiten
Ich habe meine Lösung erfolgreich implementiert. Und als Bonus habe ich die Service Provider-Funktion von Java verwendet, um die Verwendung zu vereinfachen. Eigentlich ist meine Lösung ein JAR, das zwei Klassen enthält: die benutzerdefinierte Annotation und den Annotation-Prozessor. Um es zu verwenden, fügen Sie dieses Glas einfach in den Klassenpfad Ihres Projekts ein und kommentieren Sie, was Sie wollen! Dies funktioniert direkt in meiner IDE (NetBeans).
Code der Anmerkung:
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 { }
Code des Prozessors:
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; } }
Fügen Sie die Datei
META-INF/services/javax.annotation.processing.Processor
in das JAR ein, um das resultierende JAR als Dienstanbieter zu aktivieren . Diese Datei ist eine acsii-Datei, die den folgenden Text enthalten muss:quelle
init
dasenv
Feld zu überschreiben und festzulegen - Sie können dasProcessingEnvironment
von erhalten,this.processingEnv
da es istprotected
.Ein schneller und nicht so schmutziger Ansatz könnte darin bestehen, eine
@SuppressWarnings
Anmerkung mit einem absichtlich falschenString
Argument zu verwenden:@SuppressWarnings("FIXME: this is a hack and should be fixed.")
Dies generiert eine Warnung, da sie vom Compiler nicht als spezifische Warnung zum Unterdrücken erkannt wird:
quelle
Ein guter Hack verdient einen anderen ... Normalerweise generiere ich Compiler-Warnungen für den beschriebenen Zweck, indem ich eine nicht verwendete Variable in die Hacky-Methode einführe.
/** * @deprecated "Temporary hack to work around remote server quirks" */ @Deprecated private void doSomeHackyStuff() { int FIXMEtemporaryHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixed; ... }
Diese nicht verwendete Variable generiert eine Warnung, die (abhängig von Ihrem Compiler) ungefähr so aussieht:
Diese Lösung ist nicht so gut wie eine benutzerdefinierte Annotation, hat jedoch den Vorteil, dass keine vorherige Vorbereitung erforderlich ist (vorausgesetzt, der Compiler ist bereits so konfiguriert, dass Warnungen für nicht verwendete Variablen ausgegeben werden). Ich würde vorschlagen, dass dieser Ansatz nur für kurzlebige Hacks geeignet ist. Für langlebige Hacks würde ich argumentieren, dass der Versuch, eine benutzerdefinierte Anmerkung zu erstellen, gerechtfertigt wäre.
quelle
build.gradle
?Ich habe eine Bibliothek geschrieben, die dies mit Anmerkungen tut: Lightweight Javac @Warning Annotation
Die Verwendung ist sehr einfach:
// some code... @Warning("This method should be refactored") public void someCodeWhichYouNeedAtTheMomentButYouWantToRefactorItLater() { // bad stuff going on here... }
Und der Compiler gibt eine Warnmeldung mit Ihrem Text aus
quelle
Wie wäre es, die Methode oder Klasse als @Deprecated zu markieren? Dokumente hier . Beachten Sie, dass es sowohl ein @Deprecated als auch ein @deprecated gibt - die Großbuchstaben-D-Version ist die Anmerkung und das Kleinbuchstaben-D ist die Javadoc-Version. In der Javadoc-Version können Sie eine beliebige Zeichenfolge angeben, die erklärt, was gerade passiert. Compiler müssen jedoch keine Warnung ausgeben, wenn sie diese sehen (obwohl dies viele tun). Die Anmerkung sollte immer eine Warnung verursachen, obwohl ich nicht glaube, dass Sie eine Erklärung hinzufügen können.
UPDATE hier ist der Code, mit dem ich gerade getestet habe: Sample.java enthält:
public class Sample { @Deprecated public static void foo() { System.out.println("I am a hack"); } }
SampleCaller.java enthält:
public class SampleCaller{ public static void main(String [] args) { Sample.foo(); } }
Wenn ich "javac Sample.java SampleCaller.java" ausführe, erhalte ich die folgende Ausgabe:
Note: SampleCaller.java uses or overrides a deprecated API. Note: Recompile with -Xlint:deprecation for details.
Ich benutze Sun's Javac 1.6. Wenn Sie eine Warnung vor Ehrlichkeit und nicht nur eine Notiz wünschen, verwenden Sie die Option -Xlint. Vielleicht sickert das richtig durch Ant.
quelle
@Deprecated
nur klassenübergreifend (daher ist es für private Methoden nutzlos).Wir können dies mit Anmerkungen tun!
Um einen Fehler auszulösen
Messager
, senden Sie eine Nachricht mitDiagnostic.Kind.ERROR
. Kurzes Beispiel:processingEnv.getMessager().printMessage( Diagnostic.Kind.ERROR, "Something happened!", element);
Hier ist eine ziemlich einfache Anmerkung, die ich geschrieben habe, um dies zu testen.
Diese
@Marker
Anmerkung gibt an, dass das Ziel eine Markierungsschnittstelle ist:package marker; import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Marker { }
Und der Anmerkungsprozessor verursacht einen Fehler, wenn dies nicht der Fall ist:
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)); } } }
Dies sind beispielsweise korrekte Verwendungen von
@Marker
:@Marker interface Example {}
@Marker interface Example extends Serializable {}
Diese Verwendungen von
@Marker
verursachen jedoch einen Compilerfehler:@Marker class Example {}
@Marker interface Example { void method(); }
Hier ist ein Blog-Beitrag, den ich sehr hilfreich fand, um mit dem Thema zu beginnen:
Kleiner Hinweis: Der Kommentator unten weist darauf hin, dass er aufgrund von
MarkerProcessor
ReferenzenMarker.class
eine Abhängigkeit von der Kompilierungszeit hat. Ich habe das obige Beispiel mit der Annahme geschrieben, dass beide Klassen in dieselbe JAR-Datei (z. B.marker.jar
) gehen würden, aber das ist nicht immer möglich.Angenommen, es gibt eine Anwendungs-JAR mit den folgenden Klassen:
com.acme.app.Main com.acme.app.@Ann com.acme.app.AnnotatedTypeA (uses @Ann) com.acme.app.AnnotatedTypeB (uses @Ann)
Dann existiert der Prozessor für
@Ann
in einer separaten JAR, die beim Kompilieren der Anwendungs-JAR verwendet wird:com.acme.proc.AnnProcessor (processes @Ann)
In diesem Fall
AnnProcessor
wäre es nicht möglich, den Typ von@Ann
direkt zu referenzieren , da dies eine zirkuläre JAR-Abhängigkeit erzeugen würde. Es könnte nur@Ann
mitString
Namen oderTypeElement
/ referenzierenTypeMirror
.quelle
Set<? extends TypeElement>
Parameter und erhalten dann die annotierten Elemente für die angegebene Runde mitgetElementsAnnotatedWith(TypeElement annotation)
. Ich verstehe auch nicht, warum Sie dieprintMessage
Methode verpackt haben .Hier finden Sie ein Tutorial zu Anmerkungen und unten ein Beispiel für die Definition Ihrer eigenen Anmerkungen. Leider ergab ein kurzes Überfliegen des Tutorials, dass diese nur im Javadoc verfügbar sind ...
Es scheint also, dass Sie wirklich nur ein @ Deprecated-Tag einwerfen können, das der Compiler ausdrucken wird, oder ein benutzerdefiniertes Tag in die Javadocs einfügen, das über den Hack informiert.
quelle
Wenn Sie IntelliJ verwenden. Sie können zu: Einstellungen> Editor> TODO gehen und "\ bhack.b *" oder ein anderes Muster hinzufügen.
Wenn du dann einen Kommentar machst wie
// HACK: temporary fix to work around server issues
Dann wird es im TODO-Werkzeugfenster zusammen mit all Ihren anderen definierten Mustern während der Bearbeitung gut angezeigt.
quelle
Sie sollten ein Tool zum Kompilieren verwenden, z. B. ant ou maven. Damit sollten Sie beim Kompilieren einige Aufgaben definieren, die beispielsweise einige Protokolle (wie Nachrichten oder Warnungen) zu Ihren FIXME-Tags erzeugen können.
Und wenn Sie einige Fehler wollen, ist es auch möglich. Stoppen Sie die Kompilierung, wenn Sie TODO in Ihrem Code belassen haben (warum nicht?)
quelle
Um überhaupt eine Warnung zu erhalten, stellte ich fest, dass nicht verwendete Variablen und benutzerdefinierte @ SuppressWarnings für mich nicht funktionierten, aber eine unnötige Besetzung:
public class Example { public void warn() { String fixmePlease = (String)"Hello"; } }
Wenn ich jetzt kompiliere:
$ javac -Xlint:all Example.java ExampleTest.java:12: warning: [cast] redundant cast to String String s = (String) "Hello!"; ^ 1 warning
quelle