Was macht das Java Assert-Schlüsselwort und wann sollte es verwendet werden?

601

Was sind einige Beispiele aus dem wirklichen Leben , um die Schlüsselrolle von Behauptungen zu verstehen?

Praveen
quelle
8
Im wirklichen Leben sieht man sie fast nie. Vermutung: Wenn Sie Assertions verwenden, müssen Sie über drei Zustände nachdenken: Assert bestanden, Assert schlägt fehl, Assert ist deaktiviert, anstatt nur zwei. Und assert ist standardmäßig deaktiviert, so dass dies der wahrscheinlichste Status ist, und es ist schwierig sicherzustellen, dass es für Ihren Code aktiviert ist. Das Ergebnis ist, dass Asserts eine vorzeitige Optimierung sind, die nur begrenzt von Nutzen wäre. Wie Sie in der Antwort von @ Björn sehen, ist es sogar schwierig, einen Anwendungsfall zu finden, bei dem Sie eine Behauptung nicht immer scheitern lassen möchten.
Yishai
35
@Yishai: "Sie müssen darüber nachdenken ... Assert ist ausgeschaltet" Wenn Sie das tun müssen, machen Sie es falsch. "Asserts sind eine vorzeitige Optimierung der eingeschränkten Nutzung" Dies ist ziemlich aus dem Ruder gelaufen. Hier ist Suns Ansicht dazu: " Verwenden von Assertions in der Java-Technologie " und dies ist auch gut zu lesen: " Die Vorteile des Programmierens mit Assertions (auch bekannt als
Assert-
5
@ DavidTonhofer, im wirklichen Leben sieht man sie fast nie. Dies ist überprüfbar. Überprüfen Sie so viele Open Source-Projekte, wie Sie möchten. Ich sage nicht, dass Sie keine Invarianten validieren. Das ist nicht dasselbe. Anders ausgedrückt. Wenn Asserts so wichtig sind, warum sind sie standardmäßig deaktiviert?
Yishai
17
Eine Referenz, FWIW: Die Beziehung zwischen Software-Assertions und Codequalität : "Wir vergleichen auch die Wirksamkeit von Assertions mit der von gängigen Techniken zur Fehlersuche wie statischen Analysewerkzeugen für Quellcode. Wir beobachten aus unserer Fallstudie, dass mit einer Zunahme der Assertionsdichte In einer Datei gibt es eine statistisch signifikante Abnahme der Fehlerdichte. "
David Tonhofer
4
@DavidTonhofer David, ich denke, Ihre Liebe zur Behauptung ist für eine ganz bestimmte Art von Programmierung, die Sie machen. In meinem Bereich, der mit Webanwendungen arbeitet, die aus irgendeinem Grund aus dem Programm austreten, ist das größte NEIN NEIN - ich persönlich habe es nie getan verwendet behaupten andere als Einheit / Integ-Test
Nachtograph

Antworten:

426

Assertions (über das Schlüsselwort assert ) wurden in Java 1.4 hinzugefügt. Sie werden verwendet, um die Richtigkeit einer Invariante im Code zu überprüfen. Sie sollten niemals im Produktionscode ausgelöst werden und weisen auf einen Fehler oder Missbrauch eines Codepfads hin. Sie können zur Laufzeit über die -eaOption im javaBefehl aktiviert werden , sind jedoch standardmäßig nicht aktiviert.

Ein Beispiel:

public Foo acquireFoo(int id) {
  Foo result = null;
  if (id > 50) {
    result = fooService.read(id);
  } else {
    result = new Foo(id);
  }
  assert result != null;

  return result;
}
Ophidian
quelle
71
Tatsächlich weist Oracle Sie an assert, die Parameter öffentlicher Methoden nicht zu überprüfen ( docs.oracle.com/javase/1.4.2/docs/guide/lang/assert.html ). Das sollte ein werfen, Exceptionanstatt das Programm zu töten.
SJuan76
10
Aber Sie erklären immer noch nicht, warum sie existieren. Warum können Sie keine if () -Prüfung durchführen und eine Ausnahme auslösen?
El Mac
7
@ElMac - Zusicherungen gelten für die Teile dev / debug / test des Zyklus - sie sind nicht für die Produktion bestimmt. Ein if-Block läuft in prod. Einfache Zusicherungen werden die Bank nicht brechen, aber teure Zusicherungen, die eine komplexe Datenvalidierung durchführen, können Ihre Produktionsumgebung beeinträchtigen, weshalb sie dort deaktiviert sind.
Hoodaticus
2
@hoodaticus meinst du nur die Tatsache, dass ich alle Behauptungen für Prod Code ein- / ausschalten kann, ist der Grund? Weil ich sowieso komplexe Daten validieren und dann mit Ausnahmen behandeln kann. Wenn ich Produktionscode habe, könnte ich die komplexen (und möglicherweise teuren) Behauptungen deaktivieren, weil es funktionieren sollte und bereits getestet wurde? Theoretisch sollten sie das Programm nicht zum Erliegen bringen, da Sie dann sowieso ein Problem haben würden.
El Mac
8
This convention is unaffected by the addition of the assert construct. Do not use assertions to check the parameters of a public method. An assert is inappropriate because the method guarantees that it will always enforce the argument checks. It must check its arguments whether or not assertions are enabled. Further, the assert construct does not throw an exception of the specified type. It can throw only an AssertionError. docs.oracle.com/javase/8/docs/technotes/guides/language/…
Bakhshi
325

Nehmen wir an, Sie sollen ein Programm zur Steuerung eines Kernkraftwerks schreiben. Es ist ziemlich offensichtlich, dass selbst der kleinste Fehler katastrophale Folgen haben kann, daher muss Ihr Code sein fehlerfrei (unter der Annahme , dass die JVM ist fehlerfrei im Interesse des Arguments).

Java ist keine überprüfbare Sprache, was bedeutet: Sie können nicht berechnen, dass das Ergebnis Ihrer Operation perfekt ist. Der Hauptgrund dafür sind Zeiger: Sie können überall oder nirgendwo zeigen, daher können sie nicht so berechnet werden, dass sie genau diesen Wert haben, zumindest nicht innerhalb eines angemessenen Codebereichs. Angesichts dieses Problems gibt es keine Möglichkeit zu beweisen, dass Ihr Code insgesamt korrekt ist. Sie können jedoch beweisen, dass Sie zumindest jeden Fehler finden, wenn er auftritt.

