Sind null Referenzen wirklich eine schlechte Sache?

161

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

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!"); }

Das könnte man so 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 ein bisschen einfacher zu debuggen ist als eine NullReferenceException, da sie aussagekräftiger ist. Ist das alles, was es zu tun gibt?

Tim Goodman
quelle
54
Ich wäre vorsichtig mit dem Ansatz "Wenn der Kunde existiert / dann wird er Kunde", sobald Sie zwei Zeilen Code wie diesen schreiben, öffnen Sie die Tür für die Rennbedingungen. Holen Sie sich, und überprüfen Sie, ob Nullen / Fangausnahmen sicherer sind.
Carson63000
26
Wenn wir nur die Ursache aller Laufzeitfehler beseitigen könnten, wäre unser Code garantiert perfekt! Wenn NULL-Referenzen in C # Ihr Gehirn
zerstören
Es gibt jedoch die Null-Koaleszenz- und sicheren Navigationsoperatoren in einigen Sprachen
jk.
35
null per se ist nicht schlecht. Ein Typensystem, das keinen Unterschied zwischen Typ T und Typ T + null macht, ist schlecht.
Ingo
27
Ich komme ein paar Jahre später auf meine eigene Frage zurück und bin jetzt ein völliger Konvertit des Option / Vielleicht-Ansatzes.
Tim Goodman

Antworten:

91

null ist böse

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

Optionstyp

Die Alternative zur funktionalen Programmierung ist die Verwendung eines Optionstyps , der SOME valueoder enthalten kann NONE.

Ein guter Artikel Das Muster "Option" , in dem der Optionstyp erläutert und eine Implementierung für Java bereitgestellt wird.

Ich habe auch einen Fehlerbericht für Java zu diesem Problem gefunden: Fügen Sie Nice Option-Typen zu Java hinzu, um NullPointerExceptions zu verhindern . Die angeforderte Funktion wurde in Java 8 eingeführt.

Jonas
quelle
5
Nullable <x> in C # ist ein ähnliches Konzept, entspricht jedoch keineswegs einer Implementierung des Optionsmusters. Der Hauptgrund ist, dass in .NET System.Nullable auf Werttypen beschränkt ist.
MattDavey
27
+1 Für den Optionstyp. Nachdem sie sich mit Haskells Vielleicht vertraut gemacht haben , sehen Nullen merkwürdig aus ...
Andres F.
5
Entwickler können überall Fehler machen, daher erhalten Sie anstelle der Nullzeigerausnahme eine leere Optionsausnahme. Wie ist das letztere besser als das erstere?
Greenoldman
7
@greenoldman Es gibt keine "leere Option Ausnahme". Versuchen Sie, NULL-Werte in Datenbankspalten einzufügen, die als NOT NULL definiert sind.
Jonas
36
@greenoldman Das Problem mit Nullen ist, dass alles Null sein kann und man als Entwickler besonders vorsichtig sein muss. Ein StringTyp ist eigentlich keine Zeichenfolge. Es ist ein String or NullTyp. Sprachen, die sich vollständig an die Idee von Optionstypen halten, lassen keine Nullwerte in ihrem Typsystem zu. Dies gibt die Garantie, dass eine Typensignatur, für die ein String(oder etwas anderes) erforderlich ist , einen Wert haben muss , wenn Sie eine Typensignatur definieren . Der OptionTyp gibt eine Darstellung auf Typebene von Dingen, die möglicherweise einen Wert haben oder nicht , sodass Sie wissen, wo Sie diese Situationen explizit behandeln müssen.
KChaloux
127

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

Sie haben Recht, dass die ordnungsgemäße Fehlerbehandlung funktional mit der Überprüfung auf Null identisch sein kann if. Aber was passiert, wenn etwas, von dem Sie sich überzeugt haben, dass es unmöglich ist , eine Null zu sein, tatsächlich eine Null? Kerboom. Was auch immer als nächstes passiert, ich bin bereit zu wetten, dass 1) es nicht anmutig sein wird und 2) Sie es nicht mögen werden.

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

BlairHippo
quelle
27
+1 Normalerweise MÜSSEN Fehler im Produktionscode so schnell wie möglich identifiziert werden, damit sie behoben werden können (normalerweise Datenfehler). Je einfacher das Debuggen ist, desto besser.
4
Diese Antwort ist dieselbe, wenn sie auf eines der Beispiele des OP angewendet wird. Entweder Sie testen auf Fehlerzustände oder Sie tun es nicht, und Sie behandeln sie entweder ordnungsgemäß oder Sie tun es nicht. Mit anderen Worten, das Entfernen von Nullreferenzen aus der Sprache ändert nicht die Fehlerklassen, die auftreten, sondern nur die Manifestation dieser Fehler.
Dash-Tom-Bang
19
+1 für nicht explodierte Bomben. Bei Null-Referenzen bedeutet das Sprechen public Foo doStuff()nicht "Sachen machen und das Foo-Ergebnis zurückgeben", sondern "Sachen machen und das Foo-Ergebnis zurückgeben, ODER Null zurückgeben, aber Sie haben keine Ahnung, ob dies passieren wird oder nicht." Das Ergebnis ist, dass Sie ALLES auf Null setzen müssen, um sicherzugehen. Sie können auch Nullen entfernen und einen bestimmten Typ oder ein bestimmtes Muster (z. B. einen Werttyp) verwenden, um einen bedeutungslosen Wert anzugeben.
Matt Olenik
12
@greenoldman, Ihr Beispiel ist doppelt effektiv, um unseren Standpunkt zu verdeutlichen, dass die Verwendung von primitiven Typen (egal ob Null oder Ganzzahl) als semantische Modelle eine schlechte Praxis ist. Wenn Sie einen Typ haben, für den negative Zahlen keine gültige Antwort sind, sollten Sie eine neue Klasse von Typen erstellen, um das semantische Modell mit dieser Bedeutung zu implementieren. Der gesamte Code für die Modellierung dieses nicht negativen Typs ist jetzt in seiner Klasse lokalisiert und vom Rest des Systems isoliert. Jeder Versuch, einen negativen Wert für ihn zu erstellen, kann sofort abgefangen werden.
Huperniketes
9
@greenoldman, Sie beweisen weiterhin, dass primitive Typen für die Modellierung nicht geeignet sind. Zuerst war es über negative Zahlen. Jetzt ist der Divisionsoperator überschritten, wenn der Divisor Null ist. Wenn Sie wissen, dass Sie nicht durch Null dividieren können , können Sie vorhersagen, dass das Ergebnis nicht schön sein wird, und Sie sind dafür verantwortlich, sicherzustellen, dass Sie auf den entsprechenden "gültigen" Wert für diesen Fall testen. Softwareentwickler sind dafür verantwortlich, die Parameter zu kennen, mit denen ihr Code arbeitet, und entsprechend zu codieren. Das ist es, was Technik von Basteln unterscheidet.
Huperniketes
50

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

