Der beste Weg, um mit Nullen in Java umzugehen? [geschlossen]

21

Ich habe einen Code, der wegen NullPointerException fehlschlägt. Für das Objekt wird eine Methode aufgerufen, bei der das Objekt nicht vorhanden ist.

Dies veranlasste mich jedoch, über den besten Weg nachzudenken, um dies zu beheben. Codiere ich immer defensiv für Nullen, damit ich zukunftssicheren Code für Nullzeigerausnahmen erhalte, oder behebe ich die Ursache für die Null, damit sie nicht nachgeschaltet auftritt.

Was sind deine Gedanken?

Shaun F
quelle
5
Warum würden Sie nicht "die Ursache der Null beheben"? Können Sie ein Beispiel dafür geben, warum dies nicht die einzige vernünftige Wahl ist? Offensichtlich muss dich etwas vom Offensichtlichen abbringen. Was ist es? Warum ist es nicht Ihre erste Wahl, die Ursache zu beheben?
S.Lott
Das Beheben der "Grundursache" des Problems kann kurzfristig hilfreich sein, aber wenn der Code immer noch nicht defensiv auf Nullen überprüft und von einem anderen Prozess wiederverwendet wird, der möglicherweise eine Null einführt, muss er erneut behoben werden.
Shaun F
2
@ Shaun F: Wenn der Code kaputt ist, ist er kaputt. Ich verstehe nicht, wie es möglich ist, dass Code unerwartete Nullen erzeugt und nicht repariert wird. Es ist klar, dass etwas "dummes Management" oder "politisches" los ist. Oder etwas, das erlaubt, dass fehlerhafter Code irgendwie akzeptabel ist. Können Sie erklären, warum fehlerhafter Code nicht behoben wird? Was ist der politische Hintergrund, der Sie dazu bringt, diese Frage zu stellen?
S.Lott
1
"anfällig für spätere Datenerstellung mit Nullen" Was bedeutet das überhaupt? Wenn "Ich kann die Datenerstellungsroutine reparieren", liegt keine weitere Sicherheitsanfälligkeit vor. Ich kann nicht verstehen, was diese "spätere Datenerstellung mit Nullen" möglicherweise bedeuten kann. Buggy-Software?
S.Lott,
1
@ S.Lott, Kapselung und Einzelverantwortung. Ihr gesamter Code "weiß" nicht, was Ihr gesamter anderer Code tut. Wenn eine Klasse Froobinatoren akzeptiert, werden so wenige Annahmen wie möglich darüber getroffen, wer und wie den Froobinator erstellt hat. Es kommt aus "externem" Code, oder es kommt einfach aus einem anderen Teil der Codebasis. Ich sage nicht, dass Sie Buggy-Code nicht reparieren sollten; Aber suchen Sie nach "defensiver Programmierung" und Sie werden herausfinden, worauf sich das OP bezieht.
Paul Draper

Antworten:

45

Wenn null ein sinnvoller Eingabeparameter für Ihre Methode ist, korrigieren Sie die Methode. Wenn nicht, beheben Sie den Anrufer. "Vernünftig" ist ein flexibler Begriff, daher schlage ich den folgenden Test vor: Wie soll die Methode eine Null-Eingabe behandeln? Wenn Sie mehr als eine mögliche Antwort finden, ist Null keine sinnvolle Eingabe.

user281377
quelle
1
Es ist wirklich so einfach.
biziclop
3
Guava hat einige sehr nette Hilfsmethoden, die den Null-Check so einfach wie möglich machen Precondition.checkNotNull(...). Siehe stackoverflow.com/questions/3022319/…
Meine Regel ist, wenn möglich alles auf vernünftige Standardwerte zu initialisieren und sich danach Gedanken über Null-Ausnahmen zu machen.
Davidk01
Thorbjørn: Ersetzt es also eine versehentliche NullPointerException durch eine beabsichtigte NullPointerException? In einigen Fällen kann die frühzeitige Überprüfung nützlich oder sogar notwendig sein. Aber ich fürchte, es gibt viele sinnlose Null-Checks, die wenig Wert hinzufügen und das Programm nur vergrößern.
user281377
6
Wenn null kein vernünftiger Eingabeparameter für Ihre Methode ist, aber Aufrufe der Methode wahrscheinlich versehentlich null übergeben (insbesondere, wenn die Methode öffentlich ist), möchten Sie möglicherweise auch, dass Ihre Methode eine auslöst, IllegalArgumentExceptionwenn null angegeben wird. Dies signalisiert den Aufrufern der Methode, dass sich der Fehler in ihrem Code befindet (und nicht in der Methode selbst).
Brian
20