Diese Idee basiert auf dem DbC-Paradigma ( Design-by-Contract ): Sie definieren zunächst (mit mathematischer Genauigkeit), was Ihre Methode tun soll, und überprüfen dies dann, indem Sie sie während der tatsächlichen Ausführung testen. Beispiel:

// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
  return a + b;
}

Obwohl dies ziemlich offensichtlich gut funktioniert, werden die meisten Programmierer den versteckten Fehler in diesem nicht sehen (Hinweis: Die Ariane V ist aufgrund eines ähnlichen Fehlers abgestürzt). Jetzt definiert DbC, dass Sie immer die Ein- und Ausgabe einer Funktion überprüfen müssen, um sicherzustellen, dass sie ordnungsgemäß funktioniert. Java kann dies durch Behauptungen tun:

// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
    assert (Integer.MAX_VALUE - a >= b) : "Value of " + a + " + " + b + " is too large to add.";
  final int result = a + b;
    assert (result - a == b) : "Sum of " + a + " + " + b + " returned wrong sum " + result;
  return result;
}

Sollte diese Funktion jetzt jemals fehlschlagen, werden Sie es bemerken. Sie werden wissen, dass es ein Problem in Ihrem Code gibt, Sie wissen, wo es ist und was es verursacht hat (ähnlich wie bei Ausnahmen). Und was noch wichtiger ist: Sie beenden die Ausführung richtig, wenn verhindert wird, dass weiterer Code mit falschen Werten arbeitet, und möglicherweise das, was er steuert, beschädigt.

Java-Ausnahmen sind ein ähnliches Konzept, aber sie können nicht alles überprüfen. Wenn Sie noch mehr Überprüfungen wünschen (auf Kosten der Ausführungsgeschwindigkeit), müssen Sie Zusicherungen verwenden. Wenn Sie dies tun, wird Ihr Code aufgebläht, aber Sie können am Ende ein Produkt in einer überraschend kurzen Entwicklungszeit liefern (je früher Sie einen Fehler beheben, desto geringer sind die Kosten). Und außerdem: Wenn Ihr Code einen Fehler enthält, werden Sie ihn erkennen. Es gibt keine Möglichkeit, dass ein Fehler auftritt und später Probleme verursacht.

Dies ist immer noch keine Garantie für fehlerfreien Code, aber es ist viel näher daran als übliche Programme.

TwoThe
quelle
29
Ich habe dieses Beispiel gewählt, weil es versteckte Fehler in scheinbar fehlerfreiem Code sehr gut darstellt. Wenn dies ähnlich ist wie das, was jemand anderes präsentiert hat, dann hatte er vielleicht die gleiche Idee. ;)
TwoThe
8
Sie wählen assert, weil es fehlschlägt, wenn die Zusicherung falsch ist. Ein Wenn kann irgendein Verhalten haben. Das Treffen von Randfällen ist Aufgabe von Unit Testing. Durch die Verwendung von Design by Contract wurde der Vertrag ziemlich gut spezifiziert, aber wie bei realen Verträgen benötigen Sie eine Kontrolle, um sicherzustellen, dass diese eingehalten werden. Bei Behauptungen wird ein Watchdog eingefügt, der Sie dann bei Missachtung des Vertrages unterstützt. Stellen Sie sich das als einen nervigen Anwalt vor, der jedes Mal "FALSCH" schreit, wenn Sie etwas außerhalb oder gegen einen von Ihnen unterzeichneten Vertrag tun und Sie dann nach Hause schicken, damit Sie nicht weiter arbeiten und den Vertrag weiter verletzen können!
Eric
5
Notwendig in diesem einfachen Fall: Nein, aber die DbC definiert, dass jedes Ergebnis überprüft werden muss. Stellen Sie sich vor, jemand ändert diese Funktion jetzt in etwas viel Komplexeres, dann muss er auch die Nachprüfung anpassen, und dann wird sie plötzlich nützlich.
TwoThe
4
Es tut mir leid, dies wiederzubeleben, aber ich habe eine spezielle Frage. Was ist der Unterschied zwischen dem, was @TwoThe getan hat und dem, anstatt assert zu verwenden, nur ein new IllegalArgumentExceptionmit der Nachricht zu werfen ? Ich meine, abgesehen davon, dass o throwsder Methodendeklaration und dem Code hinzugefügt werden muss, um diese Ausnahme woanders zu verwalten. Warum assertsollte man eine neue Ausnahme auslösen? Oder warum nicht ein ifstatt assert? Kann das nicht wirklich
verstehen
14
-1: Die Behauptung, auf Überlauf zu prüfen, ist falsch, wenn asie negativ sein kann. Die zweite Behauptung ist nutzlos; Für int-Werte ist es immer so, dass a + b - b == a. Dieser Test kann nur fehlschlagen, wenn der Computer grundlegend defekt ist. Um sich gegen diese Eventualität zu verteidigen, müssen Sie die Konsistenz über mehrere CPUs hinweg überprüfen.
Kevin Cline
63

Assertions sind ein Tool in der Entwicklungsphase, um Fehler in Ihrem Code zu erkennen. Sie sind so konzipiert, dass sie leicht entfernt werden können, sodass sie im Produktionscode nicht vorhanden sind. Behauptungen sind also nicht Teil der "Lösung", die Sie dem Kunden liefern. Es handelt sich um interne Überprüfungen, um sicherzustellen, dass die von Ihnen getroffenen Annahmen korrekt sind. Das häufigste Beispiel ist das Testen auf Null. Viele Methoden sind so geschrieben:

void doSomething(Widget widget) {
  if (widget != null) {
    widget.someMethod(); // ...
    ... // do more stuff with this widget
  }
}