Erstens wird es im Allgemeinen verwendet, um einen speziellen Status anzugeben . Anstatt für jeden Zustand eine neue Klasse oder Konstante zu definieren, wie dies normalerweise bei Spezialisierungen der Fall ist, wird bei Verwendung einer Nullreferenz ein verlustbehafteter, stark verallgemeinerter Typ / Wert verwendet .

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

Drittens werden durch Nullreferenzen zusätzliche zu testende Codepfade eingeführt .

Viertens, sobald Nullreferenzen als gültige Zustände für Parameter sowie als Rückgabewerte verwendet werden, erfordert die defensive Programmierung (für durch das Design verursachte Zustände) mehr Nullreferenzprüfungen an verschiedenen Stellen … nur für den Fall.

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

Verwenden Sie das NullObject-Muster , um die Laufzeitüberprüfung zu nutzen und NOP- Methoden aufzurufen , die für diesen Status spezifisch sind ( gemäß der Schnittstelle des regulären Status), und um die zusätzliche Überprüfung auf Nullreferenzen in Ihrer gesamten Codebasis zu vermeiden.

Es ist mehr Arbeit durch eine NullObject Klasse für jede Schnittstelle zu schaffen , mit dem Sie einen speziellen Zustand darstellen wollen. Aber zumindest ist die Spezialisierung auf jeden speziellen Zustand beschränkt und nicht auf den Code, in dem der Zustand vorhanden sein könnte. IOW wird die Anzahl der Tests reduziert, da Ihre Methoden weniger alternative Ausführungspfade enthalten .

Huperniketes
quelle
7
... weil es so viel einfacher ist, herauszufinden, wer die Abfalldaten, die Ihr vermeintlich nützliches Objekt füllen, generiert (oder besser gesagt, nicht geändert oder initialisiert hat), als eine NULL-Referenz zu verfolgen? Ich kaufe es nicht, obwohl ich größtenteils in statisch typisiertem Land stecke.
Dash-Tom-Bang
Garbage-Daten sind jedoch so viel seltener als Null-Referenzen. Besonders in verwalteten Sprachen.
Dean Harding
5
-1 für Null Objekt.
DeadMG
4
Die Punkte (4) und (5) sind solide, aber das NullObject ist kein Mittel. Sie werden in noch schlimmere Fehler der Trap-Logik (Workflow) geraten. Wenn Sie die aktuelle Situation GENAU kennen, kann dies sicher helfen, aber eine blinde Verwendung (anstelle von null) kostet Sie mehr.
Greenoldman
1
@ gnasher729 Während die Laufzeit von Objective-C keine Nullzeigerausnahme auslöst, wie dies bei Java und anderen Systemen der Fall ist, verbessert sich die Verwendung von Nullreferenzen aus den in meiner Antwort genannten Gründen nicht. Wenn es beispielsweise keinen navigationController in [self.navigationController.presentingViewController dismissViewControllerAnimated: YES completion: nil];der Laufzeit gibt , wird die Ansicht nicht aus der Anzeige entfernt, und Sie können stundenlang vor Xcode sitzen und sich den Kopf kratzen, um herauszufinden, warum.
Huperniketes
48

Nullen sind nicht so schlimm, es sei denn, Sie erwarten sie nicht. Sie sollten explizit in Code angeben müssen, 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 a aufzurufen Customer?, sollte ein Fehler beim Kompilieren auftreten. Der Grund, warum mehr Sprachen dies nicht tun (IMO), ist, dass sie nicht dafür sorgen, dass sich der Typ einer Variablen in Abhängigkeit von ihrem Gültigkeitsbereich ändert Typ System.

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

