Sollten Sie mit der assert-Anweisung im Produktionscode nicht null bestätigen? [geschlossen]

32

Ich habe diese Frage gesehen, habe aber noch einige Fragen zur Verwendung des assertSchlüsselworts. Ich habe mit ein paar anderen Programmierern über die Verwendung diskutiert assert. Für diesen Anwendungsfall gab es eine Methode, die null zurückgeben kann, wenn bestimmte Voraussetzungen erfüllt sind. Der von mir geschriebene Code ruft die Methode auf, gibt dann an, dass sie nicht null zurückgibt, und verwendet weiterhin das zurückgegebene Objekt.

Beispiel:

class CustomObject {
    private Object object;

    @Nullable
    public Object getObject() {
        return (object == null) ? generateObject() : object;
    }
}

Stellen Sie sich jetzt vor, ich benutze es so:

public void useObject(CustomObject customObject) {
    object = customObject.getObject();
    assert object != null;
    // Do stuff using object, which would throw a NPE if object is null.
}

Mir wurde gesagt, ich sollte das entfernen assert, dass sie niemals im Produktionscode verwendet werden sollten, sondern nur zum Testen. Ist das wahr?

Big_Bad_E
quelle
19
Standardmäßig sind Zusicherungen deaktiviert. Sie müssen sie zur Laufzeit explizit über -eaoder gleichwertig aktivieren .
Jarmod
Verwandte Frage zum Software Engineering: softwareengineering.stackexchange.com/q/137158/187318 (obwohl es ziemlich alt ist, empfiehlt die dort akzeptierte Antwort immer noch eine externe Bibliothek, die seit der Einführung Objects.requireNonNullin Java 8 nicht mehr erforderlich ist ).
Hulk
Dieser Fragentitel ist viel zu weit gefasst, aber die im Körper angegebene Frage ist viel enger und wird gemäß den Antworten vollständig von Hilfsfunktionen behandelt.
17.
Um klar zu sein, fragen Sie, ob Client- Code Nicht-Null-Überprüfungen / Asserts für Objekte implementieren soll. (Das unterscheidet sich von der Frage, ob der Bibliothekscode selbst garantieren soll, dass Objekte nicht null sein können, oder dass dies behauptet wird).
17.
1
Mögliches Duplikat: Wann sollten Zusicherungen im Produktionscode verbleiben?
Peter Mortensen

Antworten:

19

Verwenden Sie Objects.requireNonNull(Object)dafür.

Überprüft, ob die angegebene Objektreferenz nicht null ist. Diese Methode wurde hauptsächlich für die Parametervalidierung in Methoden und Konstruktoren entwickelt, [...]

In Ihrem Fall wäre dies:

public void useObject(CustomObject customObject) {
    object = customObject.getObject();
    Objects.requireNonNull(object);
    // Do stuff using object, which would throw a NPE if object is null.
}

Diese Funktion wurde für die von Ihnen genannten Zwecke ausgeführt, dh markieren Sie explizit, was nicht null sein soll. auch in der Produktion. Der große Vorteil ist, dass Sie sicherstellen, dass Sie Nullwerte genau dort finden, wo sie überhaupt nicht auftreten sollten. Sie haben weniger Probleme beim Debuggen von Problemen, die durch Nullwerte verursacht werden, die an einer Stelle übergeben wurden, an der sie nicht sein sollten.

Ein weiterer Vorteil ist die zusätzliche Flexibilität bei Nullprüfungen im Gegensatz zu assert. Während assertes sich um ein Schlüsselwort zum Überprüfen eines booleschen Werts handelt, Objects.requireNonNull(Object)handelt es sich um eine Funktion, die viel flexibler und lesbarer in Code eingebettet werden kann. Z.B:

Foo foo = Objects.requireNonNull(service.fetchFoo());

// You cannot write it in on line.
Bar bar = service.fetchBar();
assert bar != null;
service.foo(Objects.requireNonNull(service.getBar()));

// You cannot write it in on line.
Bar bar = service.getBar();
assert bar != null;
service.foo(bar);

