it-swarm.com.de

Ist ein neues Boolesches Feld besser als eine Nullreferenz, wenn ein Wert sinnvoll fehlen kann?

Angenommen, ich habe eine Klasse, Member, die eine lastChangePasswordTime hat:

class Member{
  .
  .
  .
  constructor(){
    this.lastChangePasswordTime=null,
  }
}

deren lastChangePasswordTime kann ohne Abwesenheit sinnvoll sein, da einige Mitglieder ihre Passwörter möglicherweise nie ändern.

Aber laut Wenn Nullen böse sind, was sollte verwendet werden, wenn ein Wert sinnvoll fehlen kann? und https: // softwareengineering) .stackexchange.com/a/12836/248528, ich sollte null nicht verwenden, um einen sinnvoll fehlenden Wert darzustellen. Also versuche ich ein Boolesches Flag hinzuzufügen:

class Member{
  .
  .
  .
  constructor(){
    this.isPasswordChanged=false,
    this.lastChangePasswordTime=null,
  }
}

Aber ich denke, es ist ziemlich veraltet, weil:

  1. Wenn isPasswordChanged false ist, muss lastChangePasswordTime null sein, und die Überprüfung von lastChangePasswordTime == null ist fast identisch mit der Überprüfung von isPasswordChanged ist false. Daher bevorzuge ich die direkte Überprüfung von lastChangePasswordTime == null

  2. Wenn ich hier die Logik ändere, vergesse ich möglicherweise, beide Felder zu aktualisieren.

Hinweis: Wenn ein Benutzer Kennwörter ändert, würde ich die Zeit folgendermaßen aufzeichnen:

this.lastChangePasswordTime=Date.now();

Ist das zusätzliche Boolesche Feld hier besser als eine Nullreferenz?

39
ocomfd

Ich verstehe nicht, warum, wenn Sie einen sinnvoll fehlenden Wert haben, null nicht verwendet werden sollte, wenn Sie absichtlich und vorsichtig damit umgehen.

Wenn Sie den nullbaren Wert umgeben möchten, um ein versehentliches Verweisen auf ihn zu verhindern, würde ich vorschlagen, den Wert isPasswordChanged als Funktion oder Eigenschaft zu erstellen, die das Ergebnis einer Nullprüfung zurückgibt, zum Beispiel:

class Member {
    DateTime lastChangePasswordTime = null;
    bool isPasswordChanged() { return lastChangePasswordTime != null; }

}

Meiner Meinung nach geht das so:

  • Bietet eine bessere Lesbarkeit des Codes als eine Nullprüfung, die den Kontext verlieren könnte.
  • Beseitigt die Notwendigkeit, dass Sie sich tatsächlich um die Beibehaltung des von Ihnen erwähnten Werts isPasswordChanged kümmern müssen.

Die Art und Weise, wie Sie die Daten beibehalten (vermutlich in einer Datenbank), ist dafür verantwortlich, dass die Nullen erhalten bleiben.

71
TZHX

nulls sind nicht böse. Sie ohne nachzudenken zu benutzen ist. Dies ist ein Fall, in dem null genau die richtige Antwort ist - es gibt kein Datum.

Beachten Sie, dass Ihre Lösung mehr Probleme verursacht. Was bedeutet es, wenn das Datum auf etwas gesetzt wird, aber das isPasswordChanged ist falsch? Sie haben gerade einen Fall von widersprüchlichen Informationen erstellt, die Sie speziell abfangen und behandeln müssen, während ein null -Wert eine klar definierte, eindeutige Bedeutung hat und nicht mit anderen Informationen in Konflikt stehen kann.

Also nein, Ihre Lösung ist nicht besser. Das Erlauben eines null -Werts ist hier der richtige Ansatz. Leute, die behaupten, dass null immer böse ist, egal in welchem ​​Kontext, verstehen nicht, warum null existiert.

63
Tom

Abhängig von Ihrer Programmiersprache gibt es möglicherweise gute Alternativen, z. B. einen Datentyp optional. In C++ (17) wäre dies std :: optional . Es kann entweder fehlen oder einen beliebigen Wert des zugrunde liegenden Datentyps haben.

29
dasmy

Richtig mit null

Es gibt verschiedene Möglichkeiten, null zu verwenden. Die gebräuchlichste und semantisch korrekteste Methode ist die Verwendung, wenn Sie möglicherweise einen Einzelwert haben oder nicht. In diesem Fall entspricht ein Wert entweder null oder ist so aussagekräftig wie ein Datensatz aus einer Datenbank oder so.

In diesen Situationen verwenden Sie es dann meistens so (im Pseudocode):

if (value is null) {
  doSomethingAboutIt();
  return;
}

doSomethingUseful(value);

Problem