Sehr oft sollte bei einer solchen Methode das Widget einfach niemals null sein. Wenn es also null ist, gibt es irgendwo einen Fehler in Ihrem Code, den Sie aufspüren müssen. Aber der obige Code wird Ihnen dies niemals sagen. In einem gut gemeinten Versuch, "sicheren" Code zu schreiben, verstecken Sie auch einen Fehler. Es ist viel besser, Code wie folgt zu schreiben:

/**
 * @param Widget widget Should never be null
 */
void doSomething(Widget widget) {
  assert widget != null;
  widget.someMethod(); // ...
    ... // do more stuff with this widget
}

Auf diese Weise können Sie diesen Fehler sicher frühzeitig erkennen. (Es ist auch nützlich, im Vertrag anzugeben, dass dieser Parameter niemals null sein darf.) Aktivieren Sie unbedingt die Zusicherungen, wenn Sie Ihren Code während der Entwicklung testen. (Und es ist oft schwierig, Ihre Kollegen dazu zu überreden, was ich sehr ärgerlich finde.)

Nun werden einige Ihrer Kollegen Einwände gegen diesen Code erheben und argumentieren, dass Sie immer noch die Nullprüfung durchführen sollten, um eine Ausnahme in der Produktion zu verhindern. In diesem Fall ist die Behauptung immer noch nützlich. Sie können es so schreiben:

void doSomething(Widget widget) {
  assert widget != null;
  if (widget != null) {
    widget.someMethod(); // ...
    ... // do more stuff with this widget
  }
}

Auf diese Weise werden Ihre Kollegen froh sein, dass die Nullprüfung für Produktionscode vorhanden ist, aber während der Entwicklung verbergen Sie den Fehler nicht mehr, wenn das Widget null ist.

Hier ist ein Beispiel aus der Praxis: Ich habe einmal eine Methode geschrieben, die zwei beliebige Werte auf Gleichheit vergleicht, wobei jeder Wert null sein kann:

/**
 * Compare two values using equals(), after checking for null.
 * @param thisValue (may be null)
 * @param otherValue (may be null)
 * @return True if they are both null or if equals() returns true
 */
public static boolean compare(final Object thisValue, final Object otherValue) {
  boolean result;
  if (thisValue == null) {
    result = otherValue == null;
  } else {
    result = thisValue.equals(otherValue);
  }
  return result;
}

Dieser Code delegiert die Arbeit der equals()Methode für den Fall, dass thisValue nicht null ist. Es wird jedoch davon ausgegangen, dass die equals()Methode den Vertrag von korrekt erfülltequals() indem ein Nullparameter ordnungsgemäß behandelt wird.

Ein Kollege widersprach meinem Code und sagte mir, dass viele unserer Klassen fehlerhafte equals()Methoden haben, die nicht auf Null getestet werden. Deshalb sollte ich diese Prüfung in diese Methode einfügen . Es ist fraglich, ob dies sinnvoll ist oder ob wir den Fehler erzwingen sollten, damit wir ihn erkennen und beheben können, aber ich habe mich an meinen Kollegen gewandt und einen Null-Check durchgeführt, den ich mit einem Kommentar markiert habe:

public static boolean compare(final Object thisValue, final Object otherValue) {
  boolean result;
  if (thisValue == null) {
    result = otherValue == null;
  } else {
    result = otherValue != null && thisValue.equals(otherValue); // questionable null check
  }
  return result;
}

Die zusätzliche Prüfung hier other != nullist nur erforderlich, wenn dieequals() Methode nicht wie im Vertrag vorgeschrieben auf Null prüft.

Anstatt mit meinem Kollegen eine fruchtlose Debatte über die Weisheit zu führen, den fehlerhaften Code in unserer Codebasis zu belassen, habe ich einfach zwei Aussagen in den Code eingefügt. Diese Behauptungen lassen mich während der Entwicklungsphase wissen, wenn eine unserer Klassen nicht equals()ordnungsgemäß implementiert wird, sodass ich das Problem beheben kann:

public static boolean compare(final Object thisValue, final Object otherValue) {
  boolean result;
  if (thisValue == null) {
    result = otherValue == null;
    assert otherValue == null || otherValue.equals(null) == false;
  } else {
    result = otherValue != null && thisValue.equals(otherValue);
    assert thisValue.equals(null) == false;
  }
  return result;
}

Die wichtigsten Punkte, die zu beachten sind, sind folgende:

  1. Behauptungen sind nur Werkzeuge in der Entwicklungsphase.

  2. Der Sinn einer Behauptung besteht darin, Sie wissen zu lassen, ob ein Fehler vorliegt, nicht nur in Ihrem Code, sondern auch in Ihrer Codebasis . (Die Behauptungen hier kennzeichnen tatsächlich Fehler in anderen Klassen.)

  3. Selbst wenn mein Kollege zuversichtlich wäre, dass unsere Klassen richtig geschrieben wurden, wären die Aussagen hier immer noch nützlich. Es werden neue Klassen hinzugefügt, die möglicherweise nicht auf Null getestet werden können, und diese Methode kann diese Fehler für uns kennzeichnen.

  4. In der Entwicklung sollten Sie Assertions immer aktivieren, auch wenn der von Ihnen geschriebene Code keine Assertions verwendet. Meine IDE ist so eingestellt, dass dies bei jeder neuen ausführbaren Datei standardmäßig immer der Fall ist.

  5. Die Behauptungen ändern nichts am Verhalten des Codes in der Produktion, daher ist mein Kollege froh, dass die Nullprüfung vorhanden ist und dass diese Methode auch dann ordnungsgemäß ausgeführt wird, wenn die equals()Methode fehlerhaft ist. Ich bin glücklich, weil ich jede fehlerhafte equals()Methode in der Entwicklung fangen werde .

Außerdem sollten Sie Ihre Assertionsrichtlinie testen, indem Sie eine temporäre Assertion eingeben, die fehlschlägt, damit Sie sicher sein können, dass Sie entweder über die Protokolldatei oder eine Stapelverfolgung im Ausgabestream benachrichtigt werden.