Verwenden Sie nicht null, sondern optional

Wie Sie bereits betont haben, besteht eines der größten Probleme nullin Java darin, dass es überall oder zumindest für alle Referenztypen verwendet werden kann.

Es ist unmöglich zu sagen, dass das sein könnte nullund was nicht sein könnte.

Java 8 bietet eine viel bessere Muster: Optional.

Und ein Beispiel von Oracle:

String version = "UNKNOWN";
if(computer != null) {
  Soundcard soundcard = computer.getSoundcard();
  if(soundcard != null) {
    USB usb = soundcard.getUSB();
    if(usb != null) {
      version = usb.getVersion();
    }
  }
}

Wenn jeder dieser Werte einen erfolgreichen Wert zurückgibt oder nicht, können Sie die APIs in Optionals ändern :

String name = computer.flatMap(Computer::getSoundcard)
    .flatMap(Soundcard::getUSB)
    .map(USB::getVersion)
    .orElse("UNKNOWN");

Durch die explizite Codierung von Optionalität im Typ werden Ihre Schnittstellen viel besser und Ihr Code wird sauberer.

Wenn Sie nicht Java 8 verwenden, können Sie com.google.common.base.Optionalin Google Guava nachsehen .

Eine gute Erklärung vom Guava-Team: https://github.com/google/guava/wiki/UsingAndAvoidingNullExplained

Eine allgemeinere Erklärung der Nachteile gegen Null mit Beispielen aus mehreren Sprachen: https://www.lucidchart.com/techblog/2015/08/31/the-worst-mistake-of-computer-science/


@Nonnull, @Nullable

Java 8 fügt diese Annotation hinzu, um Tools zur Codeüberprüfung wie IDEs beim Erkennen von Problemen zu unterstützen. Sie sind in ihrer Wirksamkeit ziemlich begrenzt.


Prüfen Sie, wann es Sinn macht

Schreiben Sie nicht 50% Ihres Codes und überprüfen Sie null, insbesondere wenn es nichts Sinnvolles gibt, das Ihr Code mit einem nullWert tun kann.

Auf der anderen Seite sollten Sie nullsicherstellen, dass Sie etwas verwenden , wenn es verwendet werden könnte und etwas bedeutet.


Letztendlich kann man nullJava natürlich nicht entfernen . Ich empfehle nachdrücklich, die OptionalAbstraktion zu ersetzen, wann immer dies möglich ist, und zu überprüfen null, ob Sie in anderen Fällen etwas Vernünftiges tun können.

Paul Draper
quelle
2
Normalerweise werden Antworten auf vier Jahre alte Fragen aufgrund schlechter Qualität gelöscht. Gute Arbeit, wenn wir darüber sprechen, wie Java 8 (das es damals noch nicht gab) das Problem lösen kann.
aber irrelevant für die ursprüngliche Frage natürlich. Und was ist zu sagen, das Argument ist in der Tat optional?
4.
5
@jwenting Es ist völlig relevant für die ursprüngliche Frage. Die Antwort auf "Was ist der beste Weg, um einen Nagel mit einem Schraubendreher zu schlagen" lautet "Verwenden Sie einen Hammer anstelle eines Schraubendrehers".
Daenyth
@jwenting, ich denke, ich habe das behandelt, aber aus Gründen der Klarheit: Ist das Argument nicht optional, würde ich normalerweise nicht auf null prüfen. Sie haben 100 Zeilen Nullprüfung und 40 Zeilen Substanz. Überprüfen Sie die öffentlicheren Schnittstellen auf Null, oder prüfen Sie, ob Null tatsächlich sinnvoll ist (dh das Argument ist optional).
Paul Draper
4
@ J-Boss, wie wärst du in Java nicht abgehakt NullPointerException? A NullPointerExceptionkann buchstäblich jedes Mal auftreten, wenn Sie eine Instanzmethode in Java aufrufen. Sie würden throws NullPointerExceptionin fast jeder einzelnen Methode jemals haben.
Paul Draper
8