Und es hat ein sehr großes Problem. Das Problem ist, dass zum Zeitpunkt des Aufrufs von doSomethingUseful der Wert möglicherweise nicht auf null überprüft wurde! Wenn dies nicht der Fall ist, stürzt das Programm wahrscheinlich ab. Und der Benutzer sieht möglicherweise nicht einmal gute Fehlermeldungen, da er so etwas wie "schrecklicher Fehler: gewollter Wert, aber null!" (nach dem Update: obwohl es möglicherweise noch weniger informative Fehler wie Segmentation fault. Core dumped. gibt, oder noch schlimmer, in einigen Fällen kein Fehler und falsche Manipulation auf Null)

Das Vergessen, Prüfungen für null zu schreiben und null Situationen zu behandeln, ist ein extrem häufiger Fehler. Aus diesem Grund sagte Tony Hoare, der null erfand, auf einer Softwarekonferenz namens QCon London im Jahr 2009, dass er 1965 den Milliarden-Dollar-Fehler gemacht habe: https://www.infoq.com/presentations/ Null-Referenzen-Der-Milliarden-Dollar-Fehler-Tony-Hoare

Das Problem vermeiden

Einige Technologien und Sprachen machen es unmöglich, auf verschiedene Weise nach null zu suchen, wodurch die Anzahl der Fehler verringert wird.

Zum Beispiel hat Haskell die Monade Maybe anstelle von Nullen. Angenommen, DatabaseRecord ist ein benutzerdefinierter Typ. In Haskell kann ein Wert vom Typ Maybe DatabaseRecord Gleich Just <somevalue> Oder Nothing sein. Sie können es dann auf verschiedene Arten verwenden, aber unabhängig davon, wie Sie es verwenden, können Sie keine Operation auf Nothing anwenden, ohne es zu wissen.

Zum Beispiel gibt diese Funktion namens zeroAsDefaultx für Just x Und 0 Für Nothing zurück:

zeroAsDefault :: Maybe Int -> Int
zeroAsDefault mx = case mx of
    Nothing -> 0
    Just x -> x

Christian Hackl sagt, C++ 17 und Scala haben ihre eigenen Wege. Vielleicht möchten Sie also herausfinden, ob Ihre Sprache so etwas hat, und es verwenden.

Nullen sind immer noch weit verbreitet

Wenn Sie nichts Besseres haben, ist die Verwendung von null in Ordnung. Pass einfach weiter auf. Typdeklarationen in Funktionen helfen Ihnen sowieso etwas.

Auch das mag nicht sehr progressiv klingen, aber Sie sollten prüfen, ob Ihre Kollegen null oder etwas anderes verwenden möchten. Sie sind möglicherweise konservativ und möchten aus bestimmten Gründen möglicherweise keine neuen Datenstrukturen verwenden. Zum Beispiel Unterstützung älterer Versionen einer Sprache. Solche Dinge sollten in den Codierungsstandards des Projekts deklariert und ordnungsgemäß mit dem Team besprochen werden.

Auf Ihren Vorschlag

Sie schlagen vor, ein separates boolesches Feld zu verwenden. Aber Sie müssen es trotzdem überprüfen und können trotzdem vergessen, es zu überprüfen. Hier wird also nichts gewonnen. Wenn Sie sogar etwas anderes vergessen können, z. B. jedes Mal beide Werte aktualisieren, ist es noch schlimmer. Wenn das Problem, zu vergessen, nach null zu suchen, nicht gelöst ist, macht es keinen Sinn. Das Vermeiden von null ist schwierig und Sie sollten es nicht so tun, dass es noch schlimmer wird.

Wie man null nicht benutzt

Schließlich gibt es übliche Möglichkeiten, null falsch zu verwenden. Eine Möglichkeit besteht darin, es anstelle leerer Datenstrukturen wie Arrays und Strings zu verwenden. Ein leeres Array ist ein richtiges Array wie jedes andere! Es ist fast immer wichtig und nützlich, dass Datenstrukturen, die mehreren Werten entsprechen können, leer sein können, d. H. Eine Länge von 0 haben.

Vom algebraischen Standpunkt aus ist eine leere Zeichenfolge für Zeichenfolgen 0 für Zahlen ähnlich, d. H. Identität:

a+0=a
concat(str, '')=str

Mit einer leeren Zeichenfolge können Zeichenfolgen im Allgemeinen zu einem Monoid werden: https://en.wikipedia.org/wiki/Monoid Wenn Sie es nicht verstehen, ist es für Sie nicht so wichtig.

Lassen Sie uns nun anhand dieses Beispiels sehen, warum dies für die Programmierung wichtig ist:

for (element in array) {
  doSomething(element);
}

Wenn wir hier ein leeres Array übergeben, funktioniert der Code einwandfrei. Es wird einfach nichts tun. Wenn wir hier jedoch ein null übergeben, wird wahrscheinlich ein Absturz mit dem Fehler "Null kann nicht durchlaufen werden, sorry" angezeigt. Wir könnten es in if einwickeln, aber das ist weniger sauber und wieder könnte vergessen, es zu überprüfen

Wie man mit null umgeht