MiguelMunoz
quelle
Gute Punkte zum "Verstecken eines Fehlers" und wie Behauptungen Fehler während der Entwicklung aufdecken!
Nobar
Keine dieser Überprüfungen ist langsam, daher gibt es keinen Grund, sie in der Produktion auszuschalten. Sie sollten in Protokollierungsanweisungen konvertiert werden, damit Sie Probleme erkennen können, die in Ihrer "Entwicklungsphase" nicht auftreten. (Wirklich, es gibt sowieso keine Entwicklungsphase. Die Entwicklung endet, wenn Sie sich entscheiden, die Wartung Ihres Codes überhaupt
Aleksandr Dubinsky
20

Viele gute Antworten, die erklären, was das assertSchlüsselwort bewirkt, aber nur wenige, die die eigentliche Frage beantworten: "Wann sollte das?assert Schlüsselwort im wirklichen Leben verwendet werden?"

Die Antwort: fast nie .

Behauptungen als Konzept sind wunderbar. Guter Code hat viele if (...) throw ...Aussagen (und ihre Verwandten mögen Objects.requireNonNullund Math.addExact). Bestimmte Entwurfsentscheidungen haben jedoch die Nützlichkeit des assert Schlüsselworts selbst stark eingeschränkt .

Die treibende Idee hinter dem assertSchlüsselwort ist die vorzeitige Optimierung, und das Hauptmerkmal besteht darin, dass alle Überprüfungen einfach deaktiviert werden können. Tatsächlich sind die assertÜberprüfungen standardmäßig deaktiviert.

Es ist jedoch von entscheidender Bedeutung, dass in der Produktion weiterhin invariante Überprüfungen durchgeführt werden. Dies liegt daran, dass eine perfekte Testabdeckung nicht möglich ist und der gesamte Produktionscode Fehler enthält, deren Behauptungen zur Diagnose und Minderung beitragen sollten.

Daher sollte die Verwendung von if (...) throw ...bevorzugt werden, genau wie es zum Überprüfen von Parameterwerten öffentlicher Methoden und zum Werfen erforderlich ist IllegalArgumentException.

Gelegentlich könnte man versucht sein, einen invarianten Scheck zu schreiben, dessen Verarbeitung unerwünscht lange dauert (und der oft genug aufgerufen wird, damit er eine Rolle spielt). Solche Überprüfungen verlangsamen jedoch das Testen, was ebenfalls unerwünscht ist. Solche zeitaufwändigen Prüfungen werden normalerweise als Komponententests geschrieben. Trotzdem kann es manchmal sinnvoll sein, es zu verwendenassert aus diesem Grund zu verwenden.

Verwenden Sie es nicht asserteinfach, weil es sauberer und hübscher ist als if (...) throw ...(und das sage ich mit großen Schmerzen, weil ich es sauber und hübsch mag). Wenn Sie sich einfach nicht selbst helfen können und steuern können, wie Ihre Anwendung gestartet wird, können Sie sie verwenden, assertaber immer Aussagen in der Produktion aktivieren. Zugegeben, das ist es, was ich tendenziell mache. Ich dränge auf eine lombok Anmerkung , die bewirkt , dass assertmehr wie handeln if (...) throw ....Stimmen Sie hier dafür ab.

(Rant: Die JVM-Entwickler waren eine Menge schrecklicher, vorzeitig optimierter Codierer. Deshalb hören Sie von so vielen Sicherheitsproblemen im Java-Plugin und in der JVM. Sie haben sich geweigert, grundlegende Überprüfungen und Zusicherungen in den Produktionscode aufzunehmen, und wir fahren fort Zahl den Preis.)

Aleksandr Dubinsky
quelle
2
@aberglas Eine Sammelklausel ist catch (Throwable t). Es gibt keinen Grund, nicht zu versuchen, OutOfMemoryError, AssertionError usw. abzufangen, zu protokollieren oder erneut zu versuchen / wiederherzustellen.
Aleksandr Dubinsky
1
Ich habe OutOfMemoryError abgefangen und wiederhergestellt.
Miguel Munoz
1
Ich stimme nicht zu Viele meiner Behauptungen werden verwendet, um sicherzustellen, dass meine API korrekt aufgerufen wird. Zum Beispiel könnte ich eine private Methode schreiben, die nur aufgerufen werden sollte, wenn ein Objekt eine Sperre enthält. Wenn ein anderer Entwickler diese Methode von einem Teil des Codes aus aufruft, der das Objekt nicht sperrt, teilt ihm die Behauptung sofort mit, dass sie einen Fehler gemacht haben. Es gibt viele solche Fehler, die mit Sicherheit in der Entwicklungsphase hängen bleiben können, und Aussagen sind in diesen Fällen sehr nützlich.
Miguel Munoz
2
@ MiguelMunoz In meiner Antwort sagte ich, dass die Idee der Behauptungen sehr gut ist. Es ist die Implementierung des assertSchlüsselworts schlecht. Ich werde meine Antwort bearbeiten, um klarer zu machen, dass ich mich auf das Schlüsselwort und nicht auf das Konzept beziehe.
Aleksandr Dubinsky
2
Ich mag die Tatsache, dass es einen AssertionError anstelle einer Exception auslöst. Zu viele Entwickler haben immer noch nicht gelernt, dass sie Exception nicht abfangen sollten, wenn der Code nur so etwas wie IOException auslösen kann. Ich hatte Fehler in meinem Code, die vollständig verschluckt wurden, weil jemand eine Ausnahme abgefangen hat. Behauptungen geraten nicht in diese Falle. Ausnahmen gelten für Situationen, die Sie im Produktionscode erwarten. Bei der Protokollierung sollten Sie auch alle Ihre Fehler protokollieren, auch wenn Fehler selten sind. Möchten Sie beispielsweise einen OutOfMemoryError wirklich passieren lassen, ohne ihn zu protokollieren?
Miguel Munoz
14

Hier ist der häufigste Anwendungsfall. Angenommen, Sie schalten einen Aufzählungswert ein:

switch (fruit) {
  case apple:
    // do something
    break;
  case pear:
    // do something
    break;
  case banana:
    // do something
    break;
}

Solange Sie jeden Fall bearbeiten, geht es Ihnen gut. Aber eines Tages wird jemand Feigen zu Ihrer Aufzählung hinzufügen und vergessen, sie Ihrer switch-Anweisung hinzuzufügen. Dies führt zu einem Fehler, der möglicherweise schwierig zu erkennen ist, da die Auswirkungen erst nach dem Verlassen der switch-Anweisung zu spüren sind. Aber wenn Sie Ihren Schalter so schreiben, können Sie ihn sofort fangen:

switch (fruit) {
  case apple:
    // do something
    break;
  case pear:
    // do something
    break;
  case banana:
    // do something
    break;
  default:
    assert false : "Missing enum value: " + fruit;
}
MiguelMunoz
quelle
4
Aus diesem Grund sollten Warnungen aktiviert und Warnungen als Fehler behandelt werden. Jeder halbwegs anständige Compiler kann Ihnen sagen, wenn Sie nur zulassen, dass Sie einen Enum-Check verpassen, und dies zum Zeitpunkt der Kompilierung, was unbeschreiblich besser ist, als (vielleicht eines Tages) herauszufinden Laufzeit.
Mike Nakis
9
Warum sollte hier eher eine Behauptung als eine Ausnahme verwendet werden, z. B. eine illegale Argumentausnahme?
Liltitus27
4
Dies löst ein, AssertionErrorwenn Zusicherungen aktiviert sind ( -ea). Was ist das gewünschte Verhalten in der Produktion? Ein stilles No-Op und eine mögliche Katastrophe später in der Hinrichtung? Wahrscheinlich nicht. Ich würde eine explizite vorschlagen throw new AssertionError("Missing enum value: " + fruit);.
Aioobe
1
Es gibt ein gutes Argument dafür, nur einen AssertionError auszulösen. Was das richtige Verhalten in der Produktion betrifft, so besteht der Sinn der Behauptungen darin, dies in der Produktion zu verhindern. Assertions sind ein Tool für die Entwicklungsphase, um Fehler zu erkennen, die leicht aus dem Produktionscode entfernt werden können. In diesem Fall gibt es keinen Grund, es aus dem Produktionscode zu entfernen. In vielen Fällen können Integritätstests jedoch zu Verzögerungen führen. Wenn Sie diese Tests in Zusicherungen einfügen, die nicht im Produktionscode verwendet werden, können Sie gründliche Tests schreiben, ohne befürchten zu müssen, dass sie Ihren Produktionscode verlangsamen.
Miguel Munoz
Das scheint falsch zu sein. IMHO sollten Sie nicht verwenden, defaultdamit der Compiler Sie bei fehlenden Fällen warnen kann. Sie können returnstattdessen break(dies muss möglicherweise die Methode extrahieren) den fehlenden Fall nach dem Wechsel behandeln. Auf diese Weise erhalten Sie sowohl die Warnung als auch die Gelegenheit dazu assert.
Maaartinus
12

Assertions werden verwendet, um die Nachbedingungen zu überprüfen und die Vorbedingungen "sollte niemals scheitern". Richtiger Code sollte niemals eine Behauptung verfehlen. Wenn sie ausgelöst werden, sollten sie auf einen Fehler hinweisen (hoffentlich an einem Ort, der sich in der Nähe des tatsächlichen Ortes des Problems befindet).

Ein Beispiel für eine Behauptung könnte darin bestehen, zu überprüfen, ob eine bestimmte Gruppe von Methoden in der richtigen Reihenfolge aufgerufen wird (z. B. die hasNext()zuvor next()in einer aufgerufen wurde Iterator).

Donal Fellows
quelle
1
Sie müssen hasNext () nicht vor next () aufrufen.
DJClayworth
6
@DJClayworth: Sie müssen auch nicht vermeiden, Behauptungen auszulösen. :-)
Donal Fellows
8

