it-swarm.dev

Sind Nullreferenzen wirklich eine schlechte Sache?

Ich habe gehört, dass die Aufnahme von Nullreferenzen in Programmiersprachen der "Milliarden-Dollar-Fehler" ist. Aber warum? Sicher, sie können NullReferenceExceptions verursachen, aber was nun? Jedes Element der Sprache kann bei unsachgemäßer Verwendung zu Fehlern führen.

Und was ist die Alternative? Ich nehme an, anstatt dies zu sagen:

Customer c = Customer.GetByLastName("Goodman"); // returns null if not found
if (c != null)
{
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

Man könnte das sagen:

if (Customer.ExistsWithLastName("Goodman"))
{
    Customer c = Customer.GetByLastName("Goodman") // throws error if not found
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!"); 
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

Aber wie ist das besser? In beiden Fällen erhalten Sie eine Ausnahme, wenn Sie vergessen, die Existenz des Kunden zu überprüfen.

Ich nehme an, dass eine CustomerNotFoundException etwas einfacher zu debuggen ist als eine NullReferenceException, da sie aussagekräftiger ist. Ist das alles?

165
Tim Goodman

null ist böse

Zu diesem Thema gibt es eine Präsentation zu InfoQ: Null Referenzen: Der Milliarden-Dollar-Fehler von Tony Hoare

Optionstyp

Die Alternative zur funktionalen Programmierung ist die Verwendung eines Optionstyp , der SOME value Oder NONE enthalten kann.

Ein guter Artikel Das "Options" -Muster, der den Optionstyp beschreibt und eine Implementierung für Java bereitstellt.

Ich habe auch einen Fehlerbericht für Java zu diesem Problem gefunden: Fügen Sie Java nette Optionstypen hinzu, um NullPointerExceptions zu verhindern . Das angeforderte Feature wurde in Java 8 eingeführt.

93
Jonas

Das Problem ist, dass Ihr objektorientierter Code im Grunde genommen eine Sammlung nicht explodierter Bomben ist, da theoretisch jedes Objekt eine Null sein und eine Ausnahme auslösen kann, wenn Sie versuchen, es zu verwenden.

Sie haben Recht, dass eine ordnungsgemäße Fehlerbehandlung funktional mit nullprüfenden if -Anweisungen identisch sein kann. Aber was passiert, wenn etwas, von dem Sie sich überzeugt haben, es nicht kann? möglicherweise eine Null sein ist in der Tat eine Null? Kerboom. Was auch immer als nächstes passiert, ich bin bereit zu wetten, dass 1) es nicht anmutig sein wird und 2) es Ihnen nicht gefällt.

Und entlassen Sie nicht den Wert "einfach zu debuggen". Reifer Produktionscode ist eine verrückte, weitläufige Kreatur; Alles, was Ihnen mehr Einblick in das gibt, was schief gelaufen ist und wo Sie stundenlanges Graben sparen können.

129
BlairHippo

Es gibt verschiedene Probleme bei der Verwendung von Nullreferenzen im Code.

Erstens wird es im Allgemeinen verwendet, um einen Sonderzustand anzuzeigen. Anstatt eine neue Klasse oder Konstante für jeden Zustand zu definieren, da normalerweise Spezialisierungen durchgeführt werden, wird bei Verwendung einer Nullreferenz ein verlustbehafteter, massiv verallgemeinerter Typ/Wert verwendet.

Zweitens wird das Debuggen von Code schwieriger, wenn eine Nullreferenz angezeigt wird und Sie versuchen zu bestimmen, was sie generiert hat, welcher Status wirksam ist und welche Ursache er hat, selbst wenn Sie den Upstream-Ausführungspfad verfolgen können.

Drittens führen Nullreferenzen zusätzliche zu testende Codepfade ein.

Viertens erfordert die defensive Programmierung (für vom Design verursachte Zustände), sobald Nullreferenzen als gültige Zustände für Parameter sowie Rückgabewerte verwendet werden, an verschiedenen Stellen muss mehr Nullreferenzprüfung durchgeführt werden… nur in Fall.