Es gibt mehrere Möglichkeiten, damit umzugehen. Das Peppen Ihres Codes if (obj != null) {}ist nicht ideal, es ist chaotisch, es verursacht beim späteren Lesen des Codes während des Wartungszyklus Geräusche und ist fehleranfällig, da es leicht zu vergessen ist, diese Boiler- Plattenumhüllung vorzunehmen .

Es hängt davon ab, ob der Code weiterhin im Hintergrund ausgeführt werden soll oder fehlschlägt. Ist das nullein Fehler oder eine erwartete Bedingung.

Was ist null?

In jeder Definition und jedem Fall steht Null für den absoluten Mangel an Daten. Nullen in Datenbanken stehen für das Fehlen eines Werts für diese Spalte. Eine Null Stringist nicht dasselbe wie eine leere String, eine Null intist theoretisch nicht dasselbe wie NULL. In der Praxis "kommt es darauf an". Empty Stringkann eine gute Null ObjectImplementierung für die String-Klasse darstellen, da Integerdies von der Geschäftslogik abhängt.

Alternativen sind:

  1. Das Null ObjectMuster. Erstellen Sie eine Instanz Ihres Objekts, die den nullStatus darstellt, und initialisieren Sie alle Verweise auf diesen Typ mit einem Verweis auf die NullImplementierung. Dies ist nützlich für einfache Werttyp - Objekte , die nicht viel von Verweisen auf andere Objekte, die auch sein könnte , nullsein und wird voraussichtlich nullals einen gültigen Zustand.

  2. Verwenden Sie aspektorientierte Werkzeuge, um Methoden mit einem Null CheckerAspekt zu weben , der verhindert, dass die Parameter null sind. Dies gilt für Fälle, in denen nullein Fehler vorliegt.

  3. Verwenden Sie assert()nicht viel besser als die, if (obj != null){}aber weniger Lärm.

  4. Verwenden Sie ein Vertragsdurchsetzungsprogramm wie " Verträge für Java" . Gleicher Anwendungsfall wie AspectJ, jedoch neuer und verwendet Anmerkungen anstelle externer Konfigurationsdateien. Das Beste aus beiden Arbeiten von Aspects and Asserts.

1 ist die ideale Lösung, wenn bekannt ist, dass eingehende Daten nulldurch einen Standardwert ersetzt werden müssen, damit sich vorgelagerte Verbraucher nicht mit dem gesamten Code für die Überprüfung des Nullwerts auseinandersetzen müssen. Das Abgleichen mit bekannten Standardwerten ist ebenfalls aussagekräftiger.

2, 3 und 4 sind nur praktische Alternativen, um Ausnahmegeneratoren durch NullPointerExceptionetwas Informativeres zu ersetzen , was immer eine Verbesserung darstellt.

Schlussendlich

nullin Java ist fast immer ein logischer Fehler. Sie sollten sich immer bemühen, die Grundursache zu beseitigen NullPointerExceptions. Sie sollten sich bemühen, nullBedingungen nicht als Geschäftslogik zu verwenden. if (x == null) { i = someDefault; }Nehmen Sie einfach die erste Zuweisung zu dieser Standardobjektinstanz vor.


quelle
Wenn möglich, ist das Null-Objektmuster der richtige Weg, um einen Null-Fall zu behandeln. Es ist selten, dass Sie möchten, dass eine Null Sachen in der Produktion in die Luft jagt!
Richard Miskin
@Richard: wenn das nicht nullunerwartet ist, dann ist es ein echter Fehler, dann sollte alles zum Stillstand kommen.
Null in Datenbanken und in Programmcode wird in einem wichtigen Aspekt anders gehandhabt: In der Programmierung null == null(auch in PHP), aber in Datenbanken, null != nullweil es in Datenbanken einen unbekannten Wert darstellt und nicht "nichts". Zwei Unbekannte sind nicht unbedingt gleich, während zwei Nichts gleich sind.
Simon Forsberg
8

Das Hinzufügen von Nullprüfungen kann das Testen problematisch machen. Sehen Sie sich diesen tollen Vortrag an ...

Schauen Sie sich den Vortrag zu Google Tech Talks an: "Die Clean Code Talks - Suchen Sie nicht nach Dingen!" Er spricht ungefähr in Minute 24 darüber

