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?
language-design
exceptions
pointers
null
Tim Goodman
quelle
quelle
Antworten:
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 value
oder enthalten kannNONE
.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.
quelle
String
Typ ist eigentlich keine Zeichenfolge. Es ist einString or Null
Typ. 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 einString
(oder etwas anderes) erforderlich ist , einen Wert haben muss , wenn Sie eine Typensignatur definieren . DerOption
Typ 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.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.
quelle
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.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 .
quelle
[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.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:
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.quelle
GetByLastName
null zurückgegeben wird, wenn sie nicht gefunden wird (im Gegensatz zum Auslösen einer Ausnahme). Ich kann nur sehen, obCustomer?
oderCustomer
.Maybe
- AMaybe Customer
entwederJust c
(woc
ist aCustomer
) oder nenntNothing
. In anderen Sprachen heißt esOption
- sehen Sie sich einige der anderen Antworten auf diese Frage an.Customer.FirstName
es sich um einen Typ handeltString
(im Gegensatz zuString?
). Das ist der wirkliche Nutzen - nur Variablen / Eigenschaften , die ein sinnvoller haben nichts Wert als NULL festlegbare deklariert werden.Es gibt viele ausgezeichnete Antworten, die die unglücklichen Symptome abdecken. Daher
null
mö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 :Und Sie können Variablen wie folgt überprüfen:
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önnennull
. Aber warum würdest du das tun? Eine Variable vom TypFoo
sollte nurFoo
s enthalten - das ist der springende Punkt des Typsystems!null
ist nichtFoo
mehr als es"Hello, world!"
ist. Schlimmer noch, esnull
gibt 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 alsFoo
s verwendet werden. Beachten Sie, dass das Ausführen der Zeichenfolgenprüfung im vorherigen Snippet nicht die Tatsache verbreitet, dass foo1 wirklich einFoo
-bar
wahrscheinlich auch eine eigene Prüfung hat, nur um sicherzugehen.Vergleichen Sie dies mit der Verwendung eines
Maybe
/Option
-Typs mit Mustererkennung:Innerhalb der
Just foo
Klausel wissen sowohl Sie als auch das Programm, dass unsereMaybe Foo
Variable wirklich einenFoo
Wert enthält - diese Informationen werden in der Aufrufkette weitergegeben undbar
müssen nicht überprüft werden. DaMaybe Foo
es sich um einen anderen Typ handeltFoo
, sind Sie gezwungen, mit der MöglichkeitNothing
umzugehen , die er enthalten könnteNullPointerException
. Sie können viel einfacher über Ihr Programm nachdenken, und der Compiler kann Nullprüfungen auslassen, wenn er weiß, dass alle Variablen des TypsFoo
tatsächlichFoo
s enthalten . Jeder gewinnt.quelle
my Int $variable = Int
, wo derInt
Wert null vom Typ istInt
?this type only contains integers
im Gegensatz zuthis type contains integers and this other value
, werden Sie ein Problem haben jedes Mal , wenn Sie nur ganze Zahlen wollen.??
,?.
, und so weiter) für Dinge , die Sprachen wie Haskell als Bibliotheksfunktionen implementieren. Ich finde es viel ordentlicher.null
/Nothing
propagation ist in keiner Weise "speziell". Warum sollten wir also mehr Syntax lernen müssen, um es zu haben?(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 kannt.doSomething()
. Nun, UNLESSt
ist zur Laufzeit null. Jetzt sind alle Wetten geschlossen. Der Compiler sagte, es sei OK, aber ich finde viel später heraus, dasst
dies NICHT der Fall istdoSomething()
. 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 / 0
zum 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:
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".
quelle
*int
kennzeichnet eine Variable vom Typ entweder einint
oderNULL
. Ebenso*Widget
identifiziert in C ++ eine Variable vom Typ entweder aWidget
, eine Sache, von der der Typ abgeleitet istWidget
, oderNULL
. Das Problem mit Java und Derivate wie C # ist , dass sie den Speicherort verwendenWidget
zu bedeuten*Widget
. Die Lösung besteht nicht darin, vorzutäuschen, dass*Widget
man nicht in der Lage sein sollte zu haltenNULL
, sondern einen geeignetenWidget
Speicherorttyp zu haben .Ein
null
Zeiger ist ein Werkzeugkein 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
null
Zeiger ist nicht so weit davon entfernt.BEARBEITEN: Obwohl das
NullObject
Muster 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
NullObject
soll (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 einemNullObject
, 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 nichtout
. Nehmen wir an, der Rückgabewert ist einint
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? ImmerhinNullObject
sollte der nichts machen, aber wenn nach einem Wert gefragt wird, muss er irgendwie reagieren. Scheitern ist keine Option: Wir verwenden dasNullObject
, 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
NullObject
Muster 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.quelle
null
es existieren sollte? Es ist möglich, so gut wie jeden Sprachfehler mit der Hand zu winken, "es ist kein Problem, nur keinen Fehler machen".Maybe T
die möglicherweise entwederNothing
einenT
Wert oder einen Wert enthält. Das Wichtigste dabei ist, dass Sie vor der Verwendung prüfen müssen, ob aMaybe
wirklich a enthältT
, 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 istMaybe
.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 Siet
diese Methode angewendet haben? Mit dem Optionstyp weiß der Compiler anhand des Typs von, ob Sie die Prüfung bereits durchgeführt haben oder nichtt
. 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.Nullreferenzen sind ein Fehler, weil sie unsinnigen Code zulassen:
Es gibt Alternativen, wenn Sie das Typensystem nutzen:
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 ...quelle
Maybe
nicht in die Kernsprache integriertdata Maybe t = Nothing | Just t
.T
durch andere Werte des Typs dividiertT
, würden Sie die Operation auf Werte vom TypT
dividiert durch Werte vom Typ beschränkenNonZero<T>
.Optionale Typen und Mustererkennung. Da ich C # nicht kenne, ist hier ein Teil des Codes in einer fiktiven Sprache namens Scala # :-)
quelle
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
Nullen sind in der Tat eine kostspielige Sache.
quelle
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.
quelle
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
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
quelle
None
jeden Schritt des Weges explizit zu überprüfen .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.
quelle
null
die einzige (oder sogar eine vorzuziehende) Möglichkeit ist, das Fehlen eines Werts zu modellieren.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.
quelle
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.
quelle
None
zu etwas, das keine Option ist, zu einem Kompilierungsfehler und das Verwenden von a,Option
als ob es einen Wert hätte, ohne vorher zu überprüfen, ob es ein Kompilierungsfehler ist. Kompilierungsfehler sind sicherlich besser als Laufzeitausnahmen.s: String = None
, Sie müssen sagens: Option[String] = None
. Und wenns
es eine Option ist, können Sie nicht sagens.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?") }
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.Nulls
sind 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
null
wird 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
Options
kö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: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
Option
istNone
. 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 expliziteif
Aussage.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.quelle
Das zentrale Problem von NULL ist, dass es das System unzuverlässig macht. 1980 schrieb Tony Hoare in der seinem Turing Award gewidmeten Zeitung:
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
Generics
die Auswirkungen vonNull
Verweisen minimieren, indem Sie schreibgeschützt erstellenNullableClass<T>
(zwei Optionen):und benutze es dann als
Oder verwenden Sie den Stil mit zwei Optionen für C # -Erweiterungsmethoden:
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:
oder für eine Zeichenfolge
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.
quelle
Nein.
Das Versäumnis einer Sprache, einen bestimmten Wert wie zu berücksichtigen,
null
ist 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
null
ein Wert beliebiger Art sein, bietet jedoch keine Konstrukte, um dies später zu bestätigen, was zu unerwarteten Ereignissen führtnull
(insbesondere, wenn sie von einem anderen Ort an Sie übergeben werden) So kommt es zu Abstürzen. Das ist das Böse: Sie benutzennull
zu lassen, ohne sich darum zu kümmern.quelle
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.
quelle
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.
id
Sie 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: einennull
Wert hinzufügen . Das Ergebnis ist also zweideutig : Entweder es ist einuser
oder es ist einnull
. Istnull
aber nicht der erwartete Typ. Und hier beginnt der Codegeruch .Indem
null
Sie dies vermeiden , machen Sie Ihren Code semantisch klar.Es gibt immer Möglichkeiten , um
null
: zu Sammlungen Refactoring,Null-objects
,optionals
usw.quelle
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:
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.
quelle
Ja, NULL ist ein schreckliches Design in einer objektorientierten Welt. Kurz gesagt, die Verwendung von NULL führt zu:
In diesem Blog-Beitrag finden Sie eine ausführliche Erklärung: http://www.yegor256.com/2014/05/13/why-null-is-bad.html
quelle