Was macht das Schlüsselwort assert in Java?

Schauen wir uns den kompilierten Bytecode an.

Wir werden daraus schließen, dass:

public class Assert {
    public static void main(String[] args) {
        assert System.currentTimeMillis() == 0L;
    }
}

generiert fast genau den gleichen Bytecode wie:

public class Assert {
    static final boolean $assertionsDisabled =
        !Assert.class.desiredAssertionStatus();
    public static void main(String[] args) {
        if (!$assertionsDisabled) {
            if (System.currentTimeMillis() != 0L) {
                throw new AssertionError();
            }
        }
    }
}

wo Assert.class.desiredAssertionStatus()ist truewann-ea wird in der Befehlszeile übergeben, andernfalls falsch.

Wir verwenden, System.currentTimeMillis()um sicherzustellen, dass es nicht weg optimiert wird ( assert true;tat).

Das synthetische Feld wird so generiert, dass Java Assert.class.desiredAssertionStatus()beim Laden nur einmal aufrufen muss , und speichert dann das Ergebnis dort zwischen. Siehe auch: Was bedeutet "statisch synthetisch"?

Wir können dies überprüfen mit:

javac Assert.java
javap -c -constants -private -verbose Assert.class

Mit Oracle JDK 1.8.0_45 wurde ein synthetisches statisches Feld generiert (siehe auch: Was bedeutet "statisches synthetisches"? ):

static final boolean $assertionsDisabled;
  descriptor: Z
  flags: ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC

zusammen mit einem statischen Initialisierer:

 0: ldc           #6                  // class Assert
 2: invokevirtual #7                  // Method java/lang Class.desiredAssertionStatus:()Z
 5: ifne          12
 8: iconst_1
 9: goto          13
12: iconst_0
13: putstatic     #2                  // Field $assertionsDisabled:Z
16: return

und die Hauptmethode ist:

 0: getstatic     #2                  // Field $assertionsDisabled:Z
 3: ifne          22
 6: invokestatic  #3                  // Method java/lang/System.currentTimeMillis:()J
 9: lconst_0
10: lcmp
11: ifeq          22
14: new           #4                  // class java/lang/AssertionError
17: dup
18: invokespecial #5                  // Method java/lang/AssertionError."<init>":()V
21: athrow
22: return