Hinweis zur Selbsteinschätzung eines Namens
quelle
11
Wow, das ist ziemlich cool. Zusätzlich dazu, dass ich beim Kompilieren viel mehr Fehler feststellen muss, erspare ich mir, die Dokumentation zu überprüfen, um herauszufinden, ob GetByLastNamenull zurückgegeben wird, wenn sie nicht gefunden wird (im Gegensatz zum Auslösen einer Ausnahme). Ich kann nur sehen, ob Customer?oder Customer.
Tim Goodman
14
@JBRWilkinson: Dies ist nur ein ausgeklügeltes Beispiel, um die Idee zu zeigen, nicht ein Beispiel für eine tatsächliche Implementierung. Eigentlich finde ich das eine ziemlich nette Idee.
Dean Harding
1
Sie haben Recht, dass dies jedoch nicht das Null-Objektmuster ist.
Dean Harding
13
Dies sieht so aus, als ob Haskell a Maybe- A Maybe Customerentweder Just c(wo cist a Customer) oder nennt Nothing. In anderen Sprachen heißt es Option- sehen Sie sich einige der anderen Antworten auf diese Frage an.
MatrixFrog
2
@ SimonBarker: Sie können sicher sein, ob Customer.FirstNamees sich um einen Typ handelt String(im Gegensatz zu String?). Das ist der wirkliche Nutzen - nur Variablen / Eigenschaften , die ein sinnvoller haben nichts Wert als NULL festlegbare deklariert werden.
Yogu
20

Es gibt viele ausgezeichnete Antworten, die die unglücklichen Symptome abdecken. Daher nullmöchte ich ein alternatives Argument vorlegen : Null ist ein Fehler im Typensystem.

Der Zweck eines Typensystems besteht darin, sicherzustellen, dass die verschiedenen Komponenten eines Programms richtig zusammenpassen. Ein gut geschriebenes Programm kann nicht "von der Stange kommen" undefiniertes Verhalten erkennen.

Stellen Sie sich einen hypothetischen Java-Dialekt oder eine beliebige statisch typisierte Sprache vor, 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öglich - jemand könnte eine solche Sprache entwerfen, wenn er möchte. Der spezielle Wert muss nicht sein "Hello, world!"- es hätte die Zahl 42, das Tupel (1, 4, 9)oder sagen wir, sein können null. Aber warum würdest du das tun? Eine Variable vom Typ Foosollte nur Foos enthalten - das ist der springende Punkt des Typsystems! nullist nicht Foomehr als es "Hello, world!"ist. Schlimmer noch, es nullgibt keinen Wert irgendeiner Art und Sie können nichts damit anfangen!

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

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

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

Innerhalb der Just fooKlausel wissen sowohl Sie als auch das Programm, dass unsere Maybe FooVariable wirklich einen FooWert enthält - diese Informationen werden in der Aufrufkette weitergegeben und barmüssen nicht überprüft werden. Da Maybe Fooes sich um einen anderen Typ handelt Foo, sind Sie gezwungen, mit der Möglichkeit Nothingumzugehen , die er enthalten könnte NullPointerException. Sie können viel einfacher über Ihr Programm nachdenken, und der Compiler kann Nullprüfungen auslassen, wenn er weiß, dass alle Variablen des Typs Footatsächlich Foos enthalten . Jeder gewinnt.

Doval
quelle
Was ist mit nullähnlichen Werten in Perl 6? Sie sind immer noch Nullen, außer dass sie immer eingegeben werden. Ist es immer noch ein Problem, das du schreiben kannst my Int $variable = Int, wo der IntWert null vom Typ ist Int?
Konrad Borowski
@xfix Ich bin nicht vertraut mit Perl, aber solange Sie haben keine Möglichkeit zur Durchsetzung der this type only contains integersim Gegensatz zu this type contains integers and this other value, werden Sie ein Problem haben jedes Mal , wenn Sie nur ganze Zahlen wollen.
Doval
1
+1 für den Mustervergleich. Bei der Anzahl der Antworten über den Optionstypen würde man meinen, jemand hätte die Tatsache erwähnt, dass eine einfache Sprachfunktion wie Mustervergleiche die Arbeit mit ihnen viel intuitiver macht als Nullen.
Jules
1
Ich denke, monadisches Binden ist noch nützlicher als Pattern Matching (obwohl bind for Maybe natürlich mithilfe von Pattern Matching implementiert wird). Ich denke , es ist eine Art amüsant zu sehen , Sprachen wie C # in spezielle Syntax setzt für viele Dinge ( ??, ?., und so weiter) für Dinge , die Sprachen wie Haskell als Bibliotheksfunktionen implementieren. Ich finde es viel ordentlicher. null/ Nothingpropagation ist in keiner Weise "speziell". Warum sollten wir also mehr Syntax lernen müssen, um es zu haben?
Sara
14

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

Das spezifische Problem bei null ist, dass die statische Typisierung unterbrochen wird.

Wenn ich eine habe Thing t, dann kann der Compiler garantieren, dass ich anrufen kann t.doSomething(). Nun, UNLESS tist zur Laufzeit null. Jetzt sind alle Wetten geschlossen. Der Compiler sagte, es sei OK, aber ich finde viel später heraus, dass tdies NICHT der Fall ist doSomething(). 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 einfach Python verwenden!

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

Der Unterschied zwischen einer Division durch Null oder einem Logarithmus des Negativen usw. besteht darin, dass es sich bei i = 0 immer noch um ein int handelt. Der Compiler kann weiterhin seinen Typ 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 das tut, ist das so ziemlich die Definition eines Fehlers.

(Der Compiler kann einige dieser Probleme abfangen, übrigens. Wie das Kennzeichnen von Ausdrücken i = 1 / 0zum Zeitpunkt des Kompilierens. Sie können jedoch nicht wirklich erwarten, dass der Compiler einer Funktion folgt und sicherstellt, dass alle Parameter mit der Funktionslogik übereinstimmen.)

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

String s = new Integer (1234);

Warum sollte es dann möglich sein, einen Wert (null) zuzuweisen, der die De-Referenzen aufhebt s?