Fünftens führt die Laufzeit der Sprache bereits Typprüfungen durch, wenn sie eine Selektorsuche für die Methodentabelle eines Objekts durchführt. Sie duplizieren also den Aufwand, indem Sie prüfen, ob der Objekttyp gültig/ungültig ist, und dann zur Laufzeit den Typ des gültigen Objekts überprüfen lassen, um dessen Methode aufzurufen.

Warum nicht das NullObject-Muster to nutzen Sie die Laufzeitprüfung, um es aufzurufen NOP = Methoden, die für diesen Status spezifisch sind (entsprechend der Schnittstelle des regulären Status) und gleichzeitig die zusätzliche Überprüfung auf Nullreferenzen in Ihrer gesamten Codebasis eliminieren?

Es erfordert mehr Arbeit Durch Erstellen einer NullObject-Klasse für jede Schnittstelle, mit der Sie einen speziellen Status darstellen möchten. Aber zumindest die Spezialisierung ist isoliert für jeden speziellen Zustand und nicht für den Code, in dem der Zustand vorhanden sein könnte. IOW, die Anzahl der Tests wird reduziert, weil Sie weniger alternative Ausführungspfade in Ihren Methoden haben.

50
Huperniketes

Nullen sind nicht so schlecht, es sei denn, Sie erwarten sie nicht. Sie sollten müssen im Code explizit angeben, dass Sie null erwarten, was ein Problem beim Sprachdesign ist. Bedenken Sie:

Customer? c = Customer.GetByLastName("Goodman");
// note the question mark -- 'c' is either a Customer or null
if (c != null)
{
    // c is not null, therefore its type in this block is
    // now 'Customer' instead of 'Customer?'
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

Wenn Sie versuchen, Methoden für ein Customer? sollte ein Fehler bei der Kompilierung auftreten. Der Grund, warum mehr Sprachen dies nicht tun (IMO), ist, dass sie nicht vorsehen, dass sich der Typ einer Variablen je nach Umfang ändert. Wenn die Sprache damit umgehen kann, kann das Problem vollständig innerhalb der gelöst werden Typ System.

Es gibt auch die funktionale Möglichkeit, dieses Problem mit Optionstypen und Maybe zu lösen, aber ich bin damit nicht so vertraut. Ich bevorzuge diesen Weg, da für korrekten Code theoretisch nur ein Zeichen hinzugefügt werden muss, um korrekt zu kompilieren.

Es gibt viele ausgezeichnete Antworten, die die unglücklichen Symptome von null abdecken, daher möchte ich ein alternatives Argument präsentieren: Null ist ein Fehler im Typensystem.

Der Zweck eines Typsystems besteht darin, sicherzustellen, dass die verschiedenen Komponenten eines Programms richtig "zusammenpassen"; Ein gut typisiertes Programm kann nicht "off the Rails" in undefiniertes Verhalten übergehen.

Stellen Sie sich einen hypothetischen Java-Dialekt vor oder eine andere statisch typisierte Sprache, in der Sie die Zeichenfolge "Hello, world!" Jeder Variablen eines beliebigen Typs zuweisen können:

Foo foo1 = new Foo();  // Legal
Foo foo2 = "Hello, world!"; // Also legal
Foo foo3 = "Bonjour!"; // Not legal - only "Hello, world!" is allowed

Und Sie können Variablen wie folgt überprüfen:

if (foo1 != "Hello, world!") {
    bar(foo1);
} else {
    baz();
}

Daran ist nichts Unmögliches - jemand könnte eine solche Sprache entwerfen, wenn er möchte. Der spezielle Wert muss nicht "Hello, world!" Sein - es könnte die Nummer 42, das Tupel (1, 4, 9) Oder beispielsweise null gewesen sein. Aber warum würdest du das tun? Eine Variable vom Typ Foo sollte nur Foos enthalten - das ist der springende Punkt des Typsystems! null ist nicht mehr als "Hello, world!" ein Foo. Schlimmer noch, null ist kein Wert von einem Typ, und Sie können nichts damit anfangen!

Der Programmierer kann niemals sicher sein, dass eine Variable tatsächlich ein Foo enthält, und das Programm kann es auch nicht. Um undefiniertes Verhalten zu vermeiden, müssen Variablen auf "Hello, world!" überprüft werden, bevor sie als Foos verwendet werden. Beachten Sie, dass das Durchführen der Zeichenfolgenprüfung im vorherigen Snippet nicht die Tatsache verbreitet, dass foo1 wirklich ein Foo ist - bar wird wahrscheinlich auch eine eigene Prüfung haben, nur um sicher zu gehen.

Vergleichen Sie dies mit der Verwendung eines Maybe/Option -Typs mit Mustervergleich:

case maybeFoo of
 |  Just foo => bar(foo)
 |  Nothing => baz()

Innerhalb der Just foo - Klausel wissen sowohl Sie als auch das Programm mit Sicherheit, dass unsere Variable Maybe Foo Wirklich einen Foo -Wert enthält - diese Informationen werden über die Aufrufkette weitergegeben, und bar muss keine Überprüfungen durchführen. Da Maybe Foo Ein anderer Typ als Foo ist, müssen Sie mit der Möglichkeit umgehen, dass es Nothing enthalten könnte, sodass Sie niemals blind durch ein NullPointerException. Sie können viel einfacher über Ihr Programm nachdenken und der Compiler kann Nullprüfungen weglassen, da er weiß, dass alle Variablen vom Typ Foo wirklich Foos enthalten. Jeder gewinnt.

20
Doval

Ein null Zeiger ist ein Werkzeug

kein Feind. Du solltest sie nur richtig benutzen.

Denken Sie nur eine Minute darüber nach, wie lange es dauert, einen typischen ngültigen Zeiger Fehler zu finden und zu beheben, verglichen mit dem Auffinden und Beheben eines Nullzeigerfehlers. Es ist einfach, einen Zeiger mit null zu vergleichen. Es ist eine PITA, um zu überprüfen, ob ein Nicht-Null-Zeiger auf gültige Daten zeigt oder nicht.

Wenn Sie immer noch nicht überzeugt sind, fügen Sie Ihrem Szenario eine gute Dosis Multithreading hinzu, und überlegen Sie es sich dann noch einmal.

Moral der Geschichte: Werfen Sie die Kinder nicht mit dem Wasser. Und beschuldigen Sie nicht die Werkzeuge für den Unfall. Der Mann, der vor langer Zeit die Zahl Null erfunden hat, war ziemlich schlau, da man an diesem Tag das "Nichts" nennen konnte. Der Zeiger null ist nicht so weit davon entfernt.


EDIT : Obwohl das Muster NullObject eine bessere Lösung zu sein scheint als Referenzen, die möglicherweise null sind, führt es selbst zu Problemen:

  • Eine Referenz, die ein NullObject enthält, sollte (gemäß der Theorie) nichts tun, wenn eine Methode aufgerufen wird. Auf diese Weise können subtile Fehler aufgrund fehlerhaft nicht zugewiesener Referenzen eingeführt werden, die jetzt garantiert nicht null sind (yehaa!), Aber eine unerwünschte Aktion ausführen: nichts. Bei einer NPE ist es nahezu offensichtlich, wo das Problem liegt. Mit einem NullObject, das sich irgendwie (aber falsch) verhält, besteht das Risiko, dass Fehler (zu) spät erkannt werden. Dies ähnelt nicht zufällig einem ungültigen Zeigerproblem und hat ähnliche Konsequenzen: Die Referenz verweist auf etwas, das wie gültige Daten aussieht, dies aber nicht ist, führt zu Datenfehlern und kann schwer zu finden sein. Um ehrlich zu sein, würde ich in diesen Fällen ohne einen Augenblick eine NPE bevorzugen, die sofort ausfällt (== = ==), jetzt gegenüber einem logischen/Datenfehler, der mich später plötzlich trifft die Straße runter. Erinnern Sie sich an die IBM-Studie über die Kosten von Fehlern, die davon abhängen, in welchem ​​Stadium sie erkannt werden?

  • Der Gedanke, nichts zu tun, wenn eine Methode für ein NullObject aufgerufen wird, gilt nicht, wenn ein Eigenschafts-Getter oder eine Funktion aufgerufen wird oder wenn die Methode Werte über out zurückgibt. Angenommen, der Rückgabewert ist ein int und wir entscheiden, dass "nichts tun" "0 zurückgeben" bedeutet. Aber wie können wir sicher sein, dass 0 das richtige "Nichts" ist, um (nicht) zurückgegeben zu werden? Immerhin sollte das NullObject nichts tun, aber wenn es nach einem Wert gefragt wird, muss es irgendwie reagieren. Fehlschlagen ist keine Option: Wir verwenden NullObject, um die NPE zu verhindern, und wir werden sie sicherlich nicht gegen eine andere Ausnahme tauschen (nicht implementiert, durch Null dividiert, ...), oder? Wie können Sie also richtig nichts zurückgeben, wenn Sie etwas zurückgeben müssen?

Ich kann nicht anders, aber wenn jemand versucht, das Muster NullObject auf jedes einzelne Problem anzuwenden, sieht es eher so aus, als würde er versuchen, einen Fehler durch einen anderen zu beheben. Es ist ohne Zweifel eine gute und nützliche Lösung in einigen Fällen, aber es ist sicherlich nicht das Wundermittel für alle Fälle.

15
JensG

(Werfen Sie meinen Hut für eine alte Frage in den Ring;))