http://www.youtube.com/watch?v=RlfLCWKxHJ0&list=PL693EFD059797C21E

Bei der paranoiden Programmierung werden überall Nullprüfungen hinzugefügt. Auf den ersten Blick scheint es eine gute Idee zu sein, aber aus der Sicht des Testens ist es schwierig, mit dem Null-Check-Typ-Test umzugehen.

Auch, wenn Sie eine Voraussetzung für die Existenz eines Objekts wie erstellen

class House(Door door){

    .. null check here and throw exception if Door is NULL
    this.door = door
}

Es verhindert, dass Sie House erstellen, da Sie eine Ausnahme auslösen. Angenommen, Ihre Testfälle erstellen Scheinobjekte zum Testen von etwas anderem als "Tür". Sie können dies nicht tun, da "Tür" erforderlich ist

Diejenigen, die unter der Hölle der Scheinschöpfung gelitten haben, sind sich dieser Art von Ärger wohl bewusst.

Zusammenfassend sollte Ihre Testsuite robust genug sein, um auf Türen, Häuser, Dächer oder was auch immer zu testen, ohne paranoid zu sein. Seroiusly, wie schwierig ist es, einen Null-Check-Test für bestimmte Objekte in Ihrem Test hinzuzufügen :)

Sie sollten immer Anwendungen bevorzugen, die funktionieren, weil Sie mehrere Tests haben, die BEWÄHREN, dass es funktioniert, und nicht HOPFEN, weil Sie überall eine ganze Reihe von vorbedingten Nullprüfungen haben

Constantin
quelle
4

tl; dr - Es ist GUT, nach unerwarteten nulls zu suchen, aber SCHLECHT, wenn eine Anwendung versucht, sie zu verbessern.

Einzelheiten

Offensichtlich gibt es Situationen, in denen nulles eine gültige Eingabe oder Ausgabe für eine Methode gibt, und andere, in denen dies nicht der Fall ist.

Regel 1:

Das Javadoc für eine Methode, die einen nullParameter zulässt oder einen nullWert zurückgibt , muss dies klar dokumentieren und erläutern, was dies nullbedeutet.

Regel 2:

Eine API-Methode sollte nur dann als akzeptierend oder zurückgebend angegeben werden, nullwenn ein guter Grund dafür vorliegt.

Angesichts einer klaren Spezifikation des "Vertrags" einer Methode gegenüber dem "Vertrag" nullist es ein Programmierfehler, ein zu übergeben oder zurückzugeben, nullwo Sie nicht sollten.

Regel 3:

Eine Anwendung sollte nicht versuchen, Programmierfehler zu beheben.

Wenn eine Methode eine erkennt , nulldie nicht da sein sollte, sollte es nicht versuchen , das Problem zu beheben , indem sie in etwas anderes verwandeln. Das verbirgt das Problem nur vor dem Programmierer. Stattdessen sollte NPE zugelassen und ein Fehler verursacht werden, damit der Programmierer die Ursache ermitteln und beheben kann. Hoffentlich wird der Fehler beim Testen bemerkt. Wenn nicht, sagt das etwas über Ihre Testmethode aus.

Regel 4:

Wenn möglich, schreiben Sie Ihren Code, um Programmierfehler frühzeitig zu erkennen.

Wenn Ihr Code Fehler enthält, die zu vielen NPEs führen, kann es am schwierigsten sein, herauszufinden, woher die nullWerte stammen. Eine Möglichkeit , die Diagnose zu erleichtern ist der Code zu schreiben , so dass die nullso schnell wie möglich erkannt werden. Oft können Sie dies in Verbindung mit anderen Überprüfungen tun. z.B

public setName(String name) {
    // This also detects `null` as an (intended) side-effect
    if (name.length() == 0) {
        throw new IllegalArgumentException("empty name");
    }
}

(Natürlich gibt es Fälle , in denen Regeln 3 und 4 sollte mit der Realität temperiert werden. Zum Beispiel (Regel 3), einige Arten von Anwendungen müssen nach dem Erkennen wahrscheinlich Programmierfehler weiterhin versuchen. Und (Regel 4) zu viel Kontrolle für schlechte Parameter haben kann eine Leistungsbeeinträchtigung.)

Stephen C
quelle
3

