web-dev-qa-db-ja.com

値が実質的に存在しない場合、新しいブール値フィールドはnull参照よりも優れていますか?

たとえば、lastChangePasswordTimeを持つクラスMemberがあるとします。

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

一部のメンバーはパスワードを変更できないため、lastChangePasswordTimeは意味がない場合があります。

しかし、nullが悪である場合、値が実質的に存在しない場合に何を使用する必要がありますか?およびhttps://softwareengineering.stackexchange.com/a/12836/248528、nullを使用して意味のない値を表すことはできません。だから私はブールフラグを追加しようとします:

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

しかし、私はそれがかなり時代遅れだと思います:

  1. IsPasswordChangedがfalseの場合、lastChangePasswordTimeはnullでなければならず、lastChangePasswordTime == nullの確認はisPasswordChangedがfalseの確認とほぼ同じであるため、lastChangePasswordTime == nullを直接確認することをお勧めします。

  2. ここでロジックを変更すると、bothフィールドの更新を忘れる可能性があります。

注:ユーザーがパスワードを変更すると、次のように時間を記録します。

this.lastChangePasswordTime=Date.now();

追加のブール値フィールドは、ここでnull参照よりも優れていますか?

39
ocomfd

どうしてかわかりません。もしあなたが意味のない値を持っているなら、それについて慎重で注意深いならnullは使われるべきではありません。

Null値を誤って参照しないようにするためにnull許容値を囲むことが目的である場合は、nullチェックの結果を返す関数またはプロパティとしてisPasswordChanged値を作成することをお勧めします。次に例を示します。

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

}

私の意見では、これを次のようにしています:

  • コンテキストが失われる可能性があるnullチェックよりもコードの可読性が向上します。
  • あなたが言及するisPasswordChanged値を維持することを実際に心配する必要がないようにします。

データを(おそらくデータベースに)保持する方法は、nullが確実に保持されるようにする責任があります。

71
TZHX

nullsは悪ではありません。考えずに使うのです。これは、nullが正解である場合です。日付はありません。

ソリューションによってさらに問題が発生することに注意してください。日付が何かに設定されている意味は何ですか、isPasswordChangedはfalseですか? null値には明確に定義された明確な意味があり、他の情報と競合することはありませんが、特別にキャッチして処理する必要がある競合する情報のケースを作成しました。

だから、いいえ、あなたのソリューションは良くありません。 null値を許可することは、ここでの正しいアプローチです。 nullが存在する理由がコンテキストに理解されていなくても、nullは常に邪悪であると主張する人々。

63
Tom

プログラミング言語によっては、optionalデータ型などの適切な代替手段がある場合があります。 C++(17)では、これは std :: optional になります。存在しないか、基になるデータ型の任意の値を持つことができます。

29
dasmy

Nullを正しく使用する

nullの使用方法はいくつかあります。最も一般的で意味的に正しい方法は、single値がある場合とない場合に使用することです。この場合、値はnullに等しいか、データベースのレコードなどの意味のあるものです。

これらの状況では、ほとんどの場合、(擬似コードで)次のように使用します。

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

doSomethingUseful(value);
_

問題

そして、それは非常に大きな問題を抱えています。問題は、doSomethingUsefulを呼び出すまでに、値がnullに対してチェックされていない可能性があることです。そうでなければ、プログラムはおそらくクラッシュします。また、ユーザーには「恐ろしいエラー:値が必要ですがnullになりました!」などのエラーメッセージが表示されない場合もあります。 (更新後:_Segmentation fault. Core dumped._などの有益なエラーはさらに少なくなる可能性がありますが、さらに悪いことに、エラーが発生せず、場合によってはnullが正しく処理されません)

nullのチェックを記述し、nullの状況を処理するのを忘れることは、非常に一般的なバグです。これが、nullを発明したTony Hoareが2009年にQCon Londonと呼ばれるソフトウェア会議で1965年に10億ドルの間違いを犯したと述べた理由です https://www.infoq.com/presentations/ Null-References-The-Billion-Dollar-Mistake-Tony-Hoare

問題を回避する

一部のテクノロジーと言語では、nullのチェックをさまざまな方法で忘れることができなくなり、バグの量が減少します。

たとえば、Haskellにはnullの代わりにMaybeモナドがあります。 DatabaseRecordがユーザー定義型であるとします。 Haskellでは、タイプ_Maybe DatabaseRecord_の値は_Just <somevalue>_と等しいか、またはNothingと等しい場合があります。その後、さまざまな方法で使用できますが、どのように使用しても、知らないうちにNothingに操作を適用することはできません。

たとえば、zeroAsDefaultと呼ばれるこの関数は、_Just x_の場合はxを、Nothingの場合は_0_を返します。

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

Christian HacklはC++ 17とScalaは独自の方法を持っていると言います。そのため、あなたの言語がそのようなものを持っているかどうかを調べてそれを使用したいと思うかもしれません。

ヌルはまだ広く使用されています