Beachten Sie, dass dies Objects.requireNonNull(Object)ausschließlich zur Nullprüfung dient, wenn dies assertverallgemeinert ist. asserthat in dieser Hinsicht leicht unterschiedliche Zwecke, dh hauptsächlich das Testen. Es muss aktiviert sein, damit Sie es zum Testen aktivieren und für die Produktion deaktivieren können. Verwenden Sie es sowieso nicht für Produktionscode. Dies könnte die Anwendung durch unnötige und komplizierte Validierungen verlangsamen, die zum Testen vorgesehen sind. Verwenden Sie diese Option, um Nur-Test-Prüfungen von Prüfungen zu trennen, die auch für die Produktion bestimmt sind.

Weitere Informationen finden Sie in der offiziellen Dokumentationassert .

akuzminykh
quelle
14
Wer und wann wird als Vermächtnis deklariert? Irgendwelche Links / Referenzen? Ich kann mir keinen vernünftigen Entwickler vorstellen, der "Legacy" des Tools definiert, das Validierungen ohne Footprint ermöglicht, die in Tests einfach aktiviert und in der Produktion deaktiviert werden können.
Dmitry Pisklov
Angesichts der Tatsache, dass Sie zur Laufzeit entscheiden können, ob Assert ausgeführt wird, aber zur Laufzeit nicht bestimmen können, ob die NPE von requireNonNull ausgelöst wird, was meinen Sie mit "Sie haben mehr Kontrolle damit als mit Assert"?
Pete Kirkham
@DmitryPisklov Du hast recht, das habe ich bearbeitet. Es ist ein großartiges Tool zum Testen.
Akuzminykh
2
@PeteKirkham Du hast recht, Kontrolle war das falsche Wort dafür. Ich habe mehr über die Flexibilität in der Syntax gesprochen. Außerdem: Hier finden Sie Gründe, warum Code NPEs auslösen soll.
Akuzminykh
1
"Verwenden Sie es nicht für Produktionscode. Es könnte die Anwendung verlangsamen." Es könnte, aber es könnte sich lohnen. Java verfügt über integrierte Laufzeitprüfungen, um sicherzustellen, dass Sie ein Array niemals überlaufen. Diese sind trotz möglicher Leistungseinbußen auch bei Produktionsbereitstellungen aktiviert. Wir haben nicht einmal die Wahl. Es ist nicht immer falsch, Laufzeitprüfungen im Produktionscode durchzuführen.
Max Barraclough
22

Das Wichtigste, an das Sie sich bei Behauptungen erinnern sollten, ist, dass sie deaktiviert werden können. Gehen Sie also niemals davon aus, dass sie ausgeführt werden.

Aus Gründen der Abwärtskompatibilität deaktiviert die JVM die Validierung von Zusicherungen standardmäßig. Sie müssen explizit aktiviert werden, indem entweder das Befehlszeilenargument -enableassertions oder die Kurzform -ea verwendet wird:

java -ea com.whatever.assertion.Assertion

Es ist also keine gute Praxis, sich auf sie zu verlassen.

Da Zusicherungen nicht standardmäßig aktiviert sind, können Sie niemals davon ausgehen, dass sie bei Verwendung im Code ausgeführt werden. Sie sollten daher immer nach Nullwerten und leeren Optionals suchen, die Verwendung von Zusicherungen zum Einchecken von Eingaben in eine öffentliche Methode vermeiden und stattdessen eine ungeprüfte Ausnahme verwenden ... Führen Sie im Allgemeinen alle Überprüfungen so durch, als ob die Zusicherung nicht vorhanden wäre.

