Mam zamiar popełnić brzydki tymczasowy hack, aby obejść problem z blokowaniem, podczas gdy czekamy na naprawę zewnętrznego zasobu. Oprócz oznaczenia tego wielkim, przerażającym komentarzem i kilkoma FIXME, chciałbym, aby kompilator wyrzucił oczywiste ostrzeżenie jako przypomnienie, abyśmy nie zapomnieli o tym usunąć. Na przykład coś takiego:
[javac] com.foo.Hacky.java:192: warning: FIXME temporary hack to work around library bug, remove me when library is fixed!
Czy istnieje sposób, aby spowodować celowe ostrzeżenie kompilatora za pomocą wybranego przeze mnie komunikatu? Jeśli to się nie powiedzie, jaka jest najłatwiejsza rzecz do dodania do kodu, aby wyrzucić istniejące ostrzeżenie, być może z komunikatem w ciągu znaków w linii powodującej naruszenie, aby został wydrukowany w komunikacie ostrzegawczym?
EDYCJA: wycofane tagi wydają się nic nie robić:
/**
* @deprecated "Temporary hack to work around remote server quirks"
*/
@Deprecated
private void doSomeHackyStuff() { ... }
Brak błędów kompilatora lub środowiska uruchomieniowego w eclipse lub z sun javac 1.6 (uruchamianym ze skryptu Ant) i zdecydowanie wykonuje tę funkcję.
źródło
Odpowiedzi:
Jedną techniką, którą widziałem, jest powiązanie tego z testami jednostkowymi ( robisz testy jednostkowe, prawda?). Zasadniczo tworzysz test jednostkowy, który kończy się niepowodzeniem po osiągnięciu poprawki do zasobów zewnętrznych. Następnie komentujesz ten test jednostkowy, aby powiedzieć innym, jak cofnąć swój gnarly hack, gdy problem zostanie rozwiązany.
To, co naprawdę sprytne w tym podejściu, polega na tym, że wyzwalaczem do cofnięcia włamania jest naprawa samego podstawowego problemu.
źródło
Myślę, że rozwiązaniem jest niestandardowa adnotacja, która zostanie przetworzona przez kompilator. Często piszę własne adnotacje, aby robić różne rzeczy w czasie wykonywania, ale nigdy nie próbowałem ich używać podczas kompilacji. Mogę więc podać tylko wskazówki dotyczące narzędzi, których możesz potrzebować:
Nie wiem, czy to rozwiązanie jest naprawdę wykonalne. Postaram się to zrealizować sam, gdy znajdę trochę czasu.
Edytować
Z powodzeniem wdrożyłem swoje rozwiązanie. Jako bonus, skorzystałem z narzędzia dostawcy usług Java, aby uprościć jego użycie. Właściwie moim rozwiązaniem jest słoik, który zawiera dwie klasy: niestandardową adnotację i procesor adnotacji. Aby go użyć, po prostu dodaj ten słoik do ścieżki klas swojego projektu i dodaj adnotacje, co chcesz! To działa dobrze w moim IDE (NetBeans).
Kod adnotacji:
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 { }
Kod podmiotu przetwarzającego:
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; } }
Aby udostępnić wynikowy plik jar jako usługodawcę, dodaj plik do pliku
META-INF/services/javax.annotation.processing.Processor
jar. Ten plik jest plikiem acsii, który musi zawierać następujący tekst:źródło
init
i ustawianiaenv
pola - możesz pobraćProcessingEnvironment
z,this.processingEnv
ponieważ jestprotected
.Szybkim i niezbyt brudnym podejściem może być użycie
@SuppressWarnings
adnotacji z celowo błędnymString
argumentem:@SuppressWarnings("FIXME: this is a hack and should be fixed.")
Spowoduje to wygenerowanie ostrzeżenia, ponieważ kompilator nie rozpoznaje go jako specyficznego ostrzeżenia do pominięcia:
źródło
Jeden dobry hack zasługuje na inny ... Zwykle generuję ostrzeżenia kompilatora w opisanym celu, wprowadzając nieużywaną zmienną w metodzie hacky, a więc:
/** * @deprecated "Temporary hack to work around remote server quirks" */ @Deprecated private void doSomeHackyStuff() { int FIXMEtemporaryHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixed; ... }
Ta nieużywana zmienna wygeneruje ostrzeżenie, które (w zależności od kompilatora) będzie wyglądać mniej więcej tak:
To rozwiązanie nie jest tak przyjemne jak niestandardowa adnotacja, ale ma tę zaletę, że nie wymaga wcześniejszego przygotowania (zakładając, że kompilator jest już skonfigurowany do wydawania ostrzeżeń dla nieużywanych zmiennych). Sugerowałbym, że takie podejście jest odpowiednie tylko w przypadku krótkotrwałych hacków. W przypadku długowiecznych hacków argumentowałbym, że wysiłek stworzenia niestandardowej adnotacji byłby uzasadniony.
źródło
build.gradle
?Napisałem bibliotekę, która robi to z adnotacjami: Lightweight Javac @Warning Annotation
Użycie jest bardzo proste:
// some code... @Warning("This method should be refactored") public void someCodeWhichYouNeedAtTheMomentButYouWantToRefactorItLater() { // bad stuff going on here... }
Kompilator wyśle ostrzeżenie z twoim tekstem
źródło
co powiesz na oznaczenie metody lub klasy jako @Deprecated? dokumenty tutaj . Zauważ, że istnieje zarówno @Deprecated, jak i @deprecated - wersja z wielkiej litery D to adnotacja, a mała litera d to wersja javadoc. Wersja javadoc umożliwia określenie dowolnego ciągu wyjaśniającego, co się dzieje. Ale kompilatory nie są zobowiązane do wysyłania ostrzeżeń, gdy je widzą (chociaż wielu to robi). Adnotacja powinna zawsze powodować ostrzeżenie, chociaż nie sądzę, aby można było dodać do niej wyjaśnienie.
UPDATE tutaj jest kod, który właśnie przetestowałem z: Sample.java zawiera:
public class Sample { @Deprecated public static void foo() { System.out.println("I am a hack"); } }
SampleCaller.java zawiera:
public class SampleCaller{ public static void main(String [] args) { Sample.foo(); } }
po uruchomieniu „javac Sample.java SampleCaller.java” otrzymuję następujące dane wyjściowe:
Note: SampleCaller.java uses or overrides a deprecated API. Note: Recompile with -Xlint:deprecation for details.
Używam Sun's javac 1.6. Jeśli chcesz mieć uczciwe ostrzeżenie, a nie tylko notatkę, użyj opcji -Xlint. Może to prawidłowo przesiąknie przez Ant.
źródło
@Deprecated
działa tylko między klasami (więc jest bezużyteczny w przypadku metod prywatnych).Możemy to zrobić za pomocą adnotacji!
Aby zgłosić błąd, użyj,
Messager
aby wysłać wiadomość zDiagnostic.Kind.ERROR
. Krótki przykład:processingEnv.getMessager().printMessage( Diagnostic.Kind.ERROR, "Something happened!", element);
Oto dość prosta adnotacja, którą napisałem, żeby to przetestować.
Ta
@Marker
adnotacja wskazuje, że celem jest interfejs znacznika:package marker; import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Marker { }
A procesor adnotacji powoduje błąd, jeśli tak nie jest:
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)); } } }
Na przykład są to prawidłowe zastosowania
@Marker
:@Marker interface Example {}
@Marker interface Example extends Serializable {}
Ale te zastosowania
@Marker
spowodują błąd kompilatora:@Marker class Example {}
@Marker interface Example { void method(); }
Oto post na blogu, który okazał się bardzo pomocny w rozpoczęciu tego tematu:
Mała uwaga: to, na co zwraca uwagę poniższy komentator, to fakt, że z powodu
MarkerProcessor
odniesieńMarker.class
ma zależność od czasu kompilacji. Powyższy przykład napisałem z założeniem, że obie klasy będą znajdować się w tym samym pliku JAR (powiedzmymarker.jar
), ale nie zawsze jest to możliwe.Na przykład załóżmy, że istnieje plik JAR aplikacji z następującymi klasami:
com.acme.app.Main com.acme.app.@Ann com.acme.app.AnnotatedTypeA (uses @Ann) com.acme.app.AnnotatedTypeB (uses @Ann)
Wówczas procesor dla
@Ann
istnieje w osobnym JAR, który jest używany podczas kompilacji aplikacji JAR:com.acme.proc.AnnProcessor (processes @Ann)
W takim przypadku
AnnProcessor
nie byłoby możliwe bezpośrednie odwołanie się do typu@Ann
, ponieważ spowodowałoby to utworzenie cyklicznej zależności JAR. Byłoby w stanie odwoływać się tylko@Ann
przezString
nazwę lubTypeElement
/TypeMirror
.źródło
Set<? extends TypeElement>
parametru, a następnie uzyskujesz elementy z adnotacjami dla danej rundy za pomocągetElementsAnnotatedWith(TypeElement annotation)
. Nie rozumiem też, dlaczego zapakowałeśprintMessage
metodę.Tutaj pokazuje samouczek dotyczący adnotacji, a na dole daje przykład definiowania własnych adnotacji. Niestety szybkie przejrzenie samouczka powiedziało, że są one dostępne tylko w javadoc ...
Wygląda więc na to, że wszystko, co naprawdę możesz zrobić, to wrzucić tag @Deprecated, który kompilator wydrukuje lub umieści w javadocs niestandardowy tag, który mówi o włamaniu.
źródło
Jeśli używasz IntelliJ. Możesz przejść do: Preferencje> Edytor> TODO i dodać „\ bhack.b *” lub inny wzorzec.
Jeśli następnie skomentujesz
// HACK: temporary fix to work around server issues
Następnie w oknie narzędzia TODO będzie się ładnie wyświetlać wraz ze wszystkimi innymi zdefiniowanymi wzorcami podczas edycji.
źródło
Do kompilacji należy użyć narzędzia, takiego jak ant ou maven. Dzięki niemu powinieneś zdefiniować pewne zadania w czasie kompilacji, które mogą na przykład generować dzienniki (takie jak komunikaty lub ostrzeżenia) dotyczące tagów FIXME.
A jeśli chcesz błędów, to też jest możliwe. Na przykład zatrzymaj kompilację, gdy zostawisz w kodzie trochę TODO (dlaczego nie?)
źródło
Aby w ogóle pojawiło się jakiekolwiek ostrzeżenie, stwierdziłem, że nieużywane zmienne i niestandardowe @SuppressWarnings nie działają dla mnie, ale niepotrzebne rzutowanie tak:
public class Example { public void warn() { String fixmePlease = (String)"Hello"; } }
Teraz, kiedy kompiluję:
$ javac -Xlint:all Example.java ExampleTest.java:12: warning: [cast] redundant cast to String String s = (String) "Hello!"; ^ 1 warning
źródło