何もない場合は、nullを使用することをお勧めします。気をつけてください。関数の型宣言は、とにかくいくらか役立ちます。

また、それほど進歩的ではないように聞こえるかもしれませんが、同僚がnullなどを使用したいかどうかを確認する必要があります。それらは保守的であり、何らかの理由で新しいデータ構造を使用したくない場合があります。たとえば、古いバージョンの言語をサポートしています。このようなことは、プロジェクトのコーディング標準で宣言し、チームと適切に話し合う必要があります。

あなたの提案について

別のブールフィールドを使用することをお勧めします。しかし、とにかくそれをチェックする必要があり、それでもチェックするのを忘れる可能性があります。だからここで勝ったものはありません。毎回両方の値を更新するなど、他のことを忘れることさえできれば、それはさらに悪いことです。 nullのチェックを忘れる問題が解決しない場合は、意味がありません。 nullを回避することは困難であり、悪化させるような方法で回避することはできません。

Nullを使用しない方法

最後に、nullを誤って使用する一般的な方法があります。そのような方法の1つは、配列や文字列などの空のデータ構造の代わりに使用することです。空の配列は、他の配列と同様に適切な配列です!複数の値を収めることができる、つまり空にすることができる、つまり長さが0であることがデータ構造にとってほとんど常に重要かつ有用です。

代数の観点からは、文字列の空の文字列は数値の0に非常に似ています。つまり、identity:

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

空の文字列を使用すると、一般的に文字列をモノイドにすることができます。 https://en.wikipedia.org/wiki/Monoid 取得できない場合は、それほど重要ではありません。

次に、この例でプログラミングすることが重要である理由を見てみましょう。

_for (element in array) {
  doSomething(element);
}
_

ここで空の配列を渡すと、コードは正常に動作します。何もしません。ただし、ここでnullを渡すと、「nullをループできません、申し訳ありません」などのエラーでクラッシュする可能性があります。 ifでラップすることもできますが、これはあまりクリーンではありません。あなたはチェックするのを忘れているかもしれません

Nullの処理方法

doSomethingAboutIt()がすべきこと、特に例外をスローすべきかどうかは、別の複雑な問題です。要するに、nullが特定のタスクの受け入れ可能な入力値であったかどうか、および応答で何が期待されるかによって異なります。例外は、予期されなかったイベントです。私はそのトピックにはこれ以上触れません。この答えは非常に長いです。

11
Gherman

以前に与えられた非常に良い答えのすべてに加えて、フィールドをnullにしようとするたびに、リストタイプであるかどうか慎重に検討します。 null許容型は、同等に0または1要素のリストであり、多くの場合、これはN要素のリストに一般化できます。特にこの場合、lastChangePasswordTimepasswordChangeTimesのリストと見なすことができます。

4
Joel

これを自問してみてください。どの動作にフィールドlastChangePasswordTimeが必要ですか?

メソッドIsPasswordExpired()にそのフィールドが必要で、メンバーにパスワードの変更を頻繁に要求するかどうかを判断する必要がある場合は、メンバーを最初に作成した時間にフィールドを設定します。 IsPasswordExpired()の実装は、新規メンバーと既存のメンバーで同じです。

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;
   }
}

新しく作成されたメンバーhaveがパスワードを更新するという別の要件がある場合は、passwordShouldBeChangedと呼ばれる別のブールフィールドを追加し、作成時にtrueに設定します。次に、IsPasswordExpired()メソッドの機能を変更して、そのフィールドのチェックを含めます(メソッドの名前をShouldChangePasswordに変更します)。

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;
   }
}

コードで意図を明示します。

2
Rik D

まず、ヌルが悪であることはドグマであり、ドグマではいつものように、合格/不合格テストとしてではなく、ガイドラインとして最適に機能します。

第2に、値をnullにできないようにすることで、状況を再定義できます。 InititialPasswordChangedはブール値で、最初はfalseに設定されています。PasswordSetTimeは、現在のパスワードが設定された日時です。

これには多少のコストがかかりますが、パスワードを最後に設定してからの経過時間をいつでも計算できることに注意してください。

2
jmoreno

呼び出し元が使用前にチェックする場合、どちらも「安全/正気/正しい」です。問題は、発信者がチェックしないとどうなるかです。 nullエラーのいくつかのフレーバーまたは無効な値の使用はどちらが良いですか?

正解は1つではありません。それはあなたが心配していることに依存します。

クラッシュが本当に悪いが、答えが重要ではないか、受け入れられるデフォルト値がある場合は、フラグとしてブール値を使用することをお勧めします。間違った答えを使用することがクラッシュよりも悪い問題である場合は、nullを使用することをお勧めします。

「典型的な」ケースの大部分では、高速な失敗と呼び出し側にチェックを強制することは、コードを正気にするための最速の方法です。 「Xはすべての悪の根である」伝道者をあまり信用しません。彼らは通常、すべてのユースケースを想定していません。

1
ANone