値が有意に欠落している可能性がある場合、null参照よりも新しいブールフィールドの方が良いですか?


39

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

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. ここでロジックを変更すると、両方のフィールドの更新を忘れることがあります。

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

this.lastChangePasswordTime=Date.now();

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


51
優れたソリューションを構成するものは、プログラミング言語が提供するものに大きく依存するため、この質問は言語固有のものです。たとえば、C ++ 17またはScalaでは、std::optionalまたはを使用しますOption。他の言語では、適切なメカニズムを自分で構築する必要がある場合があります。または、nullより慣用的であるため、実際に頼るか、またはそれに似た何かをする場合があります。
クリスチャンハック

16
lastChangePasswordTimeをパスワード作成時間に設定したくない理由はありますか(結局、作成はmodです)?
クリスチャンH

@ChristianHacklうーん、別の「完璧な」解決策があることに同意しますが、個別のブール値を使用する方がnull / nilチェックを行うよりも良いアイデアになるでしょうが、(主要な)言語は表示されません。ただし、C / C ++については、私はしばらくアクティブになっていないので、完全にはわかりません。
フランクホプキンス

@FrankHopkins:例は、CやC ++など、変数を初期化せずに残すことができる言語です。lastChangePasswordTimeそこには初期化されていないポインタがあり、それを何かと比較するのは未定義の動作です。NULL/ へのポインターを初期化しないnullptr特に現代のC ++(ポインターをまったく使用しない場合)では初期化しない、本当に説得力のある理由ではありませんが、誰が知っていますか?別の例としては、ポインターのない言語、またはポインターのサポートが不十分な言語が考えられます。(FORTRAN 77が思い浮かぶ...)
クリスチャンハック

このために3つのケースで列挙型を使用します:)
J. Doe

回答:


72

もしあなたが有意義な価値を欠いているnullなら、あなたがそれを意図的で注意深くしているなら、なぜ使われるべきではないのか分かりません。

誤って参照することを防ぐためにnull値を囲むことを目標とする場合isPasswordChangedは、nullチェックの結果を返す関数またはプロパティとして値を作成することをお勧めします。次に例を示します。

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

}

私の意見では、この方法でそれを行う:

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

データを(おそらくデータベースに)永続化する方法は、nullが保持されることを保証する責任があります。


29
オープニングのために+1。nullのWantonの使用は一般に、nullが予期しない結果である場合、つまり(消費者にとって)意味をなさない場合にのみ不承認となります。nullに意味がある場合、それは予期しない結果ではありません。
フラット

28
同じ状態を維持する2つの変数がないため、もう少し強くします。失敗します。null値をインスタンス内で意味があると仮定して、外部からnull値を非表示にすることは非常に良いことです。この場合、後でパスワードを変更したことがないことを1970-01-01が示すと判断できる場合。その後、プログラムの残りのロジックは気にする必要はありません。
ベント

3
isPasswordChangednullかどうかを確認するのを忘れることができるのと同じように、呼び出すのを忘れることができます。ここでは何も得られません。
ガーマン

9
@Gherman nullが意味がある、または存在する値を確認する必要があるという概念を消費者が受け取らない場合、彼はどちらの方法でも失われますが、メソッドが使用される場合、コードを読んでいるすべての人がそこにいる理由は明らかですはチェックであり、値がnull /存在しない可能性が十分にあることです。それ以外の場合、開発者が単に「なぜ」という理由でnullチェックを追加しただけなのか、それがコンセプトの一部であるのかは不明です。確かに、1つを見つけることはできますが、情報を直接入手する方法と実装を検討する必要がある方法があります。
フランクホプキンス

2
@FrankHopkins:良い点ですが、並行性が使用される場合、単一の変数の検査でもロックが必要になる場合があります。
クリスチャンハックル

63

nulls悪ではありません。考えずにそれらを使用することです。これはnullまさに正解である場合です-日付はありません。

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

だから、あなたのソリューションは良くありません。nullここでは、値を許可することが適切なアプローチです。null文脈に関係なく常に悪であると主張する人々は、なぜnull存在するのか理解していません。


17
歴史的には、null1965年にTony Hoareが10億ドルの過ちを犯したために存在します。null「悪」であると主張する人々は、もちろんトピックを単純化しすぎていますが、現代の言語やプログラミングスタイルがnull、専用のオプションタイプがあります。
クリスチャンハックル

9
@ChristianHackl:歴史的な興味から-ホアはこれまでより良い実用的な代替案が何であったかを言います(まったく新しいパラダイムに切り替えることなく)?
AnoE

5
@AnoE:興味深い質問です。Hoareがそれについて何を言っていたのかは知らないが、最近では、よく設計された現代のプログラミング言語では、nullのないプログラミングが現実になっていることが多い。
クリスチャンハックル

10
@トムワット?C#やJavaなどの言語では、DateTimeフィールドはポインターです。の概念全体nullは、ポインタに対してのみ意味があります!
トッドシーウェル

16
@Tom:Nullポインターは、結果としてどんな陽気であっても、盲目的にインデックス付けおよび逆参照できる言語および実装でのみ危険です。ヌルポインターのインデックス付けまたは逆参照の試行をトラップする言語では、ダミーオブジェクトへのポインターよりも危険性が少ないことがよくあります。プログラムを異常終了させるのは、無意味な数字を生成するよりも望ましい場合が多く、それらをトラップするシステムでは、nullポインターがそのための正しいレシピです。
スーパーキャット

29

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


8
ありますOptional、あまりにもJavaで。ヌルの意味Optionalあなたまでで...
マシューM.

1
オプションで接続するためにここに来ました。私はそれらを持っている言語の型としてこれをエンコードしたいと思います。
ジップ

