Wenn Nullen böse sind, was sollte verwendet werden, wenn ein Wert sinnvoll fehlen kann?

26

Dies ist eine der Regeln, die immer wieder wiederholt werden und die mich verwirren.

Nullen sind böse und sollten nach Möglichkeit vermieden werden .

Aber, aber - aus meiner Naivität heraus, lass mich schreien - manchmal KANN ein Wert sinnvoll fehlen!

Bitte lassen Sie mich dies an einem Beispiel fragen, das von diesem Anti-Pattern-gerittenen, schrecklichen Code stammt, an dem ich gerade arbeite . Dies ist im Kern ein auf Multiplayer-Webrunden basierendes Spiel, bei dem beide Spieler gleichzeitig an der Reihe sind (wie in Pokemon und im Gegensatz zu Chess).

Nach jeder Runde sendet der Server eine Liste der Aktualisierungen an den clientseitigen JS-Code. Die Update-Klasse:

public class GameUpdate
{
    public Player player;
    // other stuff
}

Diese Klasse wird in JSON serialisiert und an verbundene Spieler gesendet.

Mit den meisten Updates ist natürlich ein Spieler verbunden - schließlich muss bekannt sein, welcher Spieler in diesem Zug welchen Zug gemacht hat. Bei einigen Updates kann ein Player jedoch nicht sinnvoll zugeordnet werden. Beispiel: Das Spiel wurde erzwungen, weil das Zuglimit ohne Aktionen überschritten wurde. Für solche Updates ist es meiner Meinung nach sinnvoll, den Player auf null zu setzen.

Natürlich könnte ich diesen Code mit Vererbung "reparieren":

public class GameUpdate
{
    // stuff
}

public class GamePlayerUpdate : GameUpdate
{
    public Player player;
    // other stuff
}

Ich verstehe jedoch aus zwei Gründen nicht, wie sich dies verbessern lässt:

  • Der JS-Code erhält nun einfach ein Objekt ohne Player als definierte Eigenschaft. Dies ist das Gleiche, als wäre es null, da in beiden Fällen geprüft werden muss, ob der Wert vorhanden ist oder nicht.
  • Sollte der GameUpdate-Klasse ein weiteres nullbares Feld hinzugefügt werden, müsste ich in der Lage sein, Mehrfachvererbung zu verwenden, um mit diesem Design fortzufahren - aber MI ist für sich genommen böse (erfahreneren Programmierern zufolge), und C # noch wichtiger Habe es, damit ich es nicht benutzen kann.

Ich ahne, dass dieser Teil dieses Codes einer der vielen ist, in denen ein erfahrener, guter Programmierer vor Entsetzen schreien würde. Gleichzeitig kann ich nicht sehen, wie an diesem Ort diese Null irgendetwas verletzt und was stattdessen getan werden sollte.

Können Sie mir dieses Problem erklären?

