web-dev-qa-db-de.com

@Contract-Anmerkung von JetBrains

Wie funktioniert das org.jetbrains.annotations.Contract Anmerkungsarbeit? Wie unterstützt IntelliJ IDEA?

43

Zunächst einmal sollte ich sagen, dass diese Annotation nur für IDEA dient, um nach möglichen Fehlern zu suchen. Der Java Compiler ignoriert sie fast vollständig (it ' Ich werde in dem kompilierten Artefakt sein, aber keine Wirkung haben.

Das Ziel der Annotation ist es, einen Vertrag zu beschreiben, dem die Methode gehorchen wird, was hilft, IDEA= Probleme in Methoden zu fangen, die diese Methode aufrufen können. Der fragliche Vertrag ist Eine Reihe von durch Semikolon getrennten Klauseln, von denen jede eine Eingabe und eine Ausgabe beschreibt, die garantiert eintreten. Ursache und Wirkung sind durch -> getrennt und beschreiben den Fall, dass Sie der Methode X übergeben, Y ergibt immer Die Eingabe wird als durch Kommas getrennte Liste beschrieben, die den Fall mehrerer Eingaben beschreibt.

Mögliche Eingaben sind _ (Beliebiger Wert), null, !null (Nicht null), false und true sowie mögliche Ausgaben fügt dieser Liste fail hinzu.

So bedeutet beispielsweise null -> false, Dass bei einer null -Eingabe ein false-Boolescher Wert das Ergebnis ist. null -> false; !null -> true Erweitert dies, um zu sagen, dass nullimmerfalse zurückgibt und ein nichtnull -Wert always return true usw. Schließlich bedeutet null -> fail, dass die Methode eine Ausnahme auslöst, wenn Sie einen Nullwert übergeben.

Bei einem Beispiel mit mehreren Argumenten bedeutet null, !null -> fail, Dass bei einer Methode mit zwei Argumenten eine garantierte Ausnahme ausgelöst wird, wenn das erste Argument null und das zweite nicht null ist.

Wenn die Methode den Status des Objekts nicht ändert, sondern nur einen neuen Wert zurückgibt, sollten Sie pure auf true setzen.

44
dcsohl

Die offizielle Dokumentation spezifiziert die formale Grammatik aller unterstützten und anerkannten Werte für die Annotation.

In den Begriffen des Laien:

  • Mit einem Vertrag können eine oder mehrere Klauseln verknüpft sein
  • Eine Klausel ist immer[args] -> [effect]
  • Args sind eine oder mehrere Bedingungen, die als any | null | !null | false | true Definiert sind.
  • Effekte sind nur eine Einschränkung oder fail

Lassen Sie uns ein kurzes Beispiel durchgehen - einer meiner Favoriten ist: "Was auch immer Sie in diese Methode übergehen, es wird eine Ausnahme auslösen."

@Contract("_-> fail")
public void alwaysBreak(Object o) {
    throw new IllegalArgumentException();
}

In diesem Beispiel geben wir mit _ Oder "any" an, dass wir unabhängig von der Übergabe an diese Methode eine Ausnahme auslösen.

Was ist, wenn wir gelogen und gesagt haben, dass diese Methode true bedingungslos zurückgeben würde?

@Contract("_-> true")
public void alwaysBreak(Object o) {
    throw new IllegalArgumentException();
}

IntelliJ warnt davor.

enter image description here

Es ist auch (offensichtlich) verärgert, dass wir gesagt haben, wir würden einen Booleschen Wert zurückgeben, wenn wir uns in einer void -Methode befinden. ..

enter image description here


Die Hauptzeiten, in denen Sie @Contract Verwenden möchten, sind:

  • Sie möchten garantieren, dass Sie true oder false zurückgeben
  • Sie möchten sicherstellen, dass Sie einen Wert ungleich Null bei bestimmten Einschränkungen zurückgeben
  • Sie möchten klarstellen, dass Sie bei bestimmten Bedingungen einen Nullwert zurückgeben können
  • Sie möchten klarstellen, dass Sie bei bestimmten Einschränkungen eine Ausnahme auslösen

Das heißt nicht, dass @Contract Perfekt ist; weit davon entfernt. Es kann in bestimmten Kontexten keine sehr gründliche Analyse durchführen, aber wenn Sie dies in Ihrer Codebasis haben, können Ihre Tools diese Art von Analyse kostenlos durchführen.

26
Makoto

Wie funktioniert die Annotation org.jetbrains.annotations.Contract?

Obwohl die vorherigen Antworten informativ sind, habe ich nicht das Gefühl, dass sie das operative Wort "Arbeit" in Ihrer Frage ansprechen. Das heißt, sie erklären nicht, was IntelliJ hinter den Kulissen tut, um ihre Anmerkungen zu implementieren, damit Sie Ihre eigenen leicht von Grund auf neu erstellen können.

Wenn Sie sich den folgenden Quellcode ansehen, denken Sie vielleicht, dass er für etwas so Einfaches wie @NotNull Etwas zu kompliziert (oder zumindest wortreich) erscheint. Ich stimme Ihnen zu, und dies ist ein Grund, warum ich im Allgemeinen @Contract - ähnliche Klauseln vermeide, die nicht "schlicht und einfach" sind (wie @NotNull), Und stattdessen JavaDoc meine Voraussetzungen direkt.

I im Allgemeinen keine komplexen Vertragsanmerkungen verwenden - trotz des Hasses, den ich für das Überspringen dieses trendigen neuen Zugs erhalten könnte - und hier einige Gründe, warum:

  • Anmerkungen können komplex sein - z. Mehrfach verschachtelte Annotationen in eigenen Definitionen und/oder einer "Grammatik" mit dem Aussehen von Vollständigkeit . Dies kann zu einem falschen Gefühl des Vertrauens während des Kompilierens führen, indem der wahre Schuldige eines Fehlers hinter Ebenen der Dunkelheit/Abstraktion maskiert wird und die ursprünglich beabsichtigten Warnungen nicht generiert werden.
  • Ähnlich, aber anders als in meinem vorherigen Punkt: Anmerkungen verbergen dem Entwickler oft reichlich Logik in einigen wenigen Schlüsselwörtern, was zu schwer verständlichem Code für Menschen und/oder ungewöhnlichem Verhalten führt, das sich nur schwer debuggen lässt.
  • App-Konfiguration wird oft als Annotation getarnt . Schauen Sie sich das Spring Framework an .
  • Die Syntax zum Definieren von Verträgen ist im Großen und Ganzen (IMHO) ziemlich hässlich und makefile-ish. Schauen Sie sich zum Beispiel einige der JetBrains-Anmerkungsdefinitionen und unterstützenden Dateien an verstreut über sein Repo . Beachten Sie die zahlreichen XML-Dateien und reichlich Selbstreferenz? Ich würde kaum sagen, dass das Schreiben und Unterstützen Spaß macht, besonders wenn man bedenkt, dass sich die Annotationen ständig weiterentwickeln und zwischen der Android und der größeren Java Community hin- und herwechseln.

Einige zu berücksichtigende Fragen:

  • Geht es zu weit, wenn sich der Quellcode einer Annotation der Komplexität der Quelle nähert, die er annotiert?
  • Ist das Code-Segment darunter wirklich so viel besser, als zur Laufzeit nur auf Null zu prüfen und Ausnahmen mit Stacktraces zu protokollieren? Wenn Sie so etwas einschließen, müssen Ihre Benutzer weitere Abhängigkeiten, die Ihre Anmerkungen definieren, lesen, verstehen und möglicherweise Fehler beheben.

Ausgeliehen von noch einem längeren Post über Vertragssemantik, den ich nur weiter argumentieren möchte, dient meinem Punkt:

import Java.lang.annotation.Documented;
import Java.lang.annotation.ElementType;
import Java.lang.annotation.Retention;
import Java.lang.annotation.RetentionPolicy;
import javax.annotation.Nonnull;
import javax.annotation.meta.TypeQualifierDefault;

/**
 * This annotation can be applied to a package, class or method to indicate that the class fields,
 * method return types and parameters in that element are not null by default unless there is: <ul>
 * <li>An explicit nullness annotation <li>The method overrides a method in a superclass (in which
 * case the annotation of the corresponding parameter in the superclass applies) <li> there is a
 * default parameter annotation applied to a more tightly nested element. </ul>
 * <p/>
 * @see https://stackoverflow.com/a/9256595/14731
 */
@Documented
@Nonnull
@TypeQualifierDefault(
{
    ElementType.ANNOTATION_TYPE,
    ElementType.CONSTRUCTOR,
    ElementType.FIELD,
    ElementType.LOCAL_VARIABLE,
    ElementType.METHOD,
    ElementType.PACKAGE,
    ElementType.PARAMETER,
    ElementType.TYPE
})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNullByDefault
{
}

Wann und welche Verträge sollten Sie nutzen?

Ich schlage vor, sich an die Art zu halten, deren Absichten aus ihrem Namen klar hervorgehen, und solche mit eigener Semantik und sprachlichen Definitionen zu vermeiden.

Ein zu verwendendes Beispiel - trotz des vorherigen Segments - ist @NotNull, Aber beschränken Sie sich darauf, wenn alle Objektparameter null sein müssen.

Ein zu vermeidendes Beispiel sind solche wie Android und IntelliJs @Contract(...). Obwohl ich ihre IDEs liebe, sind die Details ihrer Anmerkungen ziemlich kompliziert und letztendlich für mich zu einer Quelle von Problemen und Plattformunverträglichkeiten geworden (zugegebenermaßen verursacht durch meine eigene Unkenntnis beim Erstellen neuer Verträge in fast 100% der Fälle). aber warum ist es so schwierig, es richtig zu machen?)

Zusammenfassung/Fazit

Anmerkungen sind eine großartige Idee, die eindeutig von Programmierern generiert wird, die ihre Dokumentation "kodifizieren" möchten. Ich habe das Gefühl, dass sie in letzter Zeit zu weit gegangen sind und Dokumentation in semantischen Code verwandelt haben, der zu ernsthaften Rätseln und unangenehmen Situationen führt. Noch schlimmer ist, dass sie manchmal ein falsches Gefühl für die Sicherheit beim Kompilieren vermitteln, indem sie Probleme, die sich in ihren eigenen Implementierungen manifestieren, nicht erkennen. Halten Sie sich an das Einfache und vermeiden Sie alles, was aussieht wie eine Sprache, die nicht Java ist (was Sie eigentlich schreiben wollten).

Weitere Lektüre

Diese kurze Liste ist eine Mischung aus meist kritischen (mit Optimismus!) Quellen aus StackOverflow und dem Web, die meiner Meinung nach einige meiner Punkte veranschaulichen.

In keiner bestimmten Reihenfolge:

Und nach all dem wurde mir klar, dass ich es möglicherweise immer noch versäumt habe, Ihre ursprüngliche Frage in ihrer Gesamtheit zu beantworten :)

17
EntangledLoops