Indem Sie "no value" mit "typisierten" Referenzen in Ihrem Code mischen, belasten Sie die Programmierer zusätzlich. Und wenn NullPointerExceptions auftreten, kann das Auffinden dieser Ausnahmen noch zeitaufwändiger sein. Anstatt sich auf die statische Eingabe zu verlassen, um zu sagen, "das ist ein Verweis auf etwas Erwartetes", lassen Sie die Sprache sagen, "das kann durchaus ein Verweis auf etwas Erwartetes sein".

Rob Y.
quelle
3
In C *intkennzeichnet eine Variable vom Typ entweder ein intoder NULL. Ebenso *Widgetidentifiziert in C ++ eine Variable vom Typ entweder a Widget, eine Sache, von der der Typ abgeleitet ist Widget, oder NULL. Das Problem mit Java und Derivate wie C # ist , dass sie den Speicherort verwenden Widgetzu bedeuten *Widget. Die Lösung besteht nicht darin, vorzutäuschen, dass *Widgetman nicht in der Lage sein sollte zu halten NULL, sondern einen geeigneten WidgetSpeicherorttyp zu haben .
Supercat
14

Ein nullZeiger 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 ungültigen Zeigerfehler zu finden und zu beheben , verglichen mit dem Auffinden und Beheben eines Nullzeigerfehlers. Es ist einfach, einen Zeiger gegen zu überprüfen null. Es ist ein PITA, um zu überprüfen, ob ein Zeiger ungleich Null auf gültige Daten verweist.

Wenn Sie immer noch nicht überzeugt sind, fügen Sie Ihrem Szenario eine gute Portion 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 nullZeiger ist nicht so weit davon entfernt.


BEARBEITEN: Obwohl das NullObjectMuster eine bessere Lösung zu sein scheint als Referenzen, die null sein können, führt es Probleme für sich allein ein:

  • Ein Verweis, der ein NullObjectsoll (nach der Theorie) enthält, tut nichts, wenn eine Methode aufgerufen wird. Auf diese Weise können subtile Fehler aufgrund von fehlerhaft nicht zugewiesenen Referenzen eingeführt werden, die jetzt garantiert nicht null sind (yehaa!), Aber eine unerwünschte Aktion ausführen: nichts. Bei einer NPE ist es so gut wie offensichtlich, wo das Problem liegt. Bei einem NullObject, der sich irgendwie (aber falsch) verhält, besteht die Gefahr, dass Fehler (zu) spät erkannt werden. Dies ist nicht zufällig ähnlich wie ein ungültiges Zeigerproblem und hat ähnliche Konsequenzen: Der Verweis verweist auf etwas, das wie gültige Daten aussieht, jedoch keine Datenfehler verursacht und möglicherweise schwer zu finden ist. Um ehrlich zu sein, würde ich in diesen Fällen ohne zu zögern eine NPE vorziehen , die jetzt sofort ausfällt .über einen logischen / Datenfehler, der mich später die Straße hinunter plötzlich trifft. Erinnern Sie sich an die IBM Studie über die Kosten von Fehlern, die davon abhängen, in welchem ​​Stadium sie entdeckt werden?

  • Der Begriff, nichts zu tun, wenn eine Methode auf a aufgerufen wird NullObject, wenn ein Eigenschafts-Getter oder eine Funktion aufgerufen wird oder wenn die Methode Werte über zurückgibt, gilt nicht out. Nehmen wir an, der Rückgabewert ist ein intund 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 NullObjectsollte der nichts machen, aber wenn nach einem Wert gefragt wird, muss er irgendwie reagieren. Scheitern ist keine Option: Wir verwenden das NullObject, um die NPE zu verhindern, und wir werden es mit Sicherheit nicht gegen eine andere Ausnahme eintauschen (nicht implementiert, durch Null dividiert, ...), oder? Wie können Sie also nichts korrekt zurückgeben, wenn Sie etwas zurückgeben müssen?

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

JensG
quelle
Können Sie ein Argument dafür vorlegen, warum nulles existieren sollte? Es ist möglich, so gut wie jeden Sprachfehler mit der Hand zu winken, "es ist kein Problem, nur keinen Fehler machen".
Doval
Auf welchen Wert würden Sie einen ungültigen Zeiger setzen?
JensG
Nun, Sie setzen sie nicht auf einen Wert, weil Sie sie überhaupt nicht zulassen sollten. Stattdessen verwenden Sie eine Variable eines anderen Typs, Maybe Tdie möglicherweise entweder Nothingeinen TWert oder einen Wert enthält. Das Wichtigste dabei ist, dass Sie vor der Verwendung prüfen müssen, ob a Maybewirklich a enthält T, und eine Vorgehensweise angeben müssen, falls dies nicht der Fall ist. Und weil Sie ungültige Zeiger nicht zugelassen haben, müssen Sie jetzt niemals etwas überprüfen, das kein ist Maybe.
Doval
1
Veranlasst der Compiler in Ihrem letzten Beispiel die Nullprüfung vor jedem Aufruf von t.Something()? Oder hat es eine Möglichkeit zu wissen, dass Sie den Check bereits durchgeführt haben und ihn nicht erneut durchführen lassen? Was ist, wenn Sie die Prüfung durchgeführt haben, bevor Sie tdiese Methode angewendet haben? Mit dem Optionstyp weiß der Compiler anhand des Typs von, ob Sie die Prüfung bereits durchgeführt haben oder nicht t. Wenn es sich um eine Option handelt, haben Sie sie nicht aktiviert. Wenn es sich um den nicht umbrochenen Wert handelt, haben Sie diesen Wert.
Tim Goodman
1
Was geben Sie als Nullobjekt zurück, wenn Sie nach einem Wert gefragt werden?
JensG
8