Wir schließen daraus:

  • Es gibt keine Unterstützung auf Bytecode-Ebene für assert : Es handelt sich um ein Java-Sprachkonzept
  • assertkönnte ziemlich gut mit Systemeigenschaften emuliert werden -Pcom.me.assert=true, die -eain der Befehlszeile ersetzt werden sollen, und a throw new AssertionError().
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
2
Die catch (Throwable t)Klausel kann also auch Verstöße gegen die Behauptung auffangen? Für mich beschränkt sich ihre Nützlichkeit nur auf den Fall, dass der Körper der Behauptung zeitaufwändig ist, was selten vorkommt.
Evgeni Sergeev
1
Ich bin mir nicht sicher, warum dies die Nützlichkeit der Behauptung einschränkt. Sie sollten niemals einen Throwable fangen, außer in sehr seltenen Fällen. Wenn Sie Throwable fangen müssen, aber nicht möchten, dass es Behauptungen auffängt, können Sie einfach das AssertionErrorerste fangen und es erneut werfen.
Miguel Munoz
7

Ein Beispiel aus der realen Welt aus einer Stack-Klasse (aus Assertion in Java Articles )

public int pop() {
   // precondition
   assert !isEmpty() : "Stack is empty";
   return stack[--num];
}
Björn
quelle
80
Dies würde in C verpönt sein: Eine Behauptung ist etwas, das WIRKLICH NIEMALS passieren sollte - das Platzen eines leeren Stapels sollte eine NoElementsException oder etwas in der Richtung auslösen. Siehe Donals Antwort.
Konerak
4
Genau. Obwohl dies einem offiziellen Tutorial entnommen ist, ist es ein schlechtes Beispiel.
DJClayworth
7
Dort liegt wahrscheinlich ein Speicherverlust vor. Sie sollten stack [num] = null setzen; damit der GC seine Arbeit richtig macht.
H. Rabiee
3
Ich denke, in einer privaten Methode wäre es richtig, eine Behauptung zu verwenden, da es seltsam wäre, Ausnahmen für eine Fehlfunktion einer Klasse oder Methode zu haben. In einer öffentlichen Methode, die von irgendwo außerhalb aufgerufen wird, kann man nicht wirklich sagen, wie der andere Code sie verwendet. Überprüft es wirklich isEmpty () oder nicht? Du weißt es nicht.
Vlasec
7

Eine Zusicherung ermöglicht das Erkennen von Fehlern im Code. Sie können Zusicherungen zum Testen und Debuggen aktivieren, während Sie sie deaktivieren, wenn Ihr Programm in Produktion ist.

Warum etwas behaupten, wenn Sie wissen, dass es wahr ist? Es ist nur wahr, wenn alles richtig funktioniert. Wenn das Programm einen Fehler aufweist, ist dies möglicherweise nicht der Fall. Wenn Sie dies früher im Prozess erkennen, wissen Sie, dass etwas nicht stimmt.

Eine assertAnweisung enthält diese Anweisung zusammen mit einer optionalen StringNachricht.

Die Syntax für eine assert-Anweisung hat zwei Formen:

assert boolean_expression;
assert boolean_expression: error_message;

Hier sind einige Grundregeln, die regeln, wo Behauptungen verwendet werden sollen und wo sie nicht verwendet werden sollen. Behauptungen sollten verwendet werden für:

  1. Validierung der Eingabeparameter einer privaten Methode. NICHT für öffentliche Methoden. publicMethoden sollten regelmäßige Ausnahmen auslösen, wenn fehlerhafte Parameter übergeben werden.

  2. Überall im Programm, um die Gültigkeit einer Tatsache sicherzustellen, die mit ziemlicher Sicherheit wahr ist.

Wenn Sie beispielsweise sicher sind, dass es sich nur um 1 oder 2 handelt, können Sie eine Behauptung wie die folgende verwenden:

...
if (i == 1)    {
    ...
}
else if (i == 2)    {
    ...
} else {
    assert false : "cannot happen. i is " + i;
}
...
  1. Überprüfung der Post-Bedingungen am Ende einer Methode. Dies bedeutet, dass Sie nach Ausführung der Geschäftslogik mithilfe von Zusicherungen sicherstellen können, dass der interne Status Ihrer Variablen oder Ergebnisse mit Ihren Erwartungen übereinstimmt. Beispielsweise kann eine Methode, die einen Socket oder eine Datei öffnet, eine Zusicherung am Ende verwenden, um sicherzustellen, dass der Socket oder die Datei tatsächlich geöffnet ist.

Behauptungen sollten nicht verwendet werden für:

  1. Validierung der Eingabeparameter einer öffentlichen Methode. Da Zusicherungen möglicherweise nicht immer ausgeführt werden, sollte der reguläre Ausnahmemechanismus verwendet werden.

  2. Überprüfen von Einschränkungen für etwas, das vom Benutzer eingegeben wird. Das gleiche wie oben.

  3. Sollte nicht für Nebenwirkungen verwendet werden.

Dies ist beispielsweise keine ordnungsgemäße Verwendung, da hier die Behauptung für den Nebeneffekt des Aufrufs der doSomething()Methode verwendet wird.

public boolean doSomething() {
...    
}
public void someMethod() {       
assert doSomething(); 
}

Der einzige Fall, in dem dies gerechtfertigt sein könnte, ist, wenn Sie herausfinden möchten, ob Zusicherungen in Ihrem Code aktiviert sind oder nicht:   

boolean enabled = false;    
assert enabled = true;    
if (enabled) {
    System.out.println("Assertions are enabled");
} else {
    System.out.println("Assertions are disabled");
}
Solomkinmv
quelle
5

Zusätzlich zu all den tollen Antworten, die hier gegeben werden, enthält der offizielle Java SE 7-Programmierleitfaden ein ziemlich kurzes Handbuch zur Verwendung assert. mit mehreren Beispielen, wann es eine gute (und vor allem schlechte) Idee ist, Behauptungen zu verwenden, und wie es sich vom Auslösen von Ausnahmen unterscheidet.

Verknüpfung

Ivan Bartsov
quelle
1
Genau. Der Artikel enthält viele hervorragende Beispiele. Mir hat besonders gefallen, dass sichergestellt wird, dass eine Methode nur aufgerufen wird, wenn das Objekt eine Sperre enthält.
MiguelMunoz
4