Das spezifische Problem mit null besteht darin, dass die statische Typisierung unterbrochen wird.

Wenn ich einen Thing t Habe, kann der Compiler garantieren, dass ich t.doSomething() aufrufen kann. Nun, UNLESS t ist zur Laufzeit null. Jetzt sind alle Wetten geschlossen. Der Compiler sagte, es sei in Ordnung, aber ich finde viel später heraus, dass t tatsächlich NICHT doSomething() ist. Anstatt dem Compiler vertrauen zu können, dass er Typfehler abfängt, muss ich bis zur Laufzeit warten, um sie abzufangen. Ich könnte genauso gut Python verwenden!

In gewissem Sinne führt null die dynamische Typisierung in ein statisch typisiertes System mit erwarteten Ergebnissen ein.

Der Unterschied zwischen einer Division durch Null oder einem logarithmischen Wert von negativ usw. besteht darin, dass wenn i = 0 ist, es immer noch ein int ist. Der Compiler kann seinen Typ weiterhin garantieren. Das Problem ist, dass die Logik diesen Wert auf eine Weise falsch anwendet, die von der Logik nicht zugelassen wird ... aber wenn die Logik dies tut, ist das so ziemlich die Definition eines Fehlers.

(Der Compiler kann einige dieser Probleme abfangen, übrigens. Wie das Markieren von Ausdrücken wie i = 1 / 0 Zur Kompilierungszeit. Aber Sie können nicht wirklich Erwarten Sie, dass der Compiler einer Funktion folgt, und stellen Sie sicher, dass alle Parameter mit der Logik der Funktion übereinstimmen.

Das praktische Problem ist, dass Sie viel zusätzliche Arbeit leisten und Nullprüfungen hinzufügen, um sich zur Laufzeit zu schützen. Aber was ist, wenn Sie eine vergessen? Der Compiler hindert Sie daran, Folgendes zuzuweisen:

String s = neue Ganzzahl (1234);

Warum sollte es also die Zuweisung zu einem Wert (null) zulassen, der die Verweise auf s aufhebt?

Indem Sie "kein Wert" mit "getippten" Referenzen in Ihrem Code mischen, belasten Sie die Programmierer zusätzlich. Und wenn NullPointerExceptions auftreten, kann das Aufspüren noch zeitaufwändiger sein. Anstatt sich auf statische Typisierung zu verlassen, um "dies ist ein Verweis auf etwas Erwartetes" zu sagen, lassen Sie die Sprache sagen "dies kann durchaus sein ein Verweis auf etwas Erwartetes. ""

14
Rob

Und was ist die Alternative?

Optionale Typen und Mustervergleich. Da ich C # nicht kenne, ist hier ein Stück Code in einer fiktiven Sprache namens Scala # :-)

Customer.GetByLastName("Goodman")    // returns Option[Customer]
match
{
    case Some(customer) =>
    Console.WriteLine(customer.FirstName + " " + customer.LastName + " is awesome!");

    case None =>
    Console.WriteLine("There was no customer named Goodman.  How lame!");
}
8
fredoverflow

Nullreferenzen sind ein Fehler, weil sie unsinnigen Code zulassen:

foo = null
foo.bar()

Es gibt Alternativen, wenn Sie das Typsystem nutzen:

Maybe<Foo> foo = null
foo.bar() // error{Maybe<Foo> does not have any bar method}

Die allgemeine Idee besteht darin, die Variable in eine Box zu packen. Sie können sie nur entpacken und vorzugsweise die für Eiffel vorgeschlagene Compiler-Hilfe in Anspruch nehmen.

Haskell hat es von Grund auf neu (Maybe), in C++ können Sie boost::optional<T> aber du kannst immer noch undefiniertes Verhalten bekommen ...

8
Matthieu M.

Das Problem mit Nullen ist, dass Sprachen, die es ihnen erlauben, Sie dazu zwingen, defensiv dagegen zu programmieren. Es erfordert viel Mühe (weit mehr als den Versuch, defensive If-Blöcke zu verwenden), um dies sicherzustellen

  1. die Objekte, von denen Sie erwarten, dass sie nicht null sind, sind in der Tat niemals null, und
  2. dass Ihre Abwehrmechanismen tatsächlich effektiv mit allen potenziellen NPEs umgehen.

Nullen sind also in der Tat eine kostspielige Sache.

6
luis.espinal

Die Frage, inwieweit Ihre Programmiersprache versucht, die Richtigkeit Ihres Programms zu beweisen, bevor es ausgeführt wird. In einer statisch typisierten Sprache beweisen Sie, dass Sie die richtigen Typen haben. Durch Verschieben der Standardeinstellung für nicht nullfähige Referenzen (mit optionalen nullbaren Referenzen) können Sie viele Fälle beseitigen, in denen null übergeben wird und dies nicht sein sollte. Die Frage ist, ob der zusätzliche Aufwand beim Umgang mit nicht nullbaren Referenzen den Vorteil in Bezug auf die Programmkorrektheit wert ist.

4
Winston Ewert

Das Problem ist nicht so sehr null, sondern dass Sie in vielen modernen Sprachen keinen Referenztyp ungleich Null angeben können.

Zum Beispiel könnte Ihr Code so aussehen

public void MakeCake(Egg egg, Flour flour, Milk milk)
{
    if (Egg == null) { throw ... }
    if (flour == null) { throw ... }
    if (milk == null) { throw ... }

    Egg.Crack();
    MixingBowl.Mix(Egg, flour, milk);
    // etc
}

// inside Mixing bowl class
public void Mix(Egg egg, Flour flour, Milk milk)
{
    if (Egg == null) { throw ... }
    if (flour == null) { throw ... }
    if (milk == null) { throw ... }

    //... etc
}

Wenn Klassenreferenzen weitergegeben werden, empfiehlt Ihnen die defensive Programmierung, alle Parameter erneut auf Null zu überprüfen, auch wenn Sie sie zuvor direkt auf Null überprüft haben, insbesondere beim Erstellen wiederverwendbarer Einheiten, die getestet werden. Dieselbe Referenz kann in der gesamten Codebasis leicht zehnmal auf Null überprüft werden!

Wäre es nicht besser, wenn Sie einen normalen nullbaren Typ haben könnten, wenn Sie das Ding bekommen, dann und dort mit null umgehen und ihn dann als nicht nullbaren Typ an alle Ihre kleinen Hilfsfunktionen und Klassen übergeben könnten, ohne alle auf null zu prüfen Zeit?

Das ist der Punkt der Optionslösung - nicht um das Einschalten von Fehlerzuständen zu ermöglichen, sondern um das Entwerfen von Funktionen zu ermöglichen, die implizit keine Nullargumente akzeptieren können. Ob die "Standard" -Implementierung von Typen nullbar oder nicht nullbar ist, ist weniger wichtig als die Flexibilität, beide Tools verfügbar zu haben.

http://twistedoakstudios.com/blog/Post330_non-nullable-types-vs-c-fixing-the-billion-dollar-mistake

Dies ist auch die am zweithäufigsten gewählte Funktion für C # -> https://visualstudio.uservoice.com/forums/121579-visual-studio/category/30931-languages-c

4
Michael Parker

Null ist böse. Das Fehlen einer Null kann jedoch ein größeres Übel sein.

Das Problem ist, dass Sie in der realen Welt häufig eine Situation haben, in der Sie keine Daten haben. Ihr Beispiel für die Version ohne Nullen kann immer noch in die Luft jagen - entweder haben Sie einen logischen Fehler gemacht und vergessen, nach Goodman zu suchen, oder Goodman hat zwischen der Überprüfung und der Suche nach ihr geheiratet. (Es hilft bei der Bewertung der Logik, wenn Sie glauben, dass Moriarty jeden Teil Ihres Codes beobachtet und versucht, Sie zu stolpern.)

Was macht die Suche nach Goodman, wenn sie nicht da ist? Ohne eine Null müssen Sie eine Art Standardkunde zurückgeben - und jetzt verkaufen Sie Sachen an diese Standardeinstellung.

Grundsätzlich kommt es darauf an, ob es wichtiger ist, dass der Code funktioniert, egal was oder ob er richtig funktioniert. Wenn unangemessenes Verhalten keinem Verhalten vorzuziehen ist, möchten Sie keine Nullen. (Ein Beispiel dafür, wie dies die richtige Wahl sein könnte, ist der erste Start der Ariane V. Ein nicht gefangener/0-Fehler führte dazu, dass die Rakete eine harte Wendung machte und die Selbstzerstörung feuerte, als sie aufgrund dessen auseinander fiel. Der Wert es wurde versucht zu berechnen, dass in dem Moment, in dem der Booster angezündet wurde, tatsächlich kein Zweck mehr erfüllt wurde - es hätte trotz der routinemäßigen Rückgabe von Müll eine Umlaufbahn erreicht.)

Mindestens 99 von 100 Mal würde ich Nullen verwenden. Ich würde jedoch vor Freude über die Selbstversion von ihnen springen.

3
Loren Pechtel

Optimieren Sie für den häufigsten Fall.

Es ist mühsam, ständig nach Null suchen zu müssen - Sie möchten in der Lage sein, einfach das Kundenobjekt zu ergreifen und damit zu arbeiten.

Im Normalfall sollte dies einwandfrei funktionieren - Sie suchen nach, holen das Objekt und verwenden es.

Im Ausnahmefall, in dem Sie (zufällig) einen Kunden nach Namen suchen und nicht wissen, ob dieser Datensatz/dieses Objekt vorhanden ist, benötigen Sie einen Hinweis darauf, dass dies fehlgeschlagen ist. In dieser Situation besteht die Antwort darin, eine RecordNotFound-Ausnahme auszulösen (oder den darunter liegenden SQL-Anbieter dies für Sie tun zu lassen).

Wenn Sie sich in einer Situation befinden, in der Sie nicht wissen, ob Sie den eingehenden Daten (dem Parameter) vertrauen können, möglicherweise weil sie von einem Benutzer eingegeben wurden, können Sie auch den 'TryGetCustomer (Name, Out-Kunde)' angeben. Muster. Querverweis mit int.Parse und int.TryParse.

1
JBRWilkinson

Ausnahmen sind nicht eval!

Sie sind aus einem bestimmten Grund da. Wenn Ihr Code schlecht läuft, gibt es ein geniales Muster namens Ausnahme, das Ihnen sagt, dass etwas nicht stimmt.

Indem Sie die Verwendung von Nullobjekten vermeiden, verbergen Sie einen Teil dieser Ausnahmen. Ich mag kein OP-Beispiel, bei dem er eine Nullzeiger-Ausnahme in eine gut typisierte Ausnahme konvertiert. Dies könnte tatsächlich eine gute Sache sein, da es die Lesbarkeit erhöht. Wenn Sie jedoch die Lösung Optionstyp verwenden, wie @Jonas hervorhob, verbergen Sie Ausnahmen.

Als in Ihrer Produktionsanwendung passiert nichts, anstatt eine Ausnahme auszulösen, wenn Sie auf die Schaltfläche klicken, um eine leere Option auszuwählen. Anstelle eines Nullzeigers würde eine Ausnahme ausgelöst und wir würden wahrscheinlich einen Bericht über diese Ausnahme erhalten (wie in vielen Produktionsanwendungen haben wir einen solchen Mechanismus), und dann könnten wir ihn tatsächlich beheben.

Es ist eine schlechte Idee, Ihren Code durch Vermeidung von Ausnahmen kugelsicher zu machen. Sie können Ihren Code durch Beheben von Ausnahmen kugelsicher machen. Dies ist der Pfad, den ich wählen würde.

0
Ilya Gazman

Nein.

Das Problem einer Sprache ist, dass eine Sprache einen bestimmten Wert wie null nicht berücksichtigt. Wenn Sie sich Sprachen wie Kotlin oder Swift ansehen, die den Autor dazu zwingen, sich bei jeder Begegnung explizit mit der Möglichkeit eines Nullwerts zu befassen, dann Es besteht keine Gefahr.

In Sprachen wie der fraglichen erlaubt die Sprache, dass null ein Wert von any ist-ish Geben Sie ein, stellen Sie jedoch keine Konstrukte bereit, um dies später zu bestätigen. Dies führt dazu, dass die Dinge unerwartet null sind (insbesondere wenn sie von einem anderen Ort an Sie übergeben werden), und Sie erhalten Abstürze. Das ist das Böse: Sie können null verwenden, ohne sich damit befassen zu müssen.

0
Ben Leggiero

Nulls sind problematisch, da sie explizit überprüft werden müssen, der Compiler Sie jedoch nicht warnen kann, dass Sie vergessen haben, nach ihnen zu suchen. Nur eine zeitaufwändige statische Analyse kann Ihnen dies sagen. Glücklicherweise gibt es mehrere gute Alternativen.

Nehmen Sie die Variable aus dem Gültigkeitsbereich. Viel zu oft wird null als Platzhalter verwendet, wenn ein Programmierer eine Variable zu früh deklariert oder zu lange herumhält. Der beste Ansatz ist einfach, die Variable loszuwerden. Deklarieren Sie es erst, wenn Sie einen gültigen Wert haben, den Sie dort eingeben können. Dies ist keine so schwierige Einschränkung, wie Sie vielleicht denken, und es macht Ihren Code viel sauberer.

Verwenden Sie das Nullobjektmuster. Manchmal ist ein fehlender Wert ein gültiger Status für Ihr System. Das Nullobjektmuster ist hier eine gute Lösung. Es vermeidet die Notwendigkeit ständiger expliziter Überprüfungen, ermöglicht Ihnen jedoch die Darstellung eines Nullstatus. Es ist kein "Überarbeiten einer Fehlerbedingung", wie einige Leute behaupten, weil in diesem Fall ein Nullzustand ein gültiger semantischer Zustand ist. Davon abgesehen sollten Sie dieses Muster nicht verwenden, wenn ein Nullzustand kein gültiger semantischer Zustand ist . Sie sollten Ihre Variable einfach aus dem Gültigkeitsbereich entfernen.

Verwenden Sie ein Vielleicht/eine Option. Zunächst kann der Compiler Sie warnen, dass Sie nach einem fehlenden Wert suchen müssen, aber er ersetzt nicht nur eine Art der expliziten Prüfung durch eine andere. Mit Options können Sie Ihren Code verketten und so weitermachen, als ob Ihr Wert vorhanden wäre, ohne ihn bis zur absoluten letzten Minute überprüfen zu müssen. In Scala würde Ihr Beispielcode ungefähr so ​​aussehen:

val customer = customerDb getByLastName "Goodman"
val awesomeMessage =
  customer map (c => s"${c.firstName} ${c.lastName} is awesome!")
val notFoundMessage = "There was no customer named Goodman.  How lame!"
println(awesomeMessage getOrElse notFoundMessage)

In der zweiten Zeile, in der wir die großartige Nachricht erstellen, überprüfen wir nicht explizit, ob der Kunde gefunden wurde, und die Schönheit ist , die wir nicht benötigen . Erst in der allerletzten Zeile, die viele Zeilen später sein kann, oder sogar in einem anderen Modul, beschäftigen wir uns explizit mit dem, was passiert, wenn OptionNone ist. Nicht nur das, wenn wir es vergessen hätten, hätte es den Typ nicht überprüft. Selbst dann geschieht dies auf sehr natürliche Weise ohne explizite if Anweisung.

Vergleichen Sie dies mit einem null, bei dem die Typprüfungen einwandfrei durchgeführt werden, bei dem Sie jedoch bei jedem Schritt des Weges eine explizite Überprüfung durchführen müssen oder Ihre gesamte Anwendung zur Laufzeit explodiert, wenn Sie während einer Verwendung Glück haben Fall Ihre Einheit testet Übung. Es ist einfach nicht die Mühe wert.

0
Karl Bielefeldt

Das zentrale Problem von NULL ist, dass es das System unzuverlässig macht. 1980 schrieb Tony Hoare in der seinem Turing Award gewidmeten Zeitung:

Daher wurde der beste Rat an die Urheber und Designer von ADA ignoriert. …. Lassen Sie nicht zu, dass diese Sprache in ihrem gegenwärtigen Zustand in Anwendungen verwendet wird, in denen Zuverlässigkeit von entscheidender Bedeutung ist, d. H. Kernkraftwerke, Marschflugkörper, Frühwarnsysteme, antiballistische Raketenabwehrsysteme. Die nächste Rakete, die aufgrund eines Programmiersprachenfehlers in die Irre geht, ist möglicherweise keine explorative Weltraumrakete auf einer harmlosen Reise zur Venus: Es kann sich um einen Atomsprengkopf handeln, der über einer unserer eigenen Städte explodiert. Eine unzuverlässige Programmiersprache, die unzuverlässige Programme erzeugt, stellt ein weitaus größeres Risiko für unsere Umwelt und unsere Gesellschaft dar als unsichere Autos, giftige Pestizide oder Unfälle in Kernkraftwerken. Seien Sie wachsam, um das Risiko zu verringern, nicht um es zu erhöhen.

Die ADA-Sprache hat sich seitdem stark verändert, es gibt jedoch immer noch solche Probleme in Java, C # und vielen anderen populären Sprachen.

Es ist die Pflicht des Entwicklers, Verträge zwischen einem Kunden und einem Lieferanten zu schließen. In C # können Sie beispielsweise wie in Java Generics verwenden, um die Auswirkungen der Referenz Null zu minimieren, indem Sie schreibgeschützt NullableClass<T> (Zwei Optionen) erstellen:

class NullableClass<T>
{
     public HasValue {get;}
     public T Value {get;}
}

und dann verwenden als

NullableClass<Customer> customer = dbRepository.GetCustomer('Mr. Smith');
if(customer.HasValue){
  // one logic with customer.Value
}else{
  // another logic
} 

oder verwenden Sie zwei Optionen mit C # -Erweiterungsmethoden:

customer.Do(
      // code with normal behaviour
      ,
      // what to do in case of null
) 

Der Unterschied ist signifikant. Als Kunde einer Methode wissen Sie, was Sie erwartet. Ein Team kann die Regel haben:

Wenn eine Klasse nicht vom Typ NullableClass ist, darf ihre Instanz nicht null sein .

Das Team kann diese Idee stärken, indem es Design by Contract und statische Überprüfung zur Kompilierungszeit verwendet, z. B. unter der Voraussetzung:

function SaveCustomer([NotNullAttribute]Customer customer){
     // there is no need to check whether customer is null 
     // it is a client problem, not this supplier
}

oder für eine Zeichenfolge

function GetCustomer([NotNullAndNotEmptyAttribute]String customerName){
     // there is no need to check whether customerName is null or empty 
     // it is a client problem, not this supplier
}

Dieser Ansatz kann die Zuverlässigkeit der Anwendung und die Softwarequalität drastisch erhöhen . Design by Contract ist ein Fall von Hoare-Logik , der 1988 von Bertrand Meyer in seinem berühmten Buch über objektorientierte Softwarekonstruktion und in der Eiffel-Sprache aufgefüllt wurde, aber in der modernen Softwareherstellung nicht ungültig verwendet wird.

0
Artru