Nullreferenzen sind ein Fehler, weil sie unsinnigen Code zulassen:

foo = null
foo.bar()

Es gibt Alternativen, wenn Sie das Typensystem 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 dabei vorzugsweise die für Eiffel vorgeschlagene Compiler-Hilfe in Anspruch nehmen.

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

Matthieu M.
quelle
6
-1 für "Hebel"!
Mark C
8
Aber sicherlich erlauben viele gängige Programmierkonstrukte unsinnigen Code, oder? Eine Division durch Null ist (wahrscheinlich) unsinnig, aber wir erlauben weiterhin eine Division und hoffen, dass der Programmierer sich daran erinnert, zu überprüfen, dass der Divisor nicht Null ist (oder die Ausnahme behandelt).
Tim Goodman
3
Ich bin mir nicht sicher, was Sie unter "von Grund auf neu" verstehen, aber Maybenicht in die Kernsprache integriert data Maybe t = Nothing | Just t.
Fredoverflow
4
@FredOverflow: Da das Vorspiel standardmäßig geladen ist, würde ich es als "von Grund auf neu" betrachten, dass Haskell ein erstaunlich ausreichendes Typensystem hat, um zu ermöglichen, dass diese (und andere) Feinheiten mit den allgemeinen Regeln der Sprache definiert werden. Dies ist nur ein Bonus :)
Matthieu M.
2
@TimGoodman Auch wenn die Division durch Null möglicherweise nicht das beste Beispiel für alle numerischen Werttypen ist (IEEE 754 definiert ein Ergebnis für die Division durch Null), wird in Fällen, in denen eine Ausnahme ausgelöst wird, statt Werte des Typs Tdurch andere Werte des Typs dividiert T, würden Sie die Operation auf Werte vom Typ Tdividiert durch Werte vom Typ beschränken NonZero<T>.
kbolino
8

Und was ist die Alternative?

Optionale Typen und Mustererkennung. Da ich C # nicht kenne, ist hier ein Teil des Codes 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!");
}
Überlauf
quelle
6

Das Problem mit Nullen ist, dass Sprachen, die es ihnen ermöglichen, Sie dazu zwingen, sich dagegen zu wehren. Es ist sehr aufwändig (weit mehr als der 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
  2. dass Ihre Abwehrmechanismen in der Tat alle potenziellen NPEs effektiv behandeln.

Nullen sind in der Tat eine kostspielige Sache.

luis.espinal
quelle
1
Es kann hilfreich sein, wenn Sie ein Beispiel angeben, in dem der Umgang mit Nullen erheblich aufwendiger ist. In meinem zugegebenermaßen stark vereinfachten Beispiel ist der Aufwand in etwa gleich. Ich bin nicht anderer Meinung als Sie, ich finde nur, dass bestimmte (Pseudo-) Codebeispiele ein klareres Bild ergeben als allgemeine Aussagen.
Tim Goodman
2
Ah, ich habe mich nicht klar erklärt. Es ist nicht so, dass es schwieriger ist, mit Nullen umzugehen. Es ist äußerst schwierig (wenn nicht unmöglich), zu gewährleisten, dass alle Zugriffe auf potenziell null Referenzen bereits geschützt sind. Das heißt, in einer Sprache, die Nullreferenzen zulässt, ist es einfach unmöglich zu garantieren, dass ein Stück Code beliebiger Größe frei von Nullzeigerausnahmen ist, nicht ohne einige größere Schwierigkeiten und Anstrengungen. Das ist es, was Nullreferenzen zu einer teuren Sache macht. Es ist extrem teuer, das vollständige Fehlen von Nullzeigerausnahmen zu garantieren.
Luis.espinal
4

Die Frage, inwieweit Ihre Programmiersprache versucht, die Richtigkeit Ihres Programms zu prüfen, bevor es ausgeführt wird. In einer statisch getippten Sprache beweisen Sie, dass Sie die richtigen Typen haben. Indem Sie auf die Standardeinstellung für nicht nullfähige Verweise (mit optionalen nullfähigen Verweisen) wechseln, 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 nullwertfähigen Referenzen den Vorteil hinsichtlich der Programmkorrektheit wert ist.

Winston Ewert
quelle
4

Das Problem ist nicht so sehr null, sondern, dass Sie in vielen modernen Sprachen keinen Nicht-Null-Referenztyp 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, werden Sie bei der defensiven Programmierung aufgefordert, alle Parameter erneut auf Null zu prüfen, auch wenn Sie sie zuvor nur auf Null überprüft haben, insbesondere beim Erstellen wiederverwendbarer zu testender Einheiten. Dieselbe Referenz kann leicht 10-mal in der Codebasis auf Null überprüft werden!

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

Das ist der Punkt der Option-Lösung - nicht das Einschalten von Fehlerzuständen zuzulassen, sondern das Entwerfen von Funktionen, 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 zur Verfügung zu haben.

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

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

Michael Parker
quelle
Ich denke, ein weiterer wichtiger Punkt bei Option <T> ist, dass es sich um eine Monade (und damit auch um einen Endofunktor) handelt, sodass Sie komplexe Berechnungen für Werteströme mit optionalen Typen erstellen können, ohne Nonejeden Schritt des Weges explizit zu überprüfen .
Sara
3

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 explodieren. Entweder haben Sie einen logischen Fehler gemacht und vergessen, nach Goodman zu suchen, oder Goodman hat zwischen dem Zeitpunkt, als Sie nachgesehen haben, und dem Zeitpunkt, als Sie sie nachgeschlagen haben, geheiratet. (Es hilft bei der Bewertung der Logik, wenn Sie sich vorstellen, dass Moriarty jeden Teil Ihres Codes beobachtet und versucht, Sie aus dem Gleichgewicht zu bringen.)

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

Grundsätzlich kommt es darauf an, ob es wichtiger ist, dass der Code funktioniert, egal was passiert, 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 abgefangener / 0-Fehler bewirkte, dass die Rakete eine harte Wendung ausführte und die Selbstzerstörung abfeuerte, wenn sie aufgrund dessen auseinanderbrach. 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 war - es hätte trotz der routinemäßigen Müllrückgabe eine Umlaufbahn erreicht.)