Jeprubio
quelle
Offensichtlich schlechte Praxis, sich auf sie zu verlassen, aber ist es schlechte Praxis, sie im Allgemeinen zu verwenden?
Big_Bad_E
3
@Big_Bad_E Assertions sind ein Debug-Tool, mit dem ein Programm so früh und so laut wie möglich fehlschlägt. Es ist dann Aufgabe der Testsuite, sicherzustellen, dass ein Programm trotz der Behauptungen auf keinen Fall fehlschlagen kann . Die Idee ist, dass sobald die Testsuite (sie hat 100% Abdeckung, nicht?!?) Ohne Fehler ausgeführt wird, es sicher ist, die Behauptungen zu entfernen, da sie sowieso nicht ausgelöst werden können. Sicher, in dieser Argumentation steckt ein bisschen Idealismus, aber das ist die Idee.
cmaster
11

Sicherlich ist das, was Ihnen gesagt wird, eine offensichtliche Lüge. Hier ist der Grund.

Zusicherungen sind standardmäßig deaktiviert, wenn Sie nur eigenständiges JVM ausführen. Wenn sie deaktiviert sind, haben sie keinen Platzbedarf, daher haben sie keine Auswirkungen auf Ihre Produktionsanwendung. Sie sind jedoch wahrscheinlich Ihre besten Freunde beim Entwickeln und Testen Ihres Codes, und die meisten Test-Framework-Läufer aktivieren Assertions (JUnit), sodass Ihr Assertion-Code ausgeführt wird, wenn Sie Ihre Unit-Tests ausführen, um potenzielle Fehler früher zu erkennen (z Sie können Asserts für einige Business Logic Boundary Checks hinzufügen. Dies hilft dabei, Code zu erkennen, der unangemessene Werte verwendet.

Wie aus der anderen Antwort hervorgeht, können Sie sich aus genau diesem Grund (sie sind nicht immer aktiviert) nicht auf Behauptungen verlassen, um wichtige Überprüfungen durchzuführen oder (insbesondere!) Einen Zustand beizubehalten.

Ein interessantes Beispiel für die Verwendung von Asserts finden Sie hier. Am Ende der Datei befindet sich eine Methode, singleThreadedAccess()die aus der assert-Anweisung in Zeile 201 aufgerufen wird und dazu dient, potenziellen Multithread-Zugriff in Tests abzufangen.

Dmitry Pisklov
quelle
4

Die anderen Antworten decken dies bereits gut genug ab, aber es gibt andere Möglichkeiten.

Zum Beispiel hat Spring eine statische Methode:

org.springframework.util.Assert.notNull(obj)

Es gibt auch andere Bibliotheken mit eigenen Assert.something()Methoden. Es ist auch ziemlich einfach, eigene zu schreiben.

Beachten Sie jedoch, welche Ausnahmen Sie auslösen, wenn es sich um einen Webdienst handelt. Die zuvor erwähnte Methode löst beispielsweise eine aus, IllegalArgumentExceptiondie in Spring standardmäßig eine 500 zurückgibt.

Im Fall eines Webdienstes handelt es sich häufig nicht um einen internen Serverfehler, und es sollte sich nicht um einen 500 handeln, sondern um einen 400, was eine schlechte Anforderung darstellt.

Christopher Schneider
quelle
1
Wenn die "Assert" -Methode den Prozess nicht sofort zum Absturz bringt und stattdessen eine Ausnahme auslöst, handelt es sich nicht um eine Assert-Methode. Der springende Punkt assertist, einen sofortigen Absturz mit einer erstellten Kerndatei zu erreichen, damit Sie mit Ihrem bevorzugten Debugger genau an der Stelle, an der die Bedingung verletzt wurde, ein Post-Mortem durchführen können.
cmaster
Asserts stürzen einen Prozess nicht sofort ab. Sie werfen einen Fehler, der abgefangen werden kann. Nicht behandelte Ausnahmen führen zum Absturz Ihrer Tests sowie zu Fehlern. Sie können über Semantik streiten, wenn Sie möchten. Wenn Sie dies wünschen, schreiben Sie eine benutzerdefinierte Zusicherung, die anstelle einer Ausnahme einen Fehler auslöst. Tatsache ist, dass in der überwiegenden Mehrheit der Fälle die geeignete Maßnahme darin besteht, eher eine Ausnahme als einen Fehler auszulösen.
Christopher Schneider
Ah, Java definiert tatsächlich assertzu werfen. Interessant. Die C / C ++ - Version macht so etwas nicht. Es wird sofort ein Signal ausgelöst, dass a) der Prozess abgebrochen wird und b) ein Core Dump erstellt wird. Dies geschieht aus einem Grund: Es ist kinderleicht, einen solchen Assertionsfehler zu debuggen, da Sie immer noch über die gesamten Informationen zum Aufrufstapel verfügen. Die Definition assert, stattdessen eine Ausnahme auszulösen, die dann (un) absichtlich programmatisch abgefangen werden kann, vereitelt den Zweck, imho.
cmaster
So wie es in C und C ++ einige (oder viele) seltsame Dinge gibt, gibt es auch einige in Java. Einer von ihnen ist Throwable. Sie können immer gefangen werden. Ob das gut ist oder nicht, weiß ich nicht. Ich denke es kommt darauf an. Viele Java-Programme sind Webdienste, und es wäre unerwünscht, wenn sie aus fast jedem Grund abstürzen würden, sodass fast alles abgefangen und protokolliert wird. Eines der großartigen Dinge ist die Stapelverfolgung, und dies reicht normalerweise aus, um den Grund für eine Ausnahme oder einen Fehler selbst zu diagnostizieren.
Christopher Schneider
3