Ich würde empfehlen, die Methode defensiv zu machen. Zum Beispiel:

String go(String s){  
    return s.toString();  
}

Sollte mehr entlang den Linien dieses:

String go(String s){  
    if(s == null){  
       return "";  
    }     
    return s.toString();  
}

Ich weiß, dies absolut trivial ist, aber wenn der Aufrufer ein Objekt erwartet, dass sie ein Standard-Objekt geben, die nicht Ursache eine Null herumgereicht werden.

Woot4Moo
quelle
10
Dies hat eine unangenehme Nebenwirkung, dass der Anrufer (Programmierer, den Codes gegen diese API) zur Gewohnheit vorbei null als Parameter entwickeln könnte „default“ Verhalten zu bekommen.
Goran Jovic
2
Wenn dies Java ist, sollten Sie überhaupt nicht verwenden new String().
biziclop
1
@biziclop eigentlich ist es viel wichtiger als die meisten realisieren.
Woot4Moo
1
Meiner Meinung nach ist es sinnvoller, eine Behauptung zu platzieren und eine Ausnahme auszulösen, wenn das Argument null ist, da dies eindeutig der Fehler des Aufrufers ist. Nicht still „fix“ Anrufer Fehler; Stattdessen machen sie sich bewusst von ihnen.
Andres F.
1
@ Woot4Moo So schreiben Sie Ihren eigenen Code, der dann eine Ausnahme auslöst. Das Wichtigste ist, den Anrufer zu informieren, dass die übergebenen Argumente falsch sind, und ihn so bald wie möglich zu informieren. Schweigend Korrektur nulls ist die denkbar schlechteste Option, schlimmer als ein NPE zu werfen.
Andres F.
2

Die folgenden allgemeinen Regeln über null haben mir sehr geholfen, so weit:

  1. Wenn die Daten von außerhalb Ihrer Kontrolle stammen, suchen Sie systematisch nach Nullen und handeln Sie entsprechend. Dies bedeutet, dass entweder eine Ausnahme ausgelöst wird, die für die Funktion sinnvoll ist (aktiviert oder deaktiviert, stellen Sie nur sicher, dass der Name der Ausnahme genau angibt, was gerade passiert.). Aber VERLIEREN SIE NIEMALS einen Wert in Ihrem System, der möglicherweise Überraschungen birgt.

  2. Wenn Null im Bereich der für Ihr Datenmodell geeigneten Werte liegt, müssen Sie entsprechend vorgehen.

  3. Versuchen Sie bei der Rückgabe von Werten, möglichst keine Nullen zurückzugeben. Bevorzugen Sie immer leere Listen, leere Zeichenfolgen und Null-Objektmuster. Behalten Sie Nullen als zurückgegebene Werte bei, wenn dies die bestmögliche Darstellung von Daten für einen bestimmten Anwendungsfall ist.

  4. Das wohl wichtigste von allen ... Tests, Tests und nochmal testen. Wenn Sie Ihren Code testen, testen Sie ihn nicht als Kodierer. Testen Sie ihn als Domina eines NS-Psychopathen und versuchen Sie sich alle möglichen Methoden vorzustellen, um den Code zu quälen.

Dies ist paranoid, was Nullen betrifft, die häufig zu Fassaden und Proxies führen, die Systeme mit der Außenwelt verbinden, und streng kontrollierte Werte im Inneren mit reichlich Redundanz. Die Außenwelt bedeutet hier so ziemlich alles, was ich nicht selbst codiert habe. Es macht einen Kosten Laufzeit tragen aber bisher habe ich selten dies hatte zur Optimierung von „null sichere Abschnitte“ des Codes zu schaffen. Ich muss jedoch sagen, dass ich hauptsächlich Systeme für das Gesundheitswesen erstelle, und das letzte, was ich möchte, ist, dass das Schnittstellensubsystem, das Ihre Jodallergien auf den CT-Scanner überträgt, aufgrund eines unerwarteten Nullzeigers abstürzt, weil jemand anderes in einem anderen System nie realisiert hat, dass Namen dies könnten Enthält Apostrophe oder Zeichen wie 但 但 耒耨

sowieso .... meine 2 Cent

Newtopian
quelle
1