Assert ist sehr nützlich bei der Entwicklung. Sie verwenden es, wenn etwas einfach nicht kann passieren , wenn Ihr Code ordnungsgemäß funktioniert. Es ist einfach zu bedienen und kann für immer im Code bleiben, da es im wirklichen Leben ausgeschaltet wird.

Wenn es eine Chance gibt, dass der Zustand im wirklichen Leben auftreten kann, müssen Sie damit umgehen.

Ich liebe es, weiß aber nicht, wie ich es in Eclipse / Android / ADT einschalten soll. Es scheint auch beim Debuggen ausgeschaltet zu sein. (Es gibt einen Thread dazu, der sich jedoch auf die 'Java VM' bezieht, die in der ADT-Ausführungskonfiguration nicht angezeigt wird.)

John White
quelle
1
Um die Bestätigung in der Eclipse-IDE zu aktivieren, folgen Sie bitte tutoringcenter.cs.usfca.edu/resources/…
Ayaz Pasha
Ich glaube nicht, dass es eine Möglichkeit gibt, Asserts in Android zu aktivieren. Das ist sehr enttäuschend.
MiguelMunoz
3

Hier ist eine Behauptung, die ich auf einem Server für ein Hibernate / SQL-Projekt geschrieben habe. Eine Entity-Bean hatte zwei effektiv boolesche Eigenschaften, isActive und isDefault. Jeder könnte einen Wert von "Y" oder "N" oder null haben, der als "N" behandelt wurde. Wir möchten sicherstellen, dass der Browser-Client auf diese drei Werte beschränkt ist. In meinen Setzern für diese beiden Eigenschaften habe ich diese Behauptung hinzugefügt:

assert new HashSet<String>(Arrays.asList("Y", "N", null)).contains(value) : value;

Beachten Sie Folgendes.

  1. Diese Behauptung gilt nur für die Entwicklungsphase. Wenn der Kunde einen schlechten Wert sendet, werden wir diesen frühzeitig erkennen und beheben, lange bevor wir die Produktion erreichen. Behauptungen beziehen sich auf Mängel, die Sie frühzeitig erkennen können.

  2. Diese Behauptung ist langsam und ineffizient. Das ist okay. Behauptungen sind frei, langsam zu sein. Es ist uns egal, weil es sich nur um Entwicklungswerkzeuge handelt. Dies wird den Produktionscode nicht verlangsamen, da Zusicherungen deaktiviert werden. (In diesem Punkt gibt es einige Meinungsverschiedenheiten, auf die ich später noch eingehen werde.) Dies führt zu meinem nächsten Punkt.

  3. Diese Behauptung hat keine Nebenwirkungen. Ich hätte meinen Wert gegen ein nicht modifizierbares statisches endgültiges Set testen können, aber dieses Set wäre in der Produktion geblieben, wo es niemals verwendet würde.

  4. Diese Behauptung dient dazu, den ordnungsgemäßen Betrieb des Clients zu überprüfen. Wenn wir also die Produktion erreichen, werden wir sicher sein, dass der Client ordnungsgemäß funktioniert, sodass wir die Behauptung sicher deaktivieren können.

  5. Einige Leute fragen dies: Wenn die Behauptung in der Produktion nicht benötigt wird, warum nehmen Sie sie nicht einfach heraus, wenn Sie fertig sind? Weil Sie sie immer noch benötigen, wenn Sie mit der Arbeit an der nächsten Version beginnen.

Einige Leute haben argumentiert, dass Sie niemals Behauptungen verwenden sollten, weil Sie niemals sicher sein können, dass alle Fehler verschwunden sind, sodass Sie sie auch in der Produktion beibehalten müssen. Es macht also keinen Sinn, die assert-Anweisung zu verwenden, da der einzige Vorteil von asserts darin besteht, dass Sie sie deaktivieren können. Daher sollten Sie nach dieser Überlegung (fast) niemals Asserts verwenden. Ich stimme dir nicht zu. Es ist sicher richtig, dass Sie keinen Assert verwenden sollten, wenn ein Test in die Produktion gehört. Dieser Test tut dies jedoch nicht in die Produktion. Dieser dient dazu, einen Fehler zu erkennen, der wahrscheinlich nie die Produktion erreichen wird. Daher kann er sicher ausgeschaltet werden, wenn Sie fertig sind.

Übrigens hätte ich es so schreiben können:

assert value == null || value.equals("Y") || value.equals("N") : value;

Dies ist nur für drei Werte in Ordnung, aber wenn die Anzahl der möglichen Werte größer wird, wird die HashSet-Version bequemer. Ich habe mich für die HashSet-Version entschieden, um auf Effizienz hinzuweisen.

MiguelMunoz
quelle
Ich bezweifle stark, dass die Verwendung eines so kleinen a HashSeteinen Geschwindigkeitsvorteil gegenüber einem bringt ArrayList. Darüber hinaus dominieren die Set- und Listenkreationen die Suchzeit. Sie würden gut damit umgehen, wenn sie eine Konstante verwenden. Das alles sagte, +1.
Maaartinus
Alles wahr. Ich habe es auf diese ineffiziente Weise getan, um meinen Standpunkt zu veranschaulichen, dass Behauptungen frei sind, langsam zu sein. Dieser könnte effizienter gestaltet werden, aber es gibt andere, die dies nicht können. In einem ausgezeichneten Buch mit dem Titel "Writing Solid Code" berichtet Steve Maguire von einer Behauptung in Microsoft Excel, den neuen Code für inkrementelle Aktualisierungen zu testen, bei dem Zellen übersprungen wurden, die sich nicht ändern sollten. Jedes Mal, wenn der Benutzer eine Änderung vornimmt, berechnet die Zusicherung die gesamte Tabelle neu, um sicherzustellen, dass die Ergebnisse mit denen der inkrementellen Aktualisierungsfunktion übereinstimmen. Es hat die Debug-Version wirklich verlangsamt, aber sie haben alle ihre Fehler frühzeitig erkannt.
Miguel Munoz
Völlig einverstanden. Behauptungen sind eine Art Test - sie sind weniger vielseitig als gewöhnliche Tests, aber sie können private Methoden abdecken und sind viel billiger zu schreiben. Ich werde versuchen, sie noch mehr zu verwenden.
Maaartinus
2