2
@FrankHopkins Javaで書くことを妨げるものは何もないのは残念ですが、それだけでgiveめOptional<Date> lastPasswordChangeTime = null;ません。代わりに、オプションの値を割り当てることが許可されないという非常に堅固なチームルールを浸透させますnull。オプションでは、あまりにも多くの便利な機能を購入するので、簡単にそれを放棄できません。することができます簡単に割り当てたデフォルト値(orElseまたは遅延評価と:orElseGet)、スローエラー(orElseThrow)、(ヌル安全な方法で値を変換するmapflatmapなど、)
アレクサンダー

5
@FrankHopkins Checking isPresentは、私の経験ではほとんど常にコードの匂いであり、これらのオプションを使用している開発者が「ポイントを逃している」というかなり信頼できる兆候です。確かに、それは基本的にを超える利点はref == nullありませんが、頻繁に使用するべきものでもありません。チームが不安定な場合でも、ほとんどの場合をキャッチできるlinterやFindBugsのように、静的分析ツールで法律を定めることができます。
アレクサンダー

5
@FrankHopkins:Javaコミュニティには少しパラダイムシフトが必要なだけかもしれませんが、これにはまだ何年もかかるかもしれません。あなたはScalaのを見れば、例えば、それがサポートしているnull言語はJavaへの100%互換性があるため、誰もそれを使用していない、とOption[T]のような優れた機能的なプログラミングをサポートして、場所の上にすべてをあるmapflatMap行くように、パターンマッチングと、遠く、遠くを超えた== nullかどうかをチェックします。理論的には、オプションタイプは栄光のポインタにすぎないように聞こえますが、現実はその議論が間違っていることを証明しています。
クリスチャンハック

11

正しくnullを使用する

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

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

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

doSomethingUseful(value);

問題

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

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

問題を回避する

一部のテクノロジーと言語nullでは、さまざまな方法で忘れることをチェックできないようにして、バグの量を減らしています。

たとえば、Haskell Maybeにはヌルではなくモナドがあります。これDatabaseRecordがユーザー定義型であるとします。Haskellでは、typeの値はMaybe DatabaseRecord等しいJust <somevalue>か、等しい場合がありますNothing。その後、さまざまな方法で使用できますが、どのように使用してNothingも、知らないうちに何らかの操作を適用することはできません。

たとえば、呼び出されたこの関数zeroAsDefaultxfor Just xおよび0forを返しますNothing

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に非常に似ています。

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

空の文字列はモノイドになるため、一般的に文字列を可能にします: https://en.wikipedia.org/wiki/Monoid あなたはそれを取得しない場合、それはあなたのためにそれは重要ではありません。

この例でプログラミングがなぜ重要なのかを見てみましょう。

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

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

nullの処理方法

何をdoSomethingAboutIt()すべきか、特に例外をスローすべきかどうかは別の複雑な問題です。要するに、それはnull与えられたタスクの受け入れ可能な入力値であったかどうか、そして応答で期待されるものに依存します。例外は、予期されていなかったイベントです。そのトピックについては詳しく説明しません。この答えはすでに非常に長いです。


5
実際には、horrible error: wanted value but got null!より良い、より一般的なよりSegmentation fault. Core dumped....
トビースパイツ

2
@TobySpeight本当ですが、のようなユーザーの用語の中にあるものを好むでしょうError: the product you want to buy is out of stock
ガーマン

確かに、私はそれがさらに悪化する可能性がある(そしてしばしばそれが悪化する)ことを観察していまし。私は何が良いかについて完全に同意します!そして、あなたの答えは、この質問に対して私が言ったこととほぼ同じです:+1。
トビースパイト

2
@Gherman:許可されていないnull場所を渡すnullことはプログラミングエラー、つまりコードのバグです。在庫切れの製品は、通常のビジネスロジックの一部であり、バグではありません。バグの検出をユーザーインターフェイスの通常のメッセージに変換することはできません。特に重要なアプリケーション(実際の金融取引を実行するアプリケーションなど)の場合は、すぐにクラッシュし、それ以上のコードを実行しないことをお勧めします。
クリスチャンハックル

1
@EricDuminil間違いを犯す可能性をnull完全に削除するのではなく、バックグラウンドから削除する(または減らす)必要があります。
ガーマン

4

これまでに挙げた非常に良い答えに加えて、フィールドをnullにしようとするたびに、それがリスト型であるかどうかを慎重に考えてください。nullable型は、同等に0または1要素のリストであり、多くの場合、これはN要素のリストに一般化できます。特にこの場合、lastChangePasswordTimeのリストであることを検討する必要がありpasswordChangeTimesます。


それは素晴らしい点です。多くの場合、オプションタイプについても同様です。オプションは、シーケンスの特殊なケースと見なすことができます。
クリスチャンハック

2

これを自問してください: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;
   }
}

新しく作成したメンバーパスワードを更新する必要があるという別の要件がある場合は、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

第一に、悪であるヌルはドグマであり、ドグマでは通常通り、合格/不合格テストとしてではなく、ガイドラインとして最適に機能します。

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

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


1

呼び出し元が使用前にチェックする場合、両方とも「安全/健全/正しい」です。問題は、発信者がチェックしない場合に起こることです。どちらが良いですか、いくつかの種類のヌルエラーまたは無効な値を使用していますか?

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

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

ほとんどの「典型的な」ケースでは、高速な障害と呼び出し側による強制的なチェックがコードを正気にするための最速の方法であるため、nullがデフォルトの選択であると考えています。ただし、「Xはすべての悪の根源」の伝道者にはあまり信頼しません。通常、すべてのユースケースを想定していません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.