Mindestens 99 von 100 würde ich Nullen verwenden. Ich würde jedoch vor Freude springen, wenn ich mir die Version von ihnen vorstelle.

Loren Pechtel
quelle
1
Das ist eine gute Antwort. Das Null-Konstrukt in der Programmierung ist nicht das Problem, sondern es sind alle Instanzen von Null-Werten. Wenn Sie ein Standardobjekt erstellen, werden eventuell alle Ihre Nullen durch diese Standardwerte ersetzt, und Ihre Benutzeroberfläche, Datenbank und alle dazwischen liegenden Bereiche werden stattdessen mit diesen gefüllt, die wahrscheinlich nicht mehr nützlich sind. Aber du wirst nachts schlafen gehen, weil zumindest dein Programm nicht abstürzt, denke ich.
Chris McCall
2
Sie gehen davon aus, dass dies nulldie einzige (oder sogar eine vorzuziehende) Möglichkeit ist, das Fehlen eines Werts zu modellieren.
Sara
1

Optimieren Sie für den häufigsten Fall.

Es ist mühsam, die ganze Zeit nach null zu suchen - Sie möchten nur das Kundenobjekt erfassen und damit arbeiten können.

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

In Ausnahmefällen , in denen Sie (zufällig) einen Kunden nach seinem 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 unten stehenden SQL-Anbieter dies für Sie tun zu lassen).

Wenn Sie in einer Situation sind, 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 "TryGetCustomer (Name, Ausgangskunde)" angeben. Muster. Querverweis mit int.Parse und int.TryParse.

JBRWilkinson
quelle
Ich würde behaupten, dass Sie nie wissen, ob Sie den eingehenden Daten vertrauen können, weil sie in eine Funktion eingehen und ein nullwertfähiger Typ sind. Code könnte überarbeitet werden, um die Eingabe völlig anders zu gestalten. Wenn also eine Null möglich ist, musst du immer nachsehen. Sie haben also ein System, in dem jede Variable jedes Mal auf Null geprüft wird, bevor auf sie zugegriffen wird. Die Fälle, in denen das Kontrollkästchen nicht aktiviert ist, werden zur prozentualen Fehlerquote für Ihr Programm.
Chris McCall
0

Ausnahmen sind nicht möglich!

Sie sind aus einem bestimmten Grund da. Wenn Ihr Code schlecht läuft, gibt es ein geniales Muster, Exception genannt , und es zeigt Ihnen, dass etwas nicht stimmt.

Indem Sie die Verwendung von Nullobjekten vermeiden, verbergen Sie einen Teil dieser Ausnahmen. Ich spike nicht über OP-Beispiel, in dem er Nullzeigerausnahme in gut typisierte Ausnahme umwandelt, dieses konnte wirklich gute Sache sein, da es Lesbarkeit erhöht. Wenn Sie jedoch die Option-Typ- Lösung verwenden, wie @Jonas hervorhob, verbergen Sie Ausnahmen.

Anstatt in Ihrer Produktionsanwendung eine Ausnahme zu machen, wenn Sie auf die Schaltfläche klicken, um eine leere Option auszuwählen, geschieht nichts. Anstelle des 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 sie tatsächlich beheben.

Es ist eine schlechte Idee, Ihren Code durch Vermeiden von Ausnahmen kugelsicher zu machen, und Sie durch Beheben von Ausnahmen kugelsicher zu machen, und das ist der Pfad, den ich wählen würde.

Ilya Gazman
quelle
1
Ich weiß jetzt einiges mehr über den Optionstyp als vor einigen Jahren, als ich die Frage gestellt habe, da ich sie jetzt regelmäßig in Scala verwende. Der Punkt von Option ist, dass es das Zuweisen Nonezu etwas, das keine Option ist, zu einem Kompilierungsfehler und das Verwenden von a, Optionals ob es einen Wert hätte, ohne vorher zu überprüfen, ob es ein Kompilierungsfehler ist. Kompilierungsfehler sind sicherlich besser als Laufzeitausnahmen.
Tim Goodman
Zum Beispiel: Sie können nicht sagen s: String = None, Sie müssen sagen s: Option[String] = None. Und wenn ses eine Option ist, können Sie nicht sagen s.length, aber Sie können überprüfen, ob es einen Wert hat, und den Wert gleichzeitig extrahieren, mit Mustervergleich:s match { case Some(str) => str.length; case None => throw RuntimeException("Where's my value?") }
Tim Goodman
Leider immer noch in Scala Sie können sagen s: String = null, aber das ist der Preis für die Interoperabilität mit Java. In unserem Scala-Code lehnen wir solche Dinge generell ab.
Tim Goodman
2
Ich stimme jedoch der allgemeinen Bemerkung zu, dass Ausnahmen (richtig verwendet) keine schlechte Sache sind und das "Reparieren" einer Ausnahme durch Verschlucken im Allgemeinen eine schreckliche Idee ist. Mit dem Optionstyp ist es jedoch das Ziel, Ausnahmen in Fehler bei der Kompilierung umzuwandeln, die eine bessere Sache sind.
Tim Goodman
@TimGoodman ist es nur eine Scala-Taktik oder kann es in anderen Sprachen wie Java und ActionScript 3 verwendet werden? Auch wäre es schön, wenn Sie Ihre Antwort mit vollem Beispiel bearbeiten könnten.
Ilya Gazman
0

