Von Zeit zu Zeit, wenn sich Programmierer über Nullfehler / Ausnahmen beschweren, fragt jemand, was wir ohne Null machen.
Ich habe eine grundlegende Vorstellung von der Coolness von Optionstypen, aber ich habe nicht die Kenntnisse oder Sprachkenntnisse, um sie am besten auszudrücken. Was ist eine großartige Erklärung für das Folgende, die auf eine Weise geschrieben wurde, die für den durchschnittlichen Programmierer zugänglich ist und auf die wir diese Person hinweisen könnten?
- Die Unerwünschtheit, Referenzen / Zeiger zu haben, ist standardmäßig nullbar
- Funktionsweise von Optionstypen, einschließlich Strategien zur Erleichterung der Überprüfung von Nullfällen wie z
- Mustervergleich und
- monadisches Verständnis
- Alternative Lösung wie Nachricht essen Null
- (andere Aspekte, die ich vermisst habe)
programming-languages
functional-programming
null
nullpointerexception
non-nullable
Roman A. Taycher
quelle
quelle
Antworten:
Ich denke, die kurze Zusammenfassung, warum Null unerwünscht ist, ist, dass bedeutungslose Zustände nicht darstellbar sein sollten .
Angenommen, ich modelliere eine Tür. Es kann sich in einem von drei Zuständen befinden: Öffnen, Schließen, aber Entsperren und Schließen und Sperren. Jetzt konnte ich es nach dem Vorbild von modellieren
und es ist klar, wie ich meine drei Zustände diesen beiden booleschen Variablen zuordnen kann. Damit bleibt jedoch ein vierter, unerwünschter Zustand verfügbar :
isShut==false && isLocked==true
. Da die Typen, die ich als meine Darstellung ausgewählt habe, diesen Zustand zulassen, muss ich mich mental anstrengen, um sicherzustellen, dass die Klasse niemals in diesen Zustand gelangt (möglicherweise durch explizite Codierung einer Invariante). Im Gegensatz dazu, wenn ich eine Sprache mit algebraischen Datentypen oder überprüften Aufzählungen verwendet habe, mit denen ich definieren kanndann könnte ich definieren
und es gibt keine Sorgen mehr. Das Typsystem stellt sicher, dass nur drei mögliche Zustände für eine Instanz
class Door
vorhanden sind. Dies ist der Typ, in dem Typsysteme gut sind - indem eine ganze Klasse von Fehlern beim Kompilieren explizit ausgeschlossen wird.Das Problem dabei
null
ist, dass jeder Referenztyp diesen zusätzlichen Status in seinem Bereich erhält, der normalerweise unerwünscht ist. Einestring
Variable kann eine beliebige Folge von Zeichen sein, oder es kann sich um diesen verrückten Zusatzwert handelnnull
, der nicht in meine Problemdomäne passt. EinTriangle
Objekt hat dreiPoint
s, die selbstX
undY
Werte haben, aber leider kann dasPoint
s oder dasTriangle
selbst dieser verrückte Nullwert sein, der für die Grafikdomäne, in der ich arbeite, bedeutungslos ist.Wenn Sie beabsichtigen, einen möglicherweise nicht vorhandenen Wert zu modellieren, sollten Sie sich explizit dafür entscheiden. Wenn ich beabsichtige, Menschen zu modellieren, dass jeder
Person
einFirstName
und ein hatLastName
, aber nur einige Menschen einMiddleName
s haben, dann möchte ich so etwas sagenwobei
string
hier angenommen wird, dass es sich um einen nicht nullbaren Typ handelt. Dann sind keine kniffligen Invarianten zu ermitteln und keine unerwartetenNullReferenceException
s, wenn versucht wird, die Länge des Namens einer Person zu berechnen. Das Typsystem stellt sicher, dass jeder Code, der sich mit denMiddleName
Konten befasst, die Möglichkeit hat, dass er vorhanden istNone
, während jeder Code, der sich mit den Konten befasst,FirstName
sicher davon ausgehen kann, dass dort ein Wert vorhanden ist.Mit dem obigen Typ könnten wir beispielsweise diese dumme Funktion erstellen:
ohne Sorgen. Im Gegensatz dazu wird in einer Sprache mit nullbaren Referenzen für Typen wie Zeichenfolge angenommen
Am Ende verfassen Sie Dinge wie
Dies geht in die Luft, wenn das Objekt der eingehenden Person nicht die Invariante hat, dass alles nicht null ist, oder
oder vielleicht
Angenommen, dies
p
stellt sicher , dass zuerst / zuletzt vorhanden ist, aber die Mitte kann null sein, oder Sie führen Überprüfungen durch, die verschiedene Arten von Ausnahmen auslösen, oder wer weiß was. All diese verrückten Implementierungsoptionen und Dinge, über die man nachdenken sollte, tauchen auf, weil es diesen dummen darstellbaren Wert gibt, den man nicht will oder braucht.Null fügt normalerweise unnötige Komplexität hinzu. Komplexität ist der Feind aller Software, und Sie sollten sich bemühen, die Komplexität zu reduzieren, wann immer dies sinnvoll ist.
(Beachten Sie auch, dass selbst diese einfachen Beispiele komplexer sind. Auch wenn a
FirstName
nicht sein kannnull
,string
kann a""
(die leere Zeichenfolge) darstellen, was wahrscheinlich auch kein Personenname ist, den wir modellieren möchten. Bei nullbaren Zeichenfolgen kann es dennoch vorkommen, dass wir "bedeutungslose Werte darstellen". Auch hier können Sie dies entweder über Invarianten und bedingten Code zur Laufzeit oder mithilfe des Typsystems (z. B. um einenNonEmptyString
Typ zu haben ) bekämpfen Letzteres ist vielleicht schlecht beraten ("gute" Typen werden oft über eine Reihe gemeinsamer Operationen "geschlossen" und z. B.NonEmptyString
nicht geschlossen.SubString(0,0)
), aber es zeigt mehr Punkte im Designraum. Letztendlich gibt es in einem bestimmten Typsystem eine gewisse Komplexität, die sehr gut beseitigt werden kann, und eine andere Komplexität, die nur an sich schwieriger zu beseitigen ist. Der Schlüssel für dieses Thema ist, dass in fast jedem Typsystem die Änderung von "standardmäßig nullfähige Referenzen" zu "standardmäßig nicht nullfähige Referenzen" fast immer eine einfache Änderung ist, die das Typensystem im Kampf gegen Komplexität und erheblich verbessert bestimmte Arten von Fehlern und bedeutungslosen Zuständen ausschließen. Es ist also ziemlich verrückt, dass so viele Sprachen diesen Fehler immer wieder wiederholen.)quelle
Das Schöne an Optionstypen ist nicht, dass sie optional sind. Es ist so, dass alle anderen Typen es nicht sind .
Manchmal müssen wir in der Lage sein, eine Art "Null" -Zustand darzustellen. Manchmal müssen wir eine Option "kein Wert" sowie die anderen möglichen Werte darstellen, die eine Variable annehmen kann. Eine Sprache, die dies nicht zulässt, wird ein bisschen verkrüppelt sein.
Aber oft brauchen wir es nicht und das Zulassen eines solchen "Null" -Zustands führt nur zu Mehrdeutigkeit und Verwirrung: Jedes Mal, wenn ich auf eine Referenztypvariable in .NET zugreife, muss ich berücksichtigen, dass sie möglicherweise Null ist .
Oft wird es nie tatsächlich null sein, weil die Programmierer Strukturen den Code , so dass es nie passieren können. Der Compiler kann dies jedoch nicht überprüfen, und jedes Mal, wenn Sie es sehen, müssen Sie sich fragen: "Kann dies null sein? Muss ich hier nach null suchen?"
Idealerweise sollte dies in den vielen Fällen, in denen null keinen Sinn ergibt, nicht zulässig sein .
Dies ist in .NET schwierig zu erreichen, wo fast alles null sein kann. Sie müssen sich darauf verlassen, dass der Autor des Codes, den Sie aufrufen, 100% diszipliniert und konsistent ist und klar dokumentiert hat, was null sein kann und was nicht, oder Sie müssen paranoid sein und alles überprüfen .
Wenn Typen jedoch standardmäßig nicht nullwertfähig sind , müssen Sie nicht überprüfen, ob sie nullwert sind. Sie wissen, dass sie niemals null sein können, da der Compiler / Typprüfer dies für Sie erzwingt.
Und dann brauchen wir nur noch eine Hintertür für die seltenen Fälle , in denen wir tun müssen einen Null - Zustand zu behandeln. Dann kann ein "Optionstyp" verwendet werden. Dann erlauben wir null in den Fällen, in denen wir eine bewusste Entscheidung getroffen haben, dass wir in der Lage sein müssen, den Fall "kein Wert" darzustellen, und in jedem anderen Fall wissen wir, dass der Wert niemals null sein wird.
Wie andere bereits erwähnt haben, kann null in C # oder Java eines von zwei Dingen bedeuten:
Die zweite Bedeutung muss erhalten bleiben, die erste sollte jedoch vollständig beseitigt werden. Und selbst die zweite Bedeutung sollte nicht die Standardeinstellung sein. Es ist etwas, für das wir uns entscheiden können, wenn und wann wir es brauchen . Wenn wir jedoch nicht möchten, dass etwas optional ist, möchten wir, dass die Typprüfung garantiert, dass sie niemals null ist.
quelle
Alle bisherigen Antworten konzentrieren sich darauf, warum dies
null
eine schlechte Sache ist und wie praktisch es ist, wenn eine Sprache garantieren kann, dass bestimmte Werte niemals null sind.Anschließend schlagen sie vor, dass es eine ziemlich gute Idee wäre, wenn Sie die Nicht-Null-Fähigkeit für alle Werte erzwingen. Dies ist möglich, wenn Sie ein Konzept hinzufügen
Option
oderMaybe
Typen darstellen, die möglicherweise nicht immer einen definierten Wert haben. Dies ist der Ansatz von Haskell.Es ist alles gutes Zeug! Es schließt jedoch nicht aus, explizit nullbare / nicht null-Typen zu verwenden, um den gleichen Effekt zu erzielen. Warum ist Option dann immer noch eine gute Sache? Schließlich unterstützt Scala nullfähige Werte ( muss , damit es mit Java-Bibliotheken funktionieren kann), unterstützt
Options
aber auch.Frage : Was sind die Vorteile jenseits der Lage, nulls von einer Sprache vollständig zu entfernen?
A. Zusammensetzung
Wenn Sie eine naive Übersetzung aus nullbewusstem Code erstellen
zu optionsbewusstem Code
Es gibt keinen großen Unterschied! Aber es ist auch eine schreckliche Art, Optionen zu verwenden ... Dieser Ansatz ist viel sauberer:
Oder auch:
Wenn Sie anfangen, sich mit der Liste der Optionen zu befassen, wird es noch besser. Stellen Sie sich vor, die Liste
people
selbst ist optional:Wie funktioniert das?
Der entsprechende Code mit Nullprüfungen (oder sogar elvis ?: Operatoren) wäre schmerzhaft lang. Der eigentliche Trick dabei ist die flatMap-Operation, die das verschachtelte Verständnis von Optionen und Sammlungen auf eine Weise ermöglicht, die nullbare Werte niemals erreichen können.
quelle
flatMap
würde man(>>=)
den "Bind" -Operator für Monaden nennen. Das stimmt, Haskeller mögen esflatMap
so sehr, Dinge anzupingen, dass wir sie in das Logo unserer Sprache einfügen .Option<T>
niemals null sein. Leider ist Scala äh, immer noch mit Java verbunden :-) (Wenn Scala andererseits nicht gut mit Java spielen würde, wer würde es verwenden? Oo)Da scheinen die Leute es zu vermissen:
null
ist mehrdeutig.Alices Geburtsdatum ist
null
. Was heißt das?Bobs Todesdatum ist
null
. Was bedeutet das?Eine "vernünftige" Interpretation könnte sein, dass Alices Geburtsdatum existiert, aber unbekannt ist, während Bobs Todesdatum nicht existiert (Bob lebt noch). Aber warum haben wir unterschiedliche Antworten bekommen?
Ein weiteres Problem:
null
ist ein Randfall.null = null
?nan = nan
?inf = inf
?+0 = -0
?+0/0 = -0/0
?Die Antworten lauten normalerweise "Ja", "Nein", "Ja", "Ja", "Nein" bzw. "Ja". Verrückte "Mathematiker" nennen NaN "Nichtigkeit" und sagen, dass es mit sich selbst vergleichbar ist. SQL behandelt Nullen als ungleich (also verhalten sie sich wie NaNs). Man fragt sich, was passiert, wenn Sie versuchen, ± ∞, ± 0 und NaNs in derselben Datenbankspalte zu speichern (es gibt 2 53 NaNs, von denen die Hälfte "negativ" ist).
Um die Sache noch schlimmer zu machen, unterscheiden sich Datenbanken darin, wie sie NULL behandeln, und die meisten von ihnen sind nicht konsistent ( eine Übersicht finden Sie unter NULL-Behandlung in SQLite ). Es ist ziemlich schrecklich.
Und nun zur obligatorischen Geschichte:
Ich habe kürzlich eine (sqlite3) Datenbanktabelle mit fünf Spalten entworfen
a NOT NULL, b, id_a, id_b NOT NULL, timestamp
. Da es sich um ein generisches Schema handelt, mit dem ein generisches Problem für ziemlich beliebige Apps gelöst werden soll, gibt es zwei Eindeutigkeitsbeschränkungen:id_a
existiert nur aus Kompatibilitätsgründen mit einem vorhandenen App-Design (teilweise weil ich keine bessere Lösung gefunden habe) und wird in der neuen App nicht verwendet. Da NULL der Art und Weise in SQL arbeitet, kann ich einfügen(1, 2, NULL, 3, t)
und(1, 2, NULL, 4, t)
und nicht verletzen die erste Eindeutigkeitsbedingung (da(1, 2, NULL) != (1, 2, NULL)
).Dies funktioniert speziell aufgrund der Funktionsweise von NULL in einer Eindeutigkeitsbeschränkung für die meisten Datenbanken (vermutlich, um "reale" Situationen einfacher zu modellieren, z. B. können keine zwei Personen dieselbe Sozialversicherungsnummer haben, aber nicht alle Personen haben eine).
FWIW, ohne zuerst undefiniertes Verhalten aufzurufen, können C ++ - Referenzen nicht auf null "zeigen", und es ist nicht möglich, eine Klasse mit nicht initialisierten Referenzmitgliedsvariablen zu erstellen (wenn eine Ausnahme ausgelöst wird, schlägt die Erstellung fehl).
Nebenbemerkung: Gelegentlich möchten Sie möglicherweise sich gegenseitig ausschließende Zeiger (dh nur einer von ihnen kann nicht NULL sein), z. B. in einem hypothetischen iOS
type DialogState = NotShown | ShowingActionSheet UIActionSheet | ShowingAlertView UIAlertView | Dismissed
. Stattdessen bin ich gezwungen, Dinge wie zu tunassert((bool)actionSheet + (bool)alertView == 1)
.quelle
assert(actionSheet ^ alertView)
? Oder kann deine Sprache XOR nicht bools?Die Unerwünschtheit, Referenzen / Zeiger zu haben, ist standardmäßig nullbar.
Ich denke nicht, dass dies das Hauptproblem bei Nullen ist, das Hauptproblem bei Nullen ist, dass sie zwei Dinge bedeuten können:
Sprachen, die Optionstypen unterstützen, verbieten oder raten normalerweise auch von der Verwendung nicht initialisierter Variablen.
Funktionsweise von Optionstypen, einschließlich Strategien zur Erleichterung der Überprüfung von Nullfällen, z. B. Musterabgleich.
Um effektiv zu sein, müssen Optionstypen direkt in der Sprache unterstützt werden. Andernfalls ist viel Kesselplattencode erforderlich, um sie zu simulieren. Mustervergleich und Typinferenz sind zwei wichtige Sprachfunktionen, mit denen Optionstypen einfach zu bearbeiten sind. Beispielsweise:
In F #:
In einer Sprache wie Java ohne direkte Unterstützung für Optionstypen hätten wir jedoch Folgendes:
Alternative Lösung wie Nachricht essen Null
Die "Nachricht, die nichts isst" von Objective-C ist weniger eine Lösung als vielmehr ein Versuch, den Kopfschmerz der Nullprüfung zu lindern. Anstatt beim Versuch, eine Methode für ein Nullobjekt aufzurufen, eine Laufzeitausnahme auszulösen, wird der Ausdruck grundsätzlich selbst als Null ausgewertet. Wenn man den Unglauben aufhebt, ist es so, als ob jede Instanzmethode mit beginnt
if (this == null) return null;
. Aber dann gibt es einen Informationsverlust: Sie wissen nicht, ob die Methode null zurückgegeben hat, weil sie ein gültiger Rückgabewert ist oder weil das Objekt tatsächlich null ist. Es ähnelt dem Schlucken von Ausnahmen und macht keine Fortschritte bei der Behebung der zuvor beschriebenen Probleme mit Null.quelle
Die Versammlung brachte uns Adressen, die auch als untypisierte Zeiger bekannt sind. C ordnete sie direkt als typisierte Zeiger zu, führte jedoch Algols Null als eindeutigen Zeigerwert ein, der mit allen typisierten Zeigern kompatibel ist. Das große Problem mit null in C ist, dass jeder Zeiger ohne manuelle Überprüfung sicher verwendet werden kann, da jeder Zeiger null sein kann.
In höheren Sprachen ist es umständlich, Null zu haben, da es wirklich zwei unterschiedliche Begriffe vermittelt:
Undefinierte Variablen zu haben ist so gut wie nutzlos und führt zu undefiniertem Verhalten, wann immer sie auftreten. Ich nehme an, jeder wird zustimmen, dass undefinierte Dinge um jeden Preis vermieden werden sollten.
Der zweite Fall ist die Optionalität und wird am besten explizit bereitgestellt, beispielsweise mit einem Optionstyp .
Angenommen, wir sind in einem Transportunternehmen und müssen eine Anwendung erstellen, um einen Zeitplan für unsere Fahrer zu erstellen. Für jeden Fahrer speichern wir einige Informationen wie: den Führerschein und die Telefonnummer, die im Notfall angerufen werden kann.
In C könnten wir haben:
Wie Sie sehen, müssen wir bei jeder Verarbeitung über unsere Treiberliste nach Nullzeigern suchen. Der Compiler wird Ihnen nicht helfen, die Sicherheit des Programms hängt von Ihren Schultern ab.
In OCaml würde derselbe Code folgendermaßen aussehen:
Nehmen wir jetzt an, wir möchten die Namen aller Fahrer zusammen mit ihren LKW-Lizenznummern drucken.
In C:
In OCaml wäre das:
Wie Sie in diesem trivialen Beispiel sehen können, ist die sichere Version nicht kompliziert:
Während in C man einfach einen Nullcheck und einen Boom vergessen hätte ...
Hinweis: Diese Codebeispiele wurden nicht kompiliert, aber ich hoffe, Sie haben die Ideen.
quelle
NULL
wie in "Referenz, die auf nichts verweist" für eine Algol-Sprache erfunden wurde (Wikipedia stimmt zu, siehe en.wikipedia.org/wiki/Null_pointer#Null_pointer ). Aber natürlich ist es wahrscheinlich, dass Assembly-Programmierer ihre Zeiger auf eine ungültige Adresse initialisiert haben (lesen Sie: Null = 0).Microsoft Research hat ein interessantes Projekt namens
Es handelt sich um eine C # -Erweiterung mit dem Typ "Nicht null" und einem Mechanismus, mit dem überprüft wird, ob Ihre Objekte nicht null sind. IMHO ist die Anwendung des Vertrags nach dem Vertragsprinzip jedoch für viele problematische Situationen, die durch Nullreferenzen verursacht werden, geeigneter und hilfreicher.
quelle
Aus dem .NET-Hintergrund kommend dachte ich immer, null hätte einen Punkt, es ist nützlich. Bis ich von Strukturen erfuhr und wie einfach es war, mit ihnen zu arbeiten, ohne viel Code auf der Kesselplatte zu vermeiden. Tony Hoare, der 2009 auf der QCon London sprach, entschuldigte sich für die Erfindung der Nullreferenz . Um ihn zu zitieren:
Siehe diese Frage auch bei Programmierern
quelle
Robert Nystrom bietet hier einen schönen Artikel:
http://journal.stuffwithstuff.com/2010/08/23/void-null-maybe-and-nothing/
Beschreibung seines Denkprozesses beim Hinzufügen von Unterstützung für Abwesenheit und Misserfolg zu seiner Programmiersprache Magpie .
quelle
Ich habe Null (oder Null) immer als das Fehlen eines Wertes angesehen .
Manchmal willst du das, manchmal nicht. Dies hängt von der Domain ab, mit der Sie arbeiten. Wenn die Abwesenheit von Bedeutung ist: kein zweiter Vorname, kann Ihre Bewerbung entsprechend handeln. Wenn andererseits der Nullwert nicht vorhanden sein sollte: Der Vorname ist null, dann erhält der Entwickler den sprichwörtlichen Anruf um 2 Uhr morgens.
Ich habe auch gesehen, dass Code mit Überprüfungen auf Null überladen und überkompliziert ist. Für mich bedeutet dies eines von zwei Dingen:
a) einen Fehler weiter oben im Anwendungsbaum
b) schlechtes / unvollständiges Design
Positiv zu vermerken ist, dass Null wahrscheinlich einer der nützlicheren Begriffe ist, um zu überprüfen, ob etwas fehlt, und Sprachen ohne das Konzept von Null werden die Dinge zu kompliziert, wenn es Zeit ist, Daten zu validieren. In diesem Fall setzen die Sprachen normalerweise Variablen auf eine leere Zeichenfolge, 0 oder eine leere Sammlung, wenn eine neue Variable nicht initialisiert wird. Wenn jedoch eine leere Zeichenfolge oder 0 oder eine leere Sammlung gültige Werte für Ihre Anwendung sind, liegt ein Problem vor.
Manchmal wurde dies umgangen, indem spezielle / seltsame Werte für Felder erfunden wurden, um einen nicht initialisierten Zustand darzustellen. Aber was passiert dann, wenn der Sonderwert von einem gut gemeinten Benutzer eingegeben wird? Und lassen Sie uns nicht in das Chaos geraten, das dies bei Datenüberprüfungsroutinen verursacht. Wenn die Sprache das Nullkonzept unterstützen würde, würden alle Bedenken verschwinden.
quelle
Vektorsprachen können manchmal davonkommen, wenn sie keine Null haben.
Der leere Vektor dient in diesem Fall als typisierte Null.
quelle