Assertion wird im Wesentlichen zum Debuggen der Anwendung verwendet oder als Ersatz für die Ausnahmebehandlung für einige Anwendungen, um die Gültigkeit einer Anwendung zu überprüfen.

Assertion funktioniert zur Laufzeit. Ein einfaches Beispiel, das das gesamte Konzept sehr einfach erklären kann, ist hier - Was macht das Schlüsselwort assert in Java? (WikiAnswers).

SBTec
quelle
2

Zusicherungen sind standardmäßig deaktiviert. Um sie zu aktivieren, müssen wir das Programm mit -eaOptionen ausführen (Granularität kann variiert werden). Zum Beispiel java -ea AssertionsDemo.

Es gibt zwei Formate für die Verwendung von Zusicherungen:

  1. Einfach: zB. assert 1==2; // This will raise an AssertionError.
  2. Besser: assert 1==2: "no way.. 1 is not equal to 2"; Dies löst einen AssertionError mit der angezeigten Meldung aus und ist daher besser. Obwohl in der eigentlichen Syntax assert expr1:expr2expr2 ein beliebiger Ausdruck sein kann, der einen Wert zurückgibt, habe ich ihn häufiger nur zum Drucken einer Nachricht verwendet.
Chandan Purohit
quelle
1

Um es noch einmal zusammenzufassen (und dies gilt für viele Sprachen, nicht nur für Java):

"assert" wird hauptsächlich als Debugging-Hilfe von Softwareentwicklern während des Debugging-Prozesses verwendet. Assert-Nachrichten sollten niemals erscheinen. Viele Sprachen bieten eine Option zur Kompilierungszeit, die dazu führt, dass alle "Asserts" ignoriert werden, um "Produktions" -Code zu generieren.

"Ausnahmen" sind eine praktische Möglichkeit, alle Arten von Fehlerzuständen zu behandeln, unabhängig davon, ob sie logische Fehler darstellen oder nicht. Wenn Sie auf einen Fehlerzustand stoßen, bei dem Sie nicht fortfahren können, können Sie sie einfach "in die Luft werfen". "Von wo auch immer Sie sind, erwarten Sie, dass jemand anderes bereit ist, sie zu" fangen ". Die Kontrolle wird in einem Schritt direkt vom Code, der die Ausnahme ausgelöst hat, direkt auf den Handschuh des Fängers übertragen. (Und der Fänger kann die vollständige Rückverfolgung der getätigten Anrufe sehen.)

Darüber hinaus müssen Aufrufer dieser Unterroutine nicht überprüfen, ob die Unterroutine erfolgreich war: "Wenn wir jetzt hier sind, muss sie erfolgreich gewesen sein, da sie sonst eine Ausnahme ausgelöst hätte und wir jetzt nicht hier wären!"Diese einfache Strategie erleichtert das Code-Design und das Debuggen erheblich.

Ausnahmen ermöglichen es, dass schwerwiegende Fehlerbedingungen so sind, wie sie sind: "Ausnahmen von der Regel". Und damit sie von einem Codepfad gehandhabt werden, der auch "eine Ausnahme von der Regel ... " ist!

Mike Robinson
quelle
1

Behauptungen sind Prüfungen, die möglicherweise ausgeschaltet werden. Sie werden selten benutzt. Warum?

  • Sie dürfen nicht zum Überprüfen von Argumenten für öffentliche Methoden verwendet werden, da Sie keine Kontrolle über sie haben.
  • Sie sollten nicht für einfache Überprüfungen verwendet werden, result != nullda solche Überprüfungen sehr schnell sind und kaum etwas zu speichern ist.

Also, was ist noch übrig? Teure Überprüfungen auf Bedingungen, von denen wirklich erwartet wird , dass sie wahr sind. Ein gutes Beispiel wären die Invarianten einer Datenstruktur wie RB-Tree. Tatsächlich gibt es in ConcurrentHashMapJDK8 einige derart aussagekräftige Aussagen für die TreeNodes.

  • Sie möchten sie in der Produktion wirklich nicht einschalten, da sie die Laufzeit leicht dominieren können.
  • Möglicherweise möchten Sie sie während der Tests ein- oder ausschalten.
  • Sie möchten sie auf jeden Fall einschalten, wenn Sie am Code arbeiten.

Manchmal ist der Scheck nicht wirklich teuer, aber gleichzeitig sind Sie sich ziemlich sicher, dass er bestanden wird. In meinem Code gibt es z.

assert Sets.newHashSet(userIds).size() == userIds.size();

Ich bin mir ziemlich sicher, dass die Liste, die ich gerade erstellt habe, eindeutige Elemente enthält, aber ich wollte sie dokumentieren und überprüfen.

Maaartinus
quelle
0

Grundsätzlich wird "assert true" bestanden und "assert false" wird fehlschlagen. Schauen wir uns an, wie das funktionieren wird:

public static void main(String[] args)
{
    String s1 = "Hello";
    assert checkInteger(s1);
}

private static boolean checkInteger(String s)
{
    try {
        Integer.parseInt(s);
        return true;
    }
    catch(Exception e)
    {
        return false;
    }
}
Peter Mortensen
quelle
-8

assertist ein Schlüsselwort. Es wurde in JDK 1.4 eingeführt. Das sind zwei Arten von asserts

  1. Sehr einfache assertAussagen
  2. Einfache assertAussagen.

Standardmäßig assertwerden nicht alle Anweisungen ausgeführt. Wenn eine assertAnweisung false erhält, wird automatisch ein Assertionsfehler ausgelöst.

pavani
quelle
1
Es gibt kein Beispiel aus dem wirklichen Leben, was das Ziel der Frage ist
Rubenafo
1
Haben Sie gerade kopiert und eingefügt von: amazon.com/Programmer-Study-1Z0-803-1Z0-804-Certification/dp/… ?
Koray Tugay