Nullssind problematisch, weil sie explizit überprüft werden müssen, der Compiler Sie jedoch nicht warnen kann, dass Sie vergessen haben, nach ihnen zu suchen. Nur eine zeitaufwendige statische Analyse kann Ihnen das sagen. Zum Glück gibt es mehrere gute Alternativen.

Nehmen Sie die Variable aus dem Gültigkeitsbereich. Viel zu oft nullwird es als Platzhalter verwendet, wenn ein Programmierer eine Variable zu früh deklariert oder zu lange aufbewahrt. 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 Null-Objektmuster ist hier eine gute Lösung. Es sind keine ständigen expliziten Überprüfungen erforderlich, Sie können jedoch einen Nullstatus darstellen. Es handelt sich nicht um ein "Überarbeiten eines Fehlerzustands", wie manche behaupten, da 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 nehmen.

Verwenden Sie eine Vielleicht / Option. Zuallererst kann der Compiler Sie warnen, dass Sie nach einem fehlenden Wert suchen müssen, dies ersetzt jedoch nicht nur einen expliziten Prüftyp durch einen anderen. Mit Optionskönnen Sie Ihren Code verketten und so weitermachen, als ob Ihr Wert existiert, ohne tatsächlich bis zur letzten Minute nachsehen 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 das Schöne ist, dass wir das nicht müssen . Es ist nicht bis zur allerletzten Zeile, die viele Zeilen später sein kann, oder sogar in einem anderen Modul, wo wir uns explizit damit befassen, was passiert, wenn das Optionist None. Nicht nur das, wenn wir vergessen hätten, es zu tun, hätte es nicht überprüft Typ. Selbst dann geschieht es auf ganz natürliche Weise, ohne explizite ifAussage.

Vergleichen Sie dies mit einem null, bei dem die Typüberprüfung gut funktioniert, bei dem Sie jedoch jeden Schritt auf dem Weg explizit überprüfen müssen oder Ihre gesamte Anwendung zur Laufzeit in die Luft springt, wenn Sie während eines Anwendungsfalls Glück haben, werden Ihre Komponententests durchgeführt. Es ist einfach nicht die Mühe wert.

Karl Bielefeldt
quelle
0

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 mein Rat an die Urheber und Designer von ADA nach bestem Wissen ignoriert. … Lassen Sie diese Sprache in ihrem gegenwärtigen Zustand nicht für Anwendungen zu, bei denen Zuverlässigkeit von entscheidender Bedeutung ist, z. 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 nuklearen Sprengkopf 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. Solche Probleme bestehen jedoch immer noch in Java, C # und vielen anderen gängigen Sprachen.

Es ist die Aufgabe des Entwicklers, Verträge zwischen einem Kunden und einem Lieferanten zu erstellen. In C # können Sie beispielsweise wie in Java Genericsdie Auswirkungen von NullVerweisen minimieren, indem Sie schreibgeschützt erstellen NullableClass<T>(zwei Optionen):

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

und benutze es dann als

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

Oder verwenden Sie den Stil mit zwei Optionen für 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 verstärken, indem es Design by Contract und statische Überprüfung zum Zeitpunkt der Kompilierung verwendet, z. B. unter folgenden Voraussetzungen:

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
}

Diese Vorgehensweise kann drastisch erhöhen Anwendungssicherheit und Software - Qualität. 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 beschrieben wurde, aber in der modernen Software-Entwicklung nicht ungültig verwendet wird.

Artru
quelle
0

Nein.

Das Versäumnis einer Sprache, einen bestimmten Wert wie zu berücksichtigen, nullist das Problem der Sprache. Wenn Sie sich Sprachen wie Kotlin oder Swift ansehen , die den Schreiber zwingen, sich bei jeder Begegnung explizit mit der Möglichkeit eines Nullwerts auseinanderzusetzen, besteht keine Gefahr.

In Sprachen wie der fraglichen kann die Sprache nullein Wert beliebiger Art sein, bietet jedoch keine Konstrukte, um dies später zu bestätigen, was zu unerwarteten Ereignissen führt null(insbesondere, wenn sie von einem anderen Ort an Sie übergeben werden) So kommt es zu Abstürzen. Das ist das Böse: Sie benutzen nullzu lassen, ohne sich darum zu kümmern.

Ben Leggiero
quelle
-1

Grundsätzlich ist die zentrale Idee, dass Nullzeiger-Referenzprobleme zur Kompilierungszeit statt zur Laufzeit abgefangen werden sollten. Wenn Sie also eine Funktion schreiben, die ein Objekt annimmt und von niemandem mit Nullreferenzen aufgerufen werden soll, sollten Sie beim Typ system diese Anforderung angeben, damit der Compiler damit garantieren kann, dass der Aufruf Ihrer Funktion niemals erfolgt Kompilieren Sie, wenn der Aufrufer Ihre Anforderung nicht erfüllt. Dies kann auf viele Arten geschehen, zum Beispiel indem Sie Ihre Typen dekorieren oder Optionstypen verwenden (in einigen Sprachen auch als nullfähige Typen bezeichnet), aber das ist natürlich viel mehr Arbeit für Compiler-Designer.