Was doSomethingAboutIt() tun sollte und insbesondere, ob es eine Ausnahme auslösen sollte, ist ein weiteres kompliziertes Problem. Kurz gesagt, es hängt davon ab, ob null ein akzeptabler Eingabewert für eine bestimmte Aufgabe war und was als Antwort erwartet wird. Ausnahmen gelten für Ereignisse, die nicht erwartet wurden. Ich werde nicht weiter auf dieses Thema eingehen. Diese Antwort ist schon sehr lang.

11
Gherman

Zusätzlich zu all den sehr guten Antworten, die zuvor gegeben wurden, möchte ich hinzufügen, dass Sie jedes Mal, wenn Sie versucht sind, ein Feld null zu lassen, sorgfältig überlegen, ob es sich möglicherweise um einen Listentyp handelt. Ein nullbarer Typ ist äquivalent eine Liste von 0 oder 1 Elementen, und häufig kann dies auf eine Liste von N Elementen verallgemeinert werden. Insbesondere in diesem Fall möchten Sie möglicherweise lastChangePasswordTime als eine Liste von passwordChangeTimes betrachten.

4
Joel

Fragen Sie sich Folgendes: Für welches Verhalten ist das Feld lastChangePasswordTime erforderlich?

Wenn Sie dieses Feld für eine Methode IsPasswordExpired () benötigen, um zu bestimmen, ob ein Mitglied von Zeit zu Zeit aufgefordert werden soll, sein Kennwort zu ändern, würde ich das Feld auf den Zeitpunkt festlegen, zu dem das Mitglied ursprünglich erstellt wurde. Die Implementierung von IsPasswordExpired () ist für neue und vorhandene Mitglieder gleich.

class Member{
   private DateTime lastChangePasswordTime;

   public Member(DateTime lastChangePasswordTime) {
      // set value, maybe check for null
   }

   public bool IsPasswordExpired() {
      DateTime limit = DateTime.Now.AddMonths(-3);
      return lastChangePasswordTime < limit;
   }
}

Wenn Sie eine separate Anforderung haben, dass neu erstellte Mitglieder haben, um ihr Kennwort zu aktualisieren, würde ich ein getrenntes boolesches Feld namens passwordShouldBeChanged hinzufügen und es bei der Erstellung auf true setzen. Ich würde dann die Funktionalität der IsPasswordExpired () -Methode ändern, um eine Prüfung für dieses Feld einzuschließen (und die Methode in ShouldChangePassword umbenennen).

class Member{
   private DateTime lastChangePasswordTime;
   private bool passwordShouldBeChanged;

   public Member(DateTime lastChangePasswordTime, bool passwordShouldBeChanged) {
      // set values, maybe check for nulls
   }

   public bool ShouldChangePassword() {
      return PasswordExpired(lastChangePasswordTime) || passwordShouldBeChanged;
   }

   private static bool PasswordExpired(DateTime lastChangePasswordTime) {
      DateTime limit = DateTime.Now.AddMonths(-3);
      return lastChangePasswordTime < limit;
   }
}

Machen Sie Ihre Absichten im Code explizit.

2
Rik D

Erstens sind Nullen, die böse sind, ein Dogma, und wie beim Dogma üblich, funktioniert es am besten als Richtlinie und nicht als Pass/No-Pass-Test.

Zweitens können Sie Ihre Situation so neu definieren, dass es sinnvoll ist, dass der Wert niemals null sein kann. InititialPasswordChanged ist ein Boolescher Wert, der ursprünglich auf false festgelegt wurde. PasswordSetTime ist das Datum und die Uhrzeit, zu der das aktuelle Kennwort festgelegt wurde.

Beachten Sie, dass dies zwar mit geringen Kosten verbunden ist, Sie jedoch jetzt IMMER berechnen können, wie lange es her ist, seit ein Kennwort zuletzt festgelegt wurde.

2
jmoreno

Beide sind "sicher/vernünftig/korrekt", wenn der Anrufer vor der Verwendung prüft. Das Problem ist, was passiert, wenn der Anrufer nicht prüft. Was ist besser, eine Art Nullfehler oder die Verwendung eines ungültigen Werts?

Es gibt keine einzige richtige Antwort. Es hängt davon ab, worüber Sie sich Sorgen machen.

Wenn Abstürze wirklich schlecht sind, die Antwort jedoch nicht kritisch ist oder einen akzeptierten Standardwert hat, ist es möglicherweise besser, einen Booleschen Wert als Flag zu verwenden. Wenn die Verwendung der falschen Antwort ein schlimmeres Problem ist als ein Absturz, ist die Verwendung von null besser.

In den meisten "typischen" Fällen ist ein schneller Fehler und das Erzwingen der Überprüfung durch die Anrufer der schnellste Weg, um vernünftigen Code zu erhalten. Daher denke ich, dass Null die Standardauswahl sein sollte. Ich würde jedoch nicht zu viel Vertrauen in die Evangelisten "X ist die Wurzel aller bösen" setzen. Normalerweise haben sie nicht alle Anwendungsfälle vorweggenommen.

1
ANone