Ich würde vorschlagen, Option / Some / None-Muster aus funktionalen Sprachen zu verwenden. Ich bin kein Java-Spezialist, aber ich benutze intensiv meine eigene Implementierung dieses Musters in meinem C # -Projekt, und ich bin sicher, könnte es in Java-Welt überführt werden.

Die Idee hinter diesem Muster ist: Wenn es logisch ist, eine Situation zu haben, in der die Möglichkeit des Fehlens eines Werts besteht (zum Beispiel beim Abrufen von der Datenbank über die ID), geben Sie ein Objekt vom Typ Option [T] an, wobei T ein möglicher Wert ist . I Fall von absense eines Wertes Objekt der Klasse Keine [T] zurückgegeben, im Falle, wenn Existenz des Wertes - Ziel einiger [T] zurückgegeben, die den Wert enthält.

In diesem Fall Sie müssen die Möglichkeit Wert Abwesenheit handhaben , und wenn Sie Code - Review tun könnten Sie leicht placec unsachgemäßer Handhabung gefunden. Informationen zur Implementierung von C # finden Sie in meinem Bitbucket-Repository unter https://bitbucket.org/mikegirkin/optionsomenone

Wenn Sie einen Nullwert zurückgeben und dieser logisch dem Fehler entspricht (z. B. dass keine Datei vorhanden ist oder keine Verbindung hergestellt werden konnte), sollten Sie eine Ausnahme auslösen oder ein anderes Fehlerbehandlungsmuster verwenden. Die Idee dahinter, wieder, beenden Sie mit Lösung auf, wenn Sie müssen die Situation der Wert Abwesenheit handhaben , und einfach die Orte unsachgemäßer Handhabung im Code gefunden.

Hedin
quelle
0

Nun, wenn eines der möglichen Ergebnisse Ihrer Methode ein Nullwert ist, sollten Sie dies defensiv codieren, aber wenn eine Methode einen Nicht-Nullwert zurückgeben soll, aber nicht, würde ich das mit Sicherheit beheben.

Wie immer kommt es auf den Fall an, wie bei den meisten Dingen im Leben :)

jonezy
quelle
0

Die lang- Bibliotheken von Apache Commons bieten eine Möglichkeit, mit Nullwerten umzugehen

Mit der Methode defaultIfNull in der Klasse ObjectUtils können Sie einen Standardwert zurückgeben, wenn das übergebene Objekt null ist

Mahmoud Hossam
quelle
0
  1. Verwenden Sie den optionalen Typ nicht, es sei denn, er ist wirklich optional. Oft wird der Exit als Ausnahme besser behandelt, es sei denn, Sie haben wirklich null als Option erwartet und nicht, weil Sie regelmäßig fehlerhaften Code schreiben.

  2. Das Problem ist nicht null als eine Art es seinen Nutzen hat, wie Google-Artikel weist darauf hin. Das Problem ist, dass NULL-Werte überprüft werden sollen und behandelt, oft können sie anmutig verlassen werden.

  3. Es gibt eine Reihe von Nullfällen, die ungültige Bedingungen in Bereichen außerhalb des normalen Programmbetriebs darstellen (ungültige Benutzereingaben, Datenbankprobleme, Netzwerkfehler, fehlende Dateien, beschädigte Daten) wenn es nur ist, um es zu protokollieren.

  4. Für die Ausnahmebehandlung sind in der JVM andere Betriebsprioritäten und -berechtigungen zulässig als für einen Typ wie Optional, der für die Ausnahmebedingung sinnvoll ist, einschließlich der frühen Unterstützung für das priorisierte verzögerte Laden des Handlers in den Speicher, da dies nicht der Fall ist brauchen sie hängen die ganze Zeit herum.

  5. Sie müssen keine Null-Handler überall schreiben, nur dort, wo sie wahrscheinlich auftreten, wo Sie möglicherweise auf einen unzuverlässigen Datendienst zugreifen, und da die meisten dieser Handler in wenige allgemeine Muster unterteilt sind, die leicht abstrahiert werden können, brauchen Sie wirklich nur ein Handler Aufruf, außer in seltenen Fällen.

Ich schätze, meine Antwort wäre, es in eine überprüfte Ausnahme zu packen und damit umzugehen, oder den unzuverlässigen Code zu reparieren, wenn er in Ihren Zuständigkeitsbereich fällt.

J-Boss
quelle