Ich halte es für verständlich, dass in den 1960er und 70er Jahren der Compiler-Designer nicht all-in ging, um diese Idee umzusetzen, weil die Ressourcen der Maschinen begrenzt waren, die Compiler relativ einfach gehalten werden mussten und niemand wirklich wusste, wie schlimm das werden würde 40 Jahre später.

Shital Shah
quelle
-1

Ich habe einen einfachen Einwand gegen null:

Es unterbricht die Semantik Ihres Codes vollständig, indem es Mehrdeutigkeiten einführt .

Oft erwarten Sie, dass das Ergebnis von einem bestimmten Typ ist. Das ist semantisch richtig. Angenommen, Sie haben die Datenbank nach einem Benutzer mit einem bestimmten Namen gefragt. idSie erwarten, dass das Ergebnis von einem bestimmten Typ ist (= user). Aber was ist, wenn es keinen Benutzer dieser ID gibt? Eine (schlechte) Lösung ist: einen nullWert hinzufügen . Das Ergebnis ist also zweideutig : Entweder es ist ein useroder es ist ein null. Ist nullaber nicht der erwartete Typ. Und hier beginnt der Codegeruch .

Indem nullSie dies vermeiden , machen Sie Ihren Code semantisch klar.

Es gibt immer Möglichkeiten , um null: zu Sammlungen Refactoring, Null-objects, optionalsusw.

Thomas Junk
quelle
-2

Wenn es semantisch möglich sein wird, auf einen Speicherort des Referenz- oder Zeigertyps zuzugreifen, bevor Code ausgeführt wurde, um einen Wert dafür zu berechnen, gibt es keine perfekte Wahl, was passieren soll. Es ist eine mögliche Wahl, solche Speicherorte standardmäßig Nullzeigerreferenzen zu haben, die frei gelesen und kopiert werden können, die jedoch fehlerhaft sind, wenn sie dereferenziert oder indiziert werden. Andere Möglichkeiten sind:

  1. Die Standardeinstellung für Speicherorte ist ein Nullzeiger, aber der Code darf sie nicht anzeigen (oder sogar feststellen, dass sie Null sind), ohne dass sie abstürzen. Geben Sie möglicherweise ein explizites Mittel an, um einen Zeiger auf null zu setzen.
  2. Verwenden Sie als Standardposition einen Nullzeiger und einen Absturz, wenn versucht wird, einen Nullzeiger zu lesen. Bieten Sie jedoch eine Möglichkeit, ohne Absturz zu testen, ob ein Zeiger Null enthält. Stellen Sie auch eine explizite Möglichkeit bereit, einen Zeiger auf null zu setzen.
  3. Stellen Sie standardmäßig einen Zeiger auf eine bestimmte vom Compiler bereitgestellte Standardinstanz des angegebenen Typs ein.
  4. Stellen Sie standardmäßig einen Zeiger auf eine bestimmte vom Programm bereitgestellte Standardinstanz des angegebenen Typs ein.
  5. Wenn Sie einen Nullzeigerzugriff haben (nicht nur Dereferenzierungsoperationen), rufen Sie eine vom Programm bereitgestellte Routine auf, um eine Instanz bereitzustellen.
  6. Entwerfen Sie die Sprachsemantik so, dass keine Auflistungen von Speicherorten vorhanden sind, mit Ausnahme eines temporären Compilers, auf den nicht zugegriffen werden kann, bis Initialisierungsroutinen für alle Member ausgeführt wurden (so etwas wie ein Array-Konstruktor müsste entweder mit einer Standardinstanz versorgt werden, eine Funktion, die eine Instanz oder möglicherweise ein Funktionspaar zurückgibt - eine, die eine Instanz zurückgibt, und eine, die für zuvor erstellte Instanzen aufgerufen wird, wenn während der Erstellung des Arrays eine Ausnahme auftritt.

Die letzte Option könnte attraktiv sein, insbesondere wenn eine Sprache sowohl nullbare als auch nicht nullbare Typen enthält (man könnte die speziellen Arraykonstruktoren für alle Typen aufrufen, aber man muss sie nur aufrufen, wenn Arrays von nicht nullbaren Typen erstellt werden). wäre aber wahrscheinlich zu dem Zeitpunkt, als Nullzeiger erfunden wurden, nicht realisierbar gewesen. Von den anderen Möglichkeiten scheint keine ansprechender zu sein, als das Kopieren von Nullzeigern zuzulassen, die jedoch nicht dereferenziert oder indiziert werden. Ansatz Nr. 4 ist möglicherweise bequem als Option zu haben und sollte relativ billig zu implementieren sein, aber er sollte sicherlich nicht die einzige Option sein. Das Erfordernis, dass Zeiger standardmäßig auf ein bestimmtes gültiges Objekt verweisen müssen, ist weitaus schlimmer als Zeiger standardmäßig auf einen Nullwert, der gelesen oder kopiert, aber nicht dereferenziert oder indiziert werden kann.

Superkatze
quelle
-2

Ja, NULL ist ein schreckliches Design in einer objektorientierten Welt. Kurz gesagt, die Verwendung von NULL führt zu:

  • Ad-hoc-Fehlerbehandlung (anstelle von Ausnahmen)
  • mehrdeutige Semantik
  • langsames statt schnelles Versagen
  • Computerdenken vs. Objektdenken
  • veränderliche und unvollständige Objekte

In diesem Blog-Beitrag finden Sie eine ausführliche Erklärung: http://www.yegor256.com/2014/05/13/why-null-is-bad.html

yegor256
quelle