Gaazkam
quelle
2
Ist diese Frage spezifisch für JavaScript? Viele Sprachen haben einen optionalen Typ für diesen Zweck, aber ich weiß nicht, ob js einen hat (obwohl Sie einen erstellen könnten) oder ob er mit json funktionieren würde (obwohl das ein Teil der bösen Nullprüfungen ist, sondern der gesamte Code
Richard Tingle
9
Diese Regel ist einfach falsch. Ich bin ein bisschen erstaunt, dass sich jemand diesem Gedanken anschließt. In einigen Fällen gibt es einige gute Alternativen zu null. null ist wirklich gut, um einen fehlenden Wert darzustellen. Lassen Sie sich von solchen zufälligen Internetregeln nicht davon abhalten, Ihrem eigenen begründeten Urteil zu vertrauen.
USR
1
@ usr - da stimme ich voll zu. Null ist dein Freund. Meiner Meinung nach gibt es keinen klareren Weg, um einen fehlenden Wert darzustellen. Es kann Ihnen helfen, Fehler zu finden, Fehlerzustände zu beheben und um Vergebung zu bitten (try / catch). Was gibt es nicht zu mögen ?!
Scuba Steve
4
Eine der ersten Regeln beim Entwerfen von Software lautet "Immer auf Null prüfen". Warum dies überhaupt ein Problem ist, ist mir ein Rätsel.
MattE
1
@MattE - Auf jeden Fall. Warum sollte ich ein Entwurfsmuster konstruieren, wenn Null eine gute Sache ist, die Sie in Ihrer Basis-Toolbox haben sollten? Was Graham meiner Meinung nach vorschlug, erscheint mir als Überentwicklung.
Scuba Steve

Antworten:

20

Viele Dinge sind besser als null zurückzukehren.

  • Eine leere Zeichenfolge ("")
  • Eine leere Sammlung
  • Eine "optionale" oder "vielleicht" Monade
  • Eine Funktion, die leise nichts tut
  • Ein Objekt voller Methoden, die leise nichts tun
  • Ein aussagekräftiger Wert, der die falsche Prämisse der Frage zurückweist
    (weshalb Sie an erster Stelle für null gehalten haben)

Der Trick besteht darin, zu erkennen, wann dies getan werden muss. Null ist wirklich gut darin, einem ins Gesicht zu jagen, aber nur, wenn man es abtippt, ohne es zu überprüfen. Es ist nicht gut zu erklären, warum.

Wenn Sie nicht explodieren müssen und nicht auf null prüfen möchten, verwenden Sie eine dieser Alternativen. Es mag seltsam erscheinen, eine Sammlung zurückzugeben, wenn sie nur 0 oder 1 Elemente enthält, aber es ist wirklich gut, wenn Sie sich im Stillen mit fehlenden Werten befassen.

Monaden klingen phantastisch, aber hier können Sie nur deutlich machen, dass die gültigen Größen für die Sammlung 0 und 1 sind. Wenn Sie sie haben, ziehen Sie sie in Betracht.

Alle diese Methoden machen Ihre Absicht besser klar als null. Das ist der wichtigste Unterschied.

Es kann hilfreich sein, zu verstehen, warum Sie überhaupt zurückkehren, anstatt einfach eine Ausnahme auszulösen. Ausnahmen sind im Wesentlichen ein Weg, eine Annahme abzulehnen (sie sind nicht der einzige Weg). Wenn Sie nach dem Schnittpunkt zweier Linien fragen, gehen Sie davon aus, dass sie sich schneiden. Sie könnten parallel sein. Sollten Sie eine Ausnahme "parallele Linien" auslösen? Nun, Sie könnten, aber jetzt müssen Sie diese Ausnahme woanders behandeln oder das System anhalten lassen.

Wenn Sie lieber bleiben möchten, wo Sie sind, können Sie die Ablehnung dieser Annahme in eine Art Wert verwandeln, der entweder Ergebnisse oder Ablehnungen zum Ausdruck bringen kann. Es ist nicht so komisch. Wir machen das die ganze Zeit in der Algebra . Der Trick besteht darin, diesen Wert aussagekräftig zu machen, damit wir verstehen, was passiert ist.

Wenn der zurückgegebene Wert Ergebnisse und Ablehnungen ausdrücken kann, muss er an Code gesendet werden, der beide verarbeiten kann. Polymorphismus ist hier wirklich mächtig. Anstatt Null-Checks einfach gegen Checks zu isPresent()tauschen, können Sie Code schreiben, der sich in beiden Fällen gut verhält. Entweder durch Ersetzen leerer Werte durch Standardwerte oder durch stillschweigendes Nichtstun.

Das Problem mit null ist, dass es viel zu viele Dinge bedeuten kann. Es führt also nur zur Zerstörung von Informationen.


In meinem bevorzugten Null-Gedanken-Experiment möchte ich Sie bitten, sich eine komplexe Rube-Goldberg- Maschine vorzustellen , die codierte Nachrichten mit farbigem Licht signalisiert, indem sie farbige Glühbirnen von einem Förderband aufnimmt, in eine Steckdose schraubt und mit Strom versorgt. Das Signal wird durch die verschiedenen Farben der Lampen gesteuert, die auf dem Förderband platziert sind.

Sie wurden gebeten, dieses abscheulich teure Ding gemäß RFC3.14159 zu machen, das besagt, dass zwischen Nachrichten eine Markierung stehen sollte. Der Marker sollte überhaupt nicht hell sein. Es sollte genau einen Impuls dauern. Die gleiche Zeit, die ein einfarbiges Licht normalerweise ausstrahlt.

Sie können nicht einfach Leerzeichen zwischen den Nachrichten lassen, da der Apparat Alarme auslöst und anhält, wenn er keine Glühbirne zum Einstecken in die Fassung findet.

Jeder, der mit diesem Projekt vertraut ist, schaudert, wenn er die Maschine oder ihren Steuercode berührt. Es ist nicht leicht zu ändern. Wie geht's? Sie können eintauchen und loslegen. Möglicherweise aktualisieren Sie dieses scheußliche Design der Dinge. Ja, du könntest es so viel besser machen. Oder Sie sprechen mit dem Hausmeister und sammeln ausgebrannte Glühbirnen.

Das ist die polymorphe Lösung. Das System muss nicht einmal wissen, dass sich etwas geändert hat.


Es ist wirklich schön, wenn Sie das durchziehen können. Indem Sie eine sinnvolle Zurückweisung einer Annahme in einen Wert kodieren, müssen Sie die Frage nicht zwingen, sich zu ändern.

Wie viele Äpfel schuldest du mir, wenn ich dir 1 Apfel gebe? -1. Weil ich dir 2 Äpfel von gestern schuldig bin. Hier wird die Annahme der Frage "Sie schulden mir etwas" mit einer negativen Zahl abgelehnt. Auch wenn die Frage falsch war, enthält diese Antwort aussagekräftige Informationen. Indem die Frage auf sinnvolle Weise abgelehnt wird, ist sie nicht gezwungen, sich zu ändern.

Manchmal ist es jedoch der richtige Weg, die Frage zu ändern. Wenn die Annahme verlässlicher und dennoch nützlich ist, sollten Sie dies in Betracht ziehen.

Ihr Problem ist, dass Updates normalerweise von Spielern stammen. Sie haben jedoch festgestellt, dass ein Update erforderlich ist, das nicht von Player 1 oder Player 2 stammt. Sie benötigen ein Update, das besagt, dass die Zeit abgelaufen ist. Keiner der Spieler sagt dies. Was solltest du tun? Spieler null zu lassen, scheint so verlockend. Aber es zerstört Informationen. Sie versuchen, Wissen in ein Schwarzes Loch zu verschlüsseln.

Das Spielerfeld sagt dir nicht, wer gerade umgezogen ist. Du denkst, es tut es, aber dieses Problem beweist, dass es eine Lüge ist. Das Player-Feld zeigt an, woher das Update stammt. Dein abgelaufenes Update kommt von der Spieluhr. Meine Empfehlung ist, der Uhr die Ehre zu geben, die sie verdient, und den Namen des playerFeldes in so etwas wie zu ändern source.

Auf diese Weise kann der Server, der heruntergefahren wird und das Spiel unterbrechen muss, ein Update senden, das die Ereignisse meldet und sich selbst als Quelle des Updates aufführt.

Bedeutet das, dass Sie niemals einfach etwas weglassen sollten? Nein. Aber Sie müssen sich überlegen, wie gut nichts wirklich einen einzigen bekannten guten Standardwert bedeuten kann. Nichts ist wirklich leicht zu überbeanspruchen. Sei also vorsichtig, wenn du es benutzt. Es gibt eine Denkschule, in der es darum geht, nichts zu bevorzugen . Es heißt Konvention über Konfiguration .

Ich mag das selbst, aber nur, wenn die Konvention in irgendeiner Weise klar kommuniziert wird. Es sollte kein Weg sein, die Neulinge zu trüben. Aber selbst wenn Sie das verwenden, ist null immer noch nicht der beste Weg, dies zu tun. Null ist ein gefährliches Nichts . Null gibt vor, etwas zu sein, das Sie verwenden können, bis Sie es verwenden, dann bläst es ein Loch in das Universum (bricht Ihr semantisches Modell). Wenn Sie nicht ein Loch in das Universum werfen, ist das das Verhalten, das Sie brauchen, warum spielen Sie damit herum? Benutze etwas anderes.

kandierte_orange
quelle
9
"gib eine Sammlung zurück, wenn sie nur 0 oder 1 Elemente hat" - Es tut mir leid, aber ich glaube nicht, dass ich es verstehe :( Wenn ich mit meinem POV (a) der Klasse unnötige ungültige Zustände hinzufüge (b), muss dies dokumentiert werden Die Sammlung darf höchstens 1 Element haben (c) scheint das Problem ohnehin nicht zu lösen, da ohnehin gesucht werden muss, ob die Sammlung ihr einziges Element enthält, bevor auf sie zugegriffen werden kann, sonst wird eine Ausnahme ausgelöst?
gaazkam
2
@gaazkam sehen? Ich habe dir gesagt, es nervt die Leute. Und doch ist es genau das, was Sie jedes Mal gemacht haben, wenn Sie die leere Zeichenfolge verwenden.
candied_orange
16
Was passiert, wenn ein gültiger Wert eine leere Zeichenfolge oder eine leere Menge ist?
Blrfl
5
@Gaazkam, Sie überprüfen nicht explizit, ob die Sammlung Elemente enthält. Das Durchlaufen (oder Zuordnen) einer leeren Liste ist eine sichere Aktion, die keine Auswirkungen hat.
RubberDuck
3
Ich weiß, dass Sie die Frage beantworten, was jemand tun soll, anstatt null zurückzugeben, aber ich bin mit vielen dieser Punkte wirklich nicht einverstanden. Da wir jedoch nicht sprachspezifisch sind und es einen Grund gibt, gegen jede Regel zu verstoßen, lehne ich eine Ablehnung ab
Liath,
15

nullist hier nicht wirklich das problem. Das Problem ist, wie die meisten aktuellen Programmiersprachen mit fehlenden Informationen umgehen. Sie nehmen dieses Problem nicht ernst (oder bieten zumindest nicht die Unterstützung und Sicherheit, die die meisten Erdbewohner benötigen, um die Gefahren zu vermeiden). Dies führt zu allen Problemen, die wir zu befürchten gelernt haben (Javas NullPointerException, Segfaults, ...).

Mal sehen, wie man mit diesem Problem besser umgeht.

Andere schlugen das Null-Objekt-Muster vor . Obwohl ich denke, dass es manchmal anwendbar ist, gibt es viele Szenarien, in denen es einfach keinen Standardwert für eine Größe gibt. In diesen Fällen müssen die Verbraucher der möglicherweise fehlenden Informationen selbst entscheiden können, was zu tun ist, wenn diese Informationen fehlen.

Es gibt Programmiersprachen, die beim Kompilieren Sicherheit gegen Nullzeiger bieten (so genannte Void-Sicherheit). Um einige moderne Beispiele zu nennen: Kotlin, Rust, Swift. In diesen Sprachen wurde das Problem fehlender Informationen ernst genommen und die Verwendung von null ist völlig schmerzlos. Der Compiler verhindert, dass Sie Nullzeiger dereferenzieren.
In Java wurde versucht, die Annotationen @ Nullable / @ NotNull zusammen mit Compiler + IDE-Plugins zu erstellen, um den gleichen Effekt zu erzielen. Es war nicht wirklich erfolgreich, aber das ist nicht möglich für diese Antwort.

Ein expliziter, generischer Typ, der auf eine mögliche Abwesenheit hinweist, ist eine weitere Möglichkeit, damit umzugehen. ZB in Java gibt es java.util.Optional. Es kann funktionieren:
Auf Projekt- oder Unternehmensebene können Sie Regeln / Richtlinien festlegen

  • Verwenden Sie nur hochwertige Bibliotheken von Drittanbietern
  • immer verwenden java.util.Optionalstattnull

Ihre Sprachgemeinschaft / Ihr Sprachökosystem hat möglicherweise andere Möglichkeiten gefunden, um dieses Problem besser anzugehen als der Compiler / Interpreter.

marstato
quelle
Könnten Sie bitte erläutern, wie Java Optionals besser sind als Nullen? Wenn ich die Dokumentation lese und versuche, einen Wert von der Option abzurufen, wird eine Ausnahme ausgelöst, wenn der Wert nicht vorhanden ist. so scheint es, als wären wir am selben Ort: Ein Scheck ist notwendig und wenn wir einen vergessen, sind wir beschissen?
Gaazkam
3
@gaazkam Du hast Recht, es bietet keine Sicherheit beim Kompilieren. Die Option selbst kann auch null sein, ein weiteres Problem. Aber es ist explizit (im Vergleich zu Variablen, die möglicherweise null / doc-Kommentare sind). Dies bietet nur dann einen echten Vorteil, wenn für die gesamte Codebasis eine Codierungsregel eingerichtet wird. Sie dürfen Dinge nicht auf null setzen. Wenn Informationen fehlen, verwenden Sie Optional. Das zwingt zum Nachdenken. Die üblichen Null-Checks werden dann zu Anrufen anOptional.isPresent
marstato
8
@ Marstato Ersetzen von Null-Prüfungen durch isPresent() ist nicht die empfohlene Verwendung von Optional
candied_orange
10

Ich sehe keinen Grund in der Aussage, dass null schlecht ist. Ich habe die Diskussionen darüber überprüft und sie machen mich nur ungeduldig und irritiert.

Null kann falsch verwendet werden, als "magischer Wert", wenn es tatsächlich etwas bedeutet. Aber Sie müssten eine ziemlich verdrehte Programmierung machen, um dorthin zu gelangen.

Null sollte "nicht da" bedeuten, das ist (noch) nicht angelegt oder (schon) weg oder nicht angegeben, wenn es sich um ein Argument handelt. Es ist kein Staat, das Ganze fehlt. In einer OO-Umgebung, in der ständig Dinge erzeugt und zerstört werden, ist dies eine gültige, aussagekräftige Bedingung, die sich von jedem Zustand unterscheidet.

Mit null werden Sie zur Laufzeit geschlagen, während Sie zur Kompilierungszeit mit einer typsicheren null-Alternative geschlagen werden.

Nicht ganz wahr, ich kann eine Warnung erhalten, wenn ich vor der Verwendung der Variablen nicht nach null gesucht habe. Und es ist nicht dasselbe. Ich möchte vielleicht nicht die Ressourcen eines Objekts verschonen, das keinen Zweck hat. Und was noch wichtiger ist, ich muss trotzdem nach der Alternative suchen, um das gewünschte Verhalten zu erzielen. Wenn ich das vergesse, bekomme ich zur Laufzeit lieber eine NullReferenceException als ein undefiniertes / unbeabsichtigtes Verhalten.

Es gibt ein Problem zu beachten. In einem Multithread-Kontext müssten Sie sich vor Race-Bedingungen schützen, indem Sie zuerst eine lokale Variable zuweisen, bevor Sie die Prüfung auf Null durchführen.

Das Problem mit null ist, dass es zu vielen Dingen weit führen kann. Es führt also nur zur Zerstörung von Informationen.

Nein, es bedeutet / sollte nichts bedeuten, also gibt es nichts zu zerstören. Der Name und der Typ der Variablen / des Arguments zeigen immer an, was fehlt, das ist alles, was man wissen muss.

Bearbeiten:

Ich bin auf halbem Weg mit dem Video candied_orange in einer seiner anderen Antworten über funktionale Programmierung verbunden und es ist sehr interessant. Ich kann den Nutzen davon in bestimmten Szenarien sehen. Der funktionale Ansatz, den ich bisher mit herumfliegenden Codeteilen gesehen habe, lässt mich normalerweise zusammenzucken, wenn ich ihn in einer OO-Umgebung verwende. Aber ich denke, es hat seinen Platz. Irgendwo. Und ich vermute, es wird Sprachen geben, die gute Arbeit leisten und das, was ich als verwirrend empfinde, verbergen, wenn ich in C # darauf stoße, Sprachen, die stattdessen ein sauberes und funktionsfähiges Modell liefern.

Wenn dieses Funktionsmodell Ihre Denkweise / Ihr Framework ist, gibt es wirklich keine Alternative, um Pannen als dokumentierte Fehlerbeschreibungen voranzutreiben und den Anrufer letztendlich mit dem angesammelten Müll fertig zu werden, der auf dem ganzen Weg zurück zum Ausgangspunkt geschleppt wurde. Anscheinend funktioniert die funktionale Programmierung genau so. Nennen Sie es eine Einschränkung, nennen Sie es ein neues Paradigma. Sie können es als Hack für die funktionalen Schüler ansehen, der es ihnen ermöglicht, den unheiligen Weg ein Stück weiter zu beschreiten. Sie können sich über diese neue Art der Programmierung freuen, die aufregende neue Möglichkeiten eröffnet. Was auch immer, es erscheint mir sinnlos, in diese Welt einzutreten, auf die traditionelle OO-Art zurückzugreifen (ja, wir können Ausnahmen werfen, sorry), ein Konzept daraus zu wählen,

Jedes Konzept kann falsch angewendet werden. Aus meiner Sicht sind die vorgestellten "Lösungen" keine Alternativen für eine angemessene Verwendung von null. Sie sind Konzepte aus einer anderen Welt.

Martin Maat
quelle
9
Null zerstört das Wissen darüber, was für ein Nichts es darstellt. Es gibt die Art von nichts, die man im Stillen ignorieren sollte. Die Art von nichts, die das System anhalten muss, um einen ungültigen Zustand zu vermeiden. Die Art von nichts, was bedeutet, dass der Programmierer Fehler gemacht hat und den Code reparieren muss. Die Art von nichts, was bedeutet, dass der Benutzer nach etwas gefragt hat, das nicht existiert und das höflich erzählt werden muss. Entschuldige Nein. Alle diese wurden als null codiert. Aus diesem Grund sollten Sie sich nicht wundern, wenn Sie eine dieser Angaben als null kodieren, wenn niemand weiß, was Sie meinen. Sie zwingen uns, aus dem Kontext zu erraten.
candied_orange
3
@candied_orange Sie können für jeden optionalen Typ genau dasselbe Argument vorbringen: Stellt Nonedar….
Deduplizierer
3
@candied_orange Ich verstehe immer noch nicht, was du meinst. Verschiedene Arten von nichts? Wenn Sie null verwenden, wann sollten Sie vielleicht eine Aufzählung verwenden? Es gibt nur eine Art von nichts. Der Grund dafür, dass etwas nichts ist, kann variieren, aber das ändert weder das Nichts noch die angemessene Reaktion darauf, dass etwas nichts ist. Wenn Sie einen Wert in einem Geschäft erwarten, der nicht vorhanden und der bemerkenswert ist, werfen oder protokollieren Sie in dem Moment, in dem Sie eine Abfrage durchführen, und erhalten nichts. Sie übergeben nicht null als Argument an eine Methode und versuchen dann in dieser Methode, dies herauszufinden warum hast du null Null wäre nicht der Fehler.
Martin Maat
2
Null ist der Fehler, weil Sie die Möglichkeit hatten, die Bedeutung des Nichts zu kodieren. Ihr Verständnis des Nichts erfordert nicht den sofortigen Umgang mit dem Nichts. 0, Null, NaN, leere Zeichenfolge, leere Menge, ungültig, Null-Objekt, nicht zutreffend, nicht implementierte Ausnahme, nichts kommt in vielen, vielen Formen vor. Sie müssen nicht vergessen, was Sie jetzt wissen, nur weil Sie nicht mit dem umgehen möchten, was Sie jetzt wissen. Wenn ich Sie auffordere, die Quadratwurzel von -1 zu ziehen, können Sie entweder eine Ausnahme auslösen, um mich wissen zu lassen, dass dies unmöglich ist, und alles vergessen, oder imaginäre Zahlen erfinden und das, was Sie wissen, beibehalten.
candied_orange
1
@MartinMaat Sie lassen es so klingen, als müsse eine Funktion jedes Mal eine Ausnahme auslösen, wenn Ihre Erwartungen verletzt werden. Das stimmt einfach nicht. Es gibt oft Eckfälle, mit denen man sich befassen muss. Wenn Sie das wirklich nicht sehen können, lesen Sie dies
candied_orange
7

Ihre Frage.

Aber, aber - aus meiner Naivität heraus, lass mich schreien - manchmal KANN ein Wert sinnvoll fehlen!

Voll an Bord mit dir da. Es gibt jedoch einige Einschränkungen, auf die ich gleich eingehen werde. Aber zuerst:

Nullen sind böse und sollten nach Möglichkeit vermieden werden.

Ich denke, das ist eine gut gemeinte, aber übergeneralisierte Aussage.

Nullen sind nicht böse. Sie sind kein Gegenmuster. Sie sind nicht um jeden Preis zu vermeiden. Nullen sind jedoch fehleranfällig (da sie nicht auf Null geprüft werden können).

Aber ich verstehe nicht, wie das Nicht-Prüfen auf Null irgendwie beweist, dass die Verwendung von Null von Natur aus falsch ist.

Wenn null böse ist, sind es auch Array-Indizes und C ++ - Zeiger. Sie sind nicht. Mit ihnen kann man sich leicht in den Fuß schießen, aber sie sollten nicht um jeden Preis gemieden werden.

Für den Rest dieser Antwort werde ich die Absicht von "Nullen sind böse" an eine nuanciertere " Nullen sollten verantwortungsbewusst behandelt werden " anpassen .


Ihre Lösung.

Ich stimme Ihrer Meinung über die Verwendung von Null als globale Regel zu. Ich bin mit Ihrer Verwendung von null in diesem speziellen Fall nicht einverstanden.

Um die folgenden Rückmeldungen zusammenzufassen: Sie missverstehen den Zweck von Vererbung und Polymorphismus. Während Ihr Argument, Null zu verwenden, Gültigkeit hat, beruht Ihr weiterer "Beweis", warum der andere Weg schlecht ist, auf dem Missbrauch der Vererbung, wodurch Ihre Punkte für das Null-Problem etwas irrelevant werden.

Mit den meisten Updates ist natürlich ein Spieler verbunden - schließlich muss bekannt sein, welcher Spieler in diesem Zug welchen Zug gemacht hat. Bei einigen Updates kann ein Player jedoch nicht sinnvoll zugeordnet werden.

Wenn diese Aktualisierungen erheblich voneinander abweichen (was sie sind), benötigen Sie zwei separate Aktualisierungstypen.

Betrachten Sie zum Beispiel Nachrichten. Sie haben Fehlermeldungen und IM-Chat-Nachrichten. Sie enthalten zwar möglicherweise sehr ähnliche Daten (eine Zeichenfolgennachricht und einen Absender), verhalten sich jedoch nicht gleich. Sie sollten separat als unterschiedliche Klassen verwendet werden.

Was Sie jetzt tun, ist die effektive Unterscheidung zwischen zwei Aktualisierungstypen basierend auf der Nichtigkeit der Player-Eigenschaft. Dies entspricht der Entscheidung zwischen einer Sofortnachricht und einer Fehlermeldung, basierend auf dem Vorhandensein eines Fehlercodes. Es funktioniert, aber es ist nicht der beste Ansatz.

  • Der JS-Code erhält nun einfach ein Objekt ohne Player als definierte Eigenschaft. Dies ist das Gleiche, als wäre es null, da in beiden Fällen geprüft werden muss, ob der Wert vorhanden ist oder nicht.

Ihr Argument stützt sich auf Missstände des Polymorphismus. Wenn Sie eine Methode, die ein zurück GameUpdateObjekt, sollte es in der Regel nicht immer kümmert zwischen zu unterscheiden GameUpdate, PlayerGameUpdateoder NewlyDevelopedGameUpdate.

Eine Basisklasse muss über einen voll funktionsfähigen Vertrag verfügen, dh ihre Eigenschaften werden in der Basisklasse und nicht in der abgeleiteten Klasse definiert (der Wert dieser Basiseigenschaften kann jedoch in den abgeleiteten Klassen natürlich überschrieben werden).

Dies macht Ihre Argumentation streitig. In der guten Praxis sollte es Ihnen egal sein, ob Ihr GameUpdateObjekt einen Spieler hat oder nicht. Wenn Sie versuchen, auf die PlayerEigenschaft von a zuzugreifen GameUpdate, tun Sie etwas Unlogisches.

Eingegebene Sprachen wie C # würden es Ihnen nicht einmal erlauben , auf die PlayerEigenschaft von a zuzugreifen, GameUpdateda Sie GameUpdateeinfach nicht über die Eigenschaft verfügen. Javascript ist in seiner Herangehensweise wesentlich lässiger (und es erfordert keine Vorkompilierung), sodass es sich nicht die Mühe macht, Sie zu korrigieren, und es stattdessen zur Laufzeit in die Luft sprengt.

  • Sollte der GameUpdate-Klasse ein weiteres nullbares Feld hinzugefügt werden, müsste ich in der Lage sein, Mehrfachvererbung zu verwenden, um mit diesem Design fortzufahren - aber MI ist für sich genommen böse (erfahreneren Programmierern zufolge), und C # noch wichtiger Habe es, damit ich es nicht benutzen kann.

Sie sollten keine Klassen erstellen, die auf dem Hinzufügen von nullfähigen Eigenschaften basieren. Sie sollten Klassen basierend auf funktional einzigartigen Arten von Spielaktualisierungen erstellen .


Wie vermeide ich die Probleme mit null im Allgemeinen?

Ich denke, es ist wichtig zu erläutern, wie man das Fehlen eines Wertes sinnvoll ausdrücken kann. Dies ist eine Sammlung von Lösungen (oder schlechten Ansätzen) in keiner bestimmten Reihenfolge.

1. Verwenden Sie trotzdem null.

Dies ist der einfachste Ansatz. Sie melden sich jedoch für eine Tonne Nullprüfungen an und (wenn Sie dies nicht tun) für die Fehlerbehebung bei Nullreferenzausnahmen.

2. Verwenden Sie einen sinnlosen Wert ungleich Null

Ein einfaches Beispiel hierfür ist indexOf():

"abcde".indexOf("b"); // 1
"abcde".indexOf("f"); // -1

Stattdessen nullbekommst du -1. Auf technischer Ebene ist dies ein gültiger ganzzahliger Wert. Ein negativer Wert ist jedoch eine unsinnige Antwort, da bei Indizes positive ganze Zahlen erwartet werden.

Logischerweise kann dies nur in Fällen verwendet werden, in denen der Rückgabetyp mehr mögliche Werte zulässt, als sinnvoll zurückgegeben werden können (indexOf = positive Ganzzahlen; int = negative und positive Ganzzahlen).

Für Referenztypen können Sie dieses Prinzip weiterhin anwenden. Wenn Sie beispielsweise eine Entität mit einer Ganzzahl-ID haben, können Sie ein Dummy-Objekt mit der ID -1 anstelle von null zurückgeben.
Sie müssen lediglich einen "Dummy-Status" definieren, anhand dessen Sie messen können, ob ein Objekt tatsächlich verwendbar ist oder nicht.

Beachten Sie, dass Sie sich nicht auf vorgegebene Zeichenfolgenwerte verlassen sollten, wenn Sie den Zeichenfolgenwert nicht steuern. Sie können erwägen, den Namen eines Benutzers auf "null" zu setzen, wenn Sie ein leeres Objekt zurückgeben möchten. Es treten jedoch Probleme auf, wenn Christopher Null sich für Ihren Dienst anmeldet .

3. Werfen Sie stattdessen eine Ausnahme.

Nein einfach nein. Ausnahmen sollten für den logischen Ablauf nicht behandelt werden.

Allerdings gibt es einige Fälle, in denen Sie die (Nullreferenz-) Ausnahme nicht ausblenden, sondern eine entsprechende Ausnahme anzeigen möchten. Beispielsweise:

var existingPerson = personRepository.GetById(123);

Dies kann ein PersonNotFoundException(oder ähnliches) werfen, wenn es keine Person mit der ID 123 gibt.

Dies ist natürlich nur dann akzeptabel, wenn das Nichtfinden eines Artikels eine weitere Bearbeitung unmöglich macht. Wenn das Finden eines Elements nicht möglich ist und die Anwendung fortgesetzt werden kann, sollten Sie NIEMALS eine Ausnahme auslösen . Ich kann das nicht genug betonen.

Flater
quelle
5

Der Grund, warum Nullen schlecht sind, besteht nicht darin, dass ein fehlender Wert als Null codiert wird, sondern dass jeder Referenzwert Null sein kann. Wenn Sie C # haben, sind Sie wahrscheinlich trotzdem verloren. Ich sehe dort keinen Punkt, um ein optionales Element zu verwenden, da ein Referenztyp bereits optional ist. Für Java gibt es jedoch @Notnull-Annotationen, die Sie anscheinend auf Paketebene einrichten können. Für Werte, die übersehen werden können, können Sie die Annotation @Nullable verwenden.

max630
quelle
Ja! Das Problem ist ein unsinniger Standard.
Deduplizierer
Ich stimme dir nicht zu. Das Programmieren in einem Stil, der Null vermeidet, führt dazu, dass weniger Referenzvariablen Null sind, was dazu führt, dass weniger Referenzvariablen, die nicht Null sein sollten, Null sind. Es ist nicht 100%, aber es ist vielleicht 90%, was es wert ist, IMO.
Setzen Sie Monica
1

Nullen sind nicht böse, es gibt realistisch keine Möglichkeit, eine der Alternativen zu implementieren, ohne sich irgendwann mit etwas zu befassen, das wie eine Null aussieht.

Nullen sind jedoch gefährlich . Genau wie bei jedem anderen Low-Level-Konstrukt, wenn Sie etwas falsch machen, gibt es unten nichts, was Sie fangen könnte.

Ich denke, es gibt viele stichhaltige Argumente gegen null:

  • Wenn Sie sich bereits in einer High-Level-Domäne befinden, gibt es sicherere Muster als bei der Verwendung des Low-Level-Modells.

  • Wenn Sie nicht die Vorteile eines Low-Level-Systems benötigen und bereit sind, vorsichtig zu sein, sollten Sie möglicherweise keine Low-Level-Sprache verwenden.

  • etc ...

Dies alles ist gültig und es wird als allgemeiner Grund angesehen, diesen Rat nicht befolgt zu haben, wenn man sich nicht die Mühe macht, Getter und Setter usw. zu schreiben. Es ist nicht wirklich überraschend, dass viele Programmierer zu dem Schluss gekommen sind, dass Nullen gefährlich und faul sind (eine schlechte Kombination!), Aber wie so viele andere "universelle" Ratschläge sind sie nicht so universell wie sie formuliert sind.

Die guten Leute, die die Sprache geschrieben haben, dachten, sie wären für jemanden nützlich. Wenn Sie die Einwände verstehen und dennoch glauben, dass sie für Sie richtig sind, weiß es niemand besser.

Drjpizzle
quelle
Die Sache ist, "universeller Rat" wird normalerweise nicht als "universell" formuliert, wie die Leute behaupten. Wenn Sie die ursprüngliche Quelle für solche Ratschläge finden, sprechen sie normalerweise von den Vorbehalten, die erfahrene Programmierer kennen. Was passiert, ist, dass jemand, der den Rat liest, dazu neigt, diese Vorbehalte zu ignorieren und sich nur an den "universellen Ratschlag" zu erinnern. Wenn er diesen Ratschlag auf andere bezieht, kommt er viel "universeller" heraus, als der Urheber beabsichtigt hat .
Nicol Bolas
1
@NicolBolas, Sie haben Recht, aber die ursprüngliche Quelle ist nicht der Rat, den die meisten Leute zitieren, es ist die weitergeleitete Nachricht. Der Punkt, den ich ansprechen wollte, war lediglich, dass vielen Programmierern gesagt wurde: "Mach niemals X, X ist böse / schlecht / was auch immer", und genau wie du sagst, es fehlen viele Vorbehalte. Vielleicht wurden sie fallen gelassen, vielleicht waren sie nie anwesend, das Endergebnis ist das gleiche.
Drjpizzle
0

Java hat eine OptionalKlasse mit einem expliziten ifPresent+ Lambda, um sicher auf jeden Wert zuzugreifen. Dies kann auch in JS gemacht werden:

Siehe diese StackOverflow-Frage .

Übrigens: Viele Puristen werden diese Verwendung zweifelhaft finden, aber ich denke nicht. Optionale Funktionen sind vorhanden.

Joop Eggen
quelle
Kann nicht ein Hinweis auf ein java.lang.Optionalsein nullauch?
Deduplicator
@Deduplicator Nein, Optional.of(null)würde eine Ausnahme auslösen , Optional.ofNullable(null)würde geben Optional.empty()- den Wert nicht da.
Joop Eggen
Und Optional<Type> a = null?
Deduplizierer
@Deduplicator stimmt, aber da sollte man verwenden Optional<Optional<Type>> a = Optional.empty();;) - Mit anderen Worten, in JS (und anderen Sprachen) werden solche Dinge nicht machbar sein. Scala mit Standardwerten würde ausreichen. Java vielleicht mit einer Aufzählung. JS Ich bezweifle: Optional<Type> a = Optional.empty();.
Joop Eggen
Wir haben uns also von einer indirekten und potenziellen nulloder leeren Methode zu zwei plus einigen zusätzlichen Methoden für das zwischengeschaltete Objekt entwickelt. Das ist schon eine Leistung.
Deduplizierer
0

CAR Hoare nannte Nullen seinen "Milliarden-Dollar-Fehler". Es ist jedoch wichtig anzumerken, dass er der Meinung war, dass die Lösung nicht "nirgendwo Nullen", sondern "nicht nullbare Zeiger als Standard und Nullen nur dort, wo sie semantisch erforderlich sind".

Das Problem mit null in den Sprachen, die seinen Fehler wiederholen, ist, dass Sie nicht sagen können, dass eine Funktion nicht nullfähige Daten erwartet. es ist in der Sprache grundsätzlich unaussprechlich. Das zwingt die Funktionen dazu, eine Menge nutzloser, fremder Boilerplates für die Nullbehandlung einzuschließen, und das Vergessen einer Nullprüfung ist eine häufige Fehlerquelle.

Um diesen grundlegenden Mangel an Ausdruckskraft zu umgehen, verwenden einige Communities (z. B. die Scala-Community) nicht null. Wenn Sie in Scala einen nullwertfähigen Wert vom Typ möchten T, verwenden Sie ein Argument vom Typ Option[T], und wenn Sie einen nicht nullwertfähigen Wert möchten, verwenden Sie einfach a T. Wenn jemand eine Null in Ihren Code eingibt, wird Ihr Code explodieren. Es wird jedoch angenommen, dass der aufrufende Code der fehlerhafte Code ist. Optionist jedoch ein ziemlich netter Ersatz für null, da gängige Nullbehandlungsmuster in Bibliotheksfunktionen wie .map(f)oder extrahiert werden .getOrElse(someDefault).

user311781
quelle