Verwenden Sie Asserts großzügig, wenn dies dazu beiträgt, Programmierfehler, z. B. Fehler, zu erkennen.

Verwenden Sie assert nicht, um etwas abzufangen, das logisch passieren könnte, dh schlecht formatierte Eingaben. Verwenden Sie assert nur, wenn der Fehler nicht behoben werden kann.

Fügen Sie keine Produktionslogik in den Code ein, der ausgeführt wird, wenn die Zusicherung überprüft wird. Wenn Ihre Software gut geschrieben ist, ist dies trivial wahr, aber wenn dies nicht der Fall ist, können subtile Nebenwirkungen und ein unterschiedliches Gesamtverhalten auftreten, wenn Behauptungen aktiviert und deaktiviert sind.

Wenn in Ihrem Unternehmen "Testcode" und "Produktionscode" dasselbe tun, jedoch unterschiedliche Codebasen (oder unterschiedliche Bearbeitungsstufen), verlassen Sie diese und kommen Sie nie wieder zurück. Der Versuch, dieses Maß an Inkompetenz zu beheben, ist wahrscheinlich Zeitverschwendung. Wenn Ihr Unternehmen keine Assert-Anweisung außerhalb des Testcodes platziert, teilen Sie ihm bitte mit, dass Asserts im Produktionsbuild deaktiviert sind und dass die Behebung dieses Fehlers jetzt Ihre erste Priorität ist, wenn dies nicht der Fall ist.

Der Wert von Asserts ist genau innerhalb der Geschäftslogik und nicht nur innerhalb der Testsuite zu verwenden. Dies macht es einfach, viele Tests auf hoher Ebene durchzuführen, bei denen nicht viele Dinge explizit getestet werden müssen, um große Teile Ihres Codes zu durchlaufen und all diese Behauptungen auszulösen. In einigen meiner Projekte haben typische Tests nicht einmal wirklich etwas bestätigt, sondern nur eine Berechnung angeordnet, die auf bestimmten Eingaben basiert. Dies führte dazu, dass Hunderte von Aussagen überprüft wurden und Probleme selbst in winzigen logischen Teilen tief im Inneren gefunden wurden.

Kafein
quelle
2

Sie können assert jederzeit verwenden. Die Debatte kommt, wann zu verwenden. Zum Beispiel in der Anleitung :

  • Verwenden Sie keine Zusicherungen zur Argumentprüfung in öffentlichen Methoden.
  • Verwenden Sie keine Zusicherungen, um Arbeiten auszuführen, die Ihre Anwendung für den korrekten Betrieb benötigt.
Gatusko
quelle
4
Gute Regeln. Aber die Antwort wäre besser, wenn einige Gründe hinzugefügt würden.
cmaster