web-dev-qa-db-ja.com

nullが悪である場合、値が有意に存在しない可能性がある場合、何を使用する必要がありますか?

これは何度も繰り返し繰り返されているルールの1つであり、私を困惑させます。

ヌルは悪であり、可能な限り回避する必要があります

しかし、しかし、-私の素朴さから、私は悲鳴を上げましょう-時々価値が意味的に欠けているかもしれません!

この時点で作業中のこのアンチパターンに乗った恐ろしいコード からの例でこれを聞いてみましょう。これは、コアとなるマルチプレイヤーWebターンベースのゲームであり、両方のプレイヤーのターンが同時に実行されます(ポケモンのように、チェスとは反対です)。

各ターンの後、サーバーは更新のリストをクライアント側のJSコードにブロードキャストします。 Updateクラス:

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

このクラスはJSONにシリアル化され、接続されているプレーヤーに送信されます。

ほとんどのアップデートには、当然プレーヤーが関連付けられています。結局のところ、このターンにどのプレーヤーがどのプレーヤーを動かしたかを知る必要があります。ただし、一部の更新には、意味のあるプレーヤーを関連付けることができません。例:アクションのないターン制限を超えたため、ゲームは強制的に引き分けられました。そのようなアップデートについては、プレイヤーを無効にすることは意味があると思います。

もちろん、継承を利用してこのコードを「修正」することもできます。

public class GameUpdate
{
    // stuff
}

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

ただし、次の2つの理由により、これがどのように改善されるかはわかりません。

  • JSコードは、定義されたプロパティとしてPlayerなしのオブジェクトを受け取るだけです。これは、どちらの場合も値が存在するかどうかを確認する必要があるため、nullである場合と同じです。
  • 別のnull可能フィールドをGameUpdateクラスに追加する場合、この設計を続行するには多重継承を使用できる必要があります-しかし、MIはそれ自体が(経験豊富なプログラマーによると)悪であり、さらに重要なことに、C#はそうではありません持ってないので使えません。

私は、このコードのこの部分が、経験豊富で優れたプログラマーが恐怖で悲鳴を上げる非常に多くの1つであることを示唆しています。同時に、私はこの場所で、このnullがどのように何かを傷つけ、代わりに何をすべきかを理解できません。

この問題を説明していただけませんか?

26
gaazkam

多くの場合、nullよりも返す方が適切です。

  • 空の文字列( "")
  • 空のコレクション
  • 「オプション」または「多分」モナド
  • 静かに何もしない機能
  • 静かに何もしないメソッドでいっぱいのオブジェクト
  • 質問の誤った前提を拒否する意味のある値
    (そもそもあなたがnullだと思った理由です)

秘訣は、これをいつ行うべきかを理解することです。ヌルはあなたの顔を爆破するのに本当に優れていますが、あなたがそれをチェックせずにそれをドットで消したときだけです。理由を説明するのは得意ではありません。

爆破する必要がなく、nullをチェックしたくない場合は、次のいずれかの方法を使用してください。要素が0または1しかない場合、コレクションを返すのは奇妙に思えるかもしれませんが、欠けている値を静かに処理できるのでとても便利です。

モナドは奇妙に聞こえますが、ここで彼らがしていることは、コレクションの有効なサイズが0と1であることを明確にすることです。

これらのいずれも、nullよりも意図を明確にするのに優れています。それが最も重要な違いです。

単に例外をスローするのではなく、なぜ戻ってくるのかを理解するのに役立ちます。例外は本質的に、仮定を拒否する方法です(それらが唯一の方法ではありません)。 2本の線が交差する点を尋ねると、2本の線が交差すると想定されます。それらは平行かもしれません。 「平行線」例外をスローする必要がありますか?できますが、今はその例外を別の場所で処理するか、システムを停止させる必要があります。

自分がいる場所に留まりたい場合は、その仮定の拒否を、結果または拒否のいずれかを表すことができる一種の値に焼くことができます。それは奇妙なことではありません。 algebra で常に行います。秘訣は、その値を意味のあるものにして、何が起こったかを理解できるようにすることです。

戻り値が結果と拒否を表すことができる場合は、両方を処理できるコードに送信する必要があります。多態性は、ここで使用するのに本当に強力です。 isPresent() チェックのnullチェックを単にトレードするのではなく、どちらの場合でも適切に動作するコードを記述できます。空の値をデフォルト値に置き換えるか、何も通知せずに何もしません。

Nullの問題は、意味が多すぎることです。つまり、情報が破壊されるだけです。


私のお気に入りの空想実験では、複雑な Rube Goldbergian を想像してみてください。コンベヤーベルトから色のついた電球を取り出し、ソケットにねじ込み、電源を入れることで、色のついた光を使用してエンコードされたメッセージを送信します。アップ。信号は、コンベヤーベルトに配置されている電球のさまざまな色によって制御されます。

メッセージの間にマーカーが必要であると述べているRFC3.14159に準拠して、この恐ろしく高価なものを作成するように求められました。マーカーはまったく光があってはなりません。正確に1つのパルスが続く必要があります。単一の色のライトが通常光っているのと同じ時間。

仕掛けがアラームを発し、ソケットに入れる電球が見つからない場合は停止するため、メッセージの間にスペースを空けることはできません。

このプロジェクトに精通している人なら誰でも、機械や制御コードに触れても身震いします。変更は簡単ではありません。職業はなんですか?さて、あなたは飛び込んで、物事を壊し始めることができます。この恐ろしいデザインを更新するかもしれません。ええ、あなたはそれをもっと良くすることができました。または、管理人に話しかけて、切れた球根の収集を開始することもできます。

それがポリモーフィックなソリューションです。システムは、何が変更されたかを知る必要さえありません。


あなたがそれを取り除くことができればそれは本当にいいです。仮定の意味のある拒否を値にエンコードすることにより、質問を強制的に変更する必要はありません。

りんごを1つあげると、いくつのりんごが借りられますか。 -1。昨日からりんごを2個借りたから。ここで、「あなたは私に借りている」という質問の仮定は、負の数で拒否されます。質問が間違っていたとしても、この回答には意味のある情報がエンコードされています。意味のある方法でそれを拒否することにより、質問は強制的に変更されません。

ただし、実際に質問を変更することが実際の方法です。仮定をより信頼できるものにできる場合は、それでも役立ちますが、そうすることを検討してください。

あなたの問題は、通常、更新はプレイヤーから来るということです。しかし、プレーヤー1またはプレーヤー2以外からの更新の必要性を発見しました。時間切れになったことを示す更新が必要です。どちらのプレイヤーもこれを言っていないので、どうしますか?プレイヤーをnullのままにしておくのはとても魅力的です。しかし、それは情報を破壊します。ブラックホールに知識をエンコードしようとしています。

プレイヤーフィールドは、誰が移動したかはわかりません。あなたはそうだと思いますが、この問題はそれが嘘であることを証明しています。プレーヤーフィールドは、更新がどこから行われたかを示します。期限切れの更新はゲームの時計からのものです。私の推奨は、時計にそれにふさわしいクレジットを与え、playerフィールドの名前をsourceのようなものに変更することです。

このようにして、サーバーがシャットダウンしていてゲームを中断する必要がある場合、何が起こっているのかを報告し、それ自体を更新のソースとしてリストする更新を送信できます。

これは、単に何かを省いてはならないという意味ですか?いいえ。ただし、nothingが単一の既知の適切なデフォルト値を実際に意味すると想定できるかどうかを考慮する必要があります。 Nothingは本当に使いすぎです。使用する際は注意してください。賛成なしを受け入れる思想の学校があります。 convention over configuration と呼ばれます。

私自身もそれが好きですが、大会が何らかの形で明確に伝えられている場合のみです。初心者をかすめる方法であってはなりません。しかし、それを使用している場合でも、nullはまだそれを行うための最良の方法ではありません。 nullは危険ですなし。 Nullは、使用するまですぐに使用できるふりをして、宇宙に穴を開けます(セマンティックモデルを壊します)。宇宙の穴を吹くことが必要な行動でない限り、これをいじるのはなぜですか?他のものを使用してください。

20
candied_orange

nullは、ここでは実際には問題ではありません。問題は、現在のほとんどのプログラミング言語が不足している情報をどのように処理するかです。彼らはこの問題を真剣に受け止めていません(または、ほとんどの地球人が落とし穴を避けるために必要なサポートと安全性を少なくとも提供していません)。これは、私たちが恐れることを学んだすべての問題(Javas NullPointerException、Segfaultsなど)につながります。

その問題をより適切に処理する方法を見てみましょう。

他の人たちは Null-Object Pattern を提案しました。私はそれが時々適用可能であると思いますが、万能のデフォルト値がないだけの多くのシナリオがあります。そのような場合、存在しない可能性のある情報の消費者は、その情報が欠落している場合の対処法を自分で決定できる必要があります。

コンパイル時にnullポインターに対する安全性(いわゆるvoid安全性)を提供するプログラミング言語があります。いくつかの現代的な例を挙げましょう:Kotlin、Rust、Swift。これらの言語では、情報の欠落の問題は真剣に受け止められており、nullを使用してもまったく問題はありません。コンパイラーがnullポインターを逆参照するのを防ぎます。
Javaでは、@ Nullable/@ NotNullアノテーションをコンパイラ+ IDEプラグインと一緒に確立して、同じ効果を得る努力が行われています。それは本当に成功しなかったが、それはこの答えの範囲外です。

不在の可能性を示す明示的なジェネリック型は、それを処理するもう1つの方法です。例えば。 Java Java.util.Optionalがあります。それは機能します:
プロジェクトまたは会社レベルで、ルール/ガイドラインを確立できます

  • 高品質のサードパーティライブラリのみを使用する
  • alwaysnullの代わりにJava.util.Optionalを使用します

言語コミュニティ/エコシステムは、コンパイラ/インタプリタよりもこの問題に対処する他の方法を考え出したかもしれません。

15
marstato

Nullが悪いという記述にはメリットはありません。私はそれについての議論をチェックしました、そして、彼らは私を焦らせてイライラさせます。

Nullは、実際に何かを意味する場合、「マジックバリュー」として誤って使用される可能性があります。しかし、そこにたどり着くには、かなりひねったプログラミングを行う必要があります。

Nullは、「存在しない」ことを意味する必要があります。引数である場合、作成されていないか、(すでに)存在していないか、提供されていません。状態ではなく、全部抜けています。 OO設定では、物事は常に作成および破棄され、どの状態とも異なる、有効で意味のある状態です。

Nullを使用すると、実行時にスマックされ、コンパイル時になんらかのタイプセーフなnullの代替手段が用意されます。

完全に真実ではありませんが、変数を使用する前にnullをチェックしないという警告が表示されます。そして、それは同じではありません。目的のないオブジェクトのリソースを節約したくない場合があります。そしてもっと重要なこととして、私は望ましい動作を得るために、まったく同じ代替案をチェックする必要があります。そのことを忘れた場合、未定義/意図しない動作ではなく、実行時にNullReferenceExceptionが発生します。

注意すべき問題が1つあります。マルチスレッドのコンテキストでは、nullのチェックを実行する前に、まずローカル変数に割り当てることにより、競合状態から保護する必要があります。

Nullの問題は、多くのことを意味します。つまり、情報が破壊されるだけです。

いいえ、それは何かを意味する/すべきではないので、破壊するものは何もありません。変数/引数の名前と型は常に何が欠けているかを教えてくれます。

編集:

私は関数型プログラミングに関する彼の他の回答の1つにリンクされている video candied_orangeの途中にあり、非常に興味深いものです。特定のシナリオでその有用性を確認できます。私がこれまで見てきた機能的なアプローチは、コードの断片が飛び交うため、OOの設定で使用すると、通常、私を不快にさせます。しかし、その場所があると思います。混乱を招く混乱だと私がC#で遭遇するたびに混乱を隠す良い仕事をする言語があります。代わりに、クリーンで実行可能なモデルを提供する言語です。

その機能モデルがマインドセット/フレームワークである場合、文書化されたエラーの説明として事故を押し進める以外に選択肢はなく、最終的に、発信者は開始点までドラッグされた蓄積されたガベージを処理できます。どうやらこれは関数型プログラミングのしくみです。それを制限と呼び、それを新しいパラダイムと呼びます。機能的な弟子たちが、不浄な道を少し先に進むことを可能にするハックであると考えるかもしれません。この新しいプログラミング方法は、刺激的な新しい可能性を解き放ちます。何であれ、その世界に足を踏み入れて、伝統的なOO方法を実行する方法に(はい、私たちは例外をスローすることができ、申し訳ありません)に戻って、そこからいくつかの概念を選び、プルすることは私には無意味だと思いますそれはあなたの最近発見された新しい世界に合わないので、それは文脈から外れ、悲鳴を上げる青い殺人です。

どの概念も間違った方法で使用できます。私が立っているところから、提示された「解決策」はnullの適切な使用のための代替手段ではありません。彼らは別の世界からの概念です。

10
Martin Maat

あなたの質問。

しかし、しかし、-私の素朴さから、私は悲鳴を上げましょう-時々価値が意味的に欠けているかもしれません!

あなたと一緒に完全に乗り込みます。ただし、すぐにいくつかの注意点があります。でもまず:

ヌルは悪であり、可能な限り回避する必要があります。

これはよく意図された、しかし一般化された声明だと思います。

ヌルは悪ではありません。それらはアンチパターンではありません。それらは絶対に避けられるべきものではありません。ただし、nullはerrorになりやすい(nullのチェックに失敗するため)。

しかし、私はnullのチェックに失敗したことが、nullが本質的に間違って使用されていることの証拠であることを理解していません。

Nullが悪ければ、配列インデックスとC++ポインターも同様です。そうではありません。彼らは簡単に自分の足で撃つことができますが、すべてのコストで避けられるべきではありません。

この回答の残りの部分では、「nullは悪である」という意図を、より微妙な「nullを責任を持って処理する必要がある」に適合させます。


あなたのソリューション。

グローバルルールとしてのnullの使用に関するあなたの意見に同意しますが、この特定のケースでのnullの使用に同意しません。

以下のフィードバックをまとめると、継承とポリモーフィズムの目的を誤解しています。 nullを使用するという主張には妥当性がありますが、他の方法が悪い理由のさらなる「証拠」は、継承の誤用に基づいており、ポイントをnullの問題とは無関係にしています。

ほとんどのアップデートには、当然プレーヤーが関連付けられています。結局のところ、このターンにどのプレーヤーがどのプレーヤーを動かしたかを知る必要があります。ただし、一部の更新には、意味のあるプレーヤーを関連付けることができません。

これらの更新が有意に異なる(異なる)場合は、2つの異なる更新タイプが必要です。

たとえば、メッセージについて考えてみましょう。エラーメッセージとIMチャットメッセージがあります。ただし、非常によく似たデータ(文字列メッセージと送信者)が含まれている場合がありますが、同じようには動作しません。これらは、異なるクラスとして別々に使用する必要があります。

現在実行しているのは、Playerプロパティのnull値に基づく2種類の更新を効果的に区別することです。これは、エラーコードの存在に基づいてIMメッセージとエラーメッセージを決定することと同じです。機能しますが、最善の方法ではありません。

  • JSコードは、定義されたプロパティとしてPlayerなしのオブジェクトを受け取るだけです。これは、どちらの場合も値が存在するかどうかを確認する必要があるため、nullである場合と同じです。

あなたの議論は多態性の誤解に依存しています。 GameUpdateオブジェクトを返すメソッドがある場合、通常、GameUpdatePlayerGameUpdate、またはNewlyDevelopedGameUpdateを区別する必要はありません。

基本クラスには完全に機能するコントラクトが必要です。つまり、そのプロパティは派生クラスではなく基本クラスで定義されます(つまり、これらの基本プロパティのvalueはもちろん、派生クラスでオーバーライドされます)。

これはあなたの議論を根拠のないものにします。適切な方法では、GameUpdateオブジェクトにプレーヤーが含まれているかどうかを気にする必要はありません。 PlayerGameUpdateプロパティにアクセスしようとしている場合は、不合理なことを行っています。

C#などの型付き言語では、PlayerGameUpdateプロパティにアクセスしてを許可しないため、 GameUpdateにはプロパティがありません。 JavaScriptは、そのアプローチがかなり緩い(そして、プリコンパイルを必要としない)ので、修正する必要がなく、代わりに実行時に爆破されます。

  • 別のnull可能フィールドをGameUpdateクラスに追加する場合、この設計を続行するには多重継承を使用できる必要があります-しかし、MIはそれ自体が(経験豊富なプログラマーによると)悪であり、さらに重要なことに、C#はそうではありません持ってないので使えません。

Null許容プロパティの追加に基づいてクラスを作成するべきではありません。 機能的にユニークなタイプのゲームアップデートに基づいてクラスを作成する必要があります。


一般的にnullの問題を回避するにはどうすればよいですか?

価値の欠如を有意義に表現する方法を詳しく説明することは適切だと思います。これは、順不同の解決策(または悪いアプローチ)のコレクションです。

1。とにかくnullを使用します。

これが最も簡単な方法です。ただし、大量のnullチェックにサインアップし、失敗した場合はnull参照例外のトラブルシューティングを行います。

2。 null以外の意味のない値を使用する

ここでの簡単な例はindexOf()です:

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

nullの代わりに、-1。技術レベルでは、これは有効な整数値です。ただし、インデックスは正の整数であることが期待されるため、負の値は無意味な回答です。

論理的には、これは、戻り値の型が意味のある戻り値よりも多くの可能な値を許容する場合にのみ使用できます(indexOf =正の整数; int =負および正の整数)

参照タイプの場合も、この原則を適用できます。たとえば、整数のIDを持つエンティティがある場合、nullではなく、IDが-1に設定されたダミーオブジェクトを返すことができます。
オブジェクトが実際に使用可能かどうかを測定できる「ダミー状態」を定義するだけです。

文字列値を制御しない場合は、事前に定義された文字列値に依存しないでください。空のオブジェクトを返す場合は、ユーザー名を「null」に設定することを検討してください ただし、Christopher Nullがサービスにサインアップすると問題が発生します

3。代わりに例外をスローします。

いいえ、違います。論理フローの場合は例外を処理しないでください。

そうは言っても、(nullreference)例外を非表示にせず、適切な例外を表示したい場合があります。例えば:

var existingPerson = personRepository.GetById(123);

これは、ID 123の人物がいない場合にPersonNotFoundException(または類似の)をスローする可能性があります。

明らかに、これは、アイテムが見つからないためにそれ以上の処理ができない場合にのみ許容されます。アイテムを見つけることができず、アプリケーションを続行できる場合は、例外をスローしないでください。私はこれを十分に強調することはできません。

7
Flater

Nullが悪い理由は、欠損値がnullとしてエンコードされることではなく、参照値がnullになる可能性があることです。 C#を使用している場合、おそらくいずれにせよ失われます。参照タイプはすでにオプションであるため、そこでroがいくつかのOptionalを使用しているのがわかりません。しかし、Javaには@Notnull注釈があります パッケージレベルで設定できるようです の場合、欠落する可能性のある値には注釈@を使用できますヌル可能。

5
max630

Nullは悪ではありません。ある時点で、Nullのように見えるものを処理することなく、代替手段を実装する方法はありません。

ただし、ヌルは危険です。他の低レベルの構成要素と同じように、もしあなたがそれを間違えたとしても、あなたを捕まえるための以下のものは何もありません。

Nullに対して有効な引数がたくさんあると思います:

  • すでに高レベルドメインにいる場合は、低レベルモデルを使用するよりも安全なパターンがあります。

  • 低レベルのシステムの利点が必要で、適切な注意が必要な場合を除き、低レベルの言語を使用するべきではありません。

  • etc ...

これらはすべて有効であり、ゲッターやセッターなどを書く手間を省く必要がないことが、そのアドバイスに従わなかった一般的な理由と見なされています。多くのプログラマーがnullは危険で怠惰(悪い組み合わせ)であるという結論に達したことはそれほど驚くべきことではありませんが、他の多くの「普遍的な」アドバイスのように、彼らは言葉ほど普遍的ではありません。

言語を書いた善良な人々は、彼らが誰かのために役立つと思いました。あなたが異論を理解し、それでもあなたが彼らにとって正しいと思うなら、他の誰もよりよく知ることはありません。

1
drjpizzle

車。ホアレは彼の「10億ドルの間違い」を無効にした。ただし、彼が解決策は「どこにもnullなし」ではなく、「デフォルトとしてnull不可のポインタであり、意味的に必要な場合にのみnull化する」と彼が考えたということに注意することが重要です。

彼の間違いを繰り返す言語におけるnullの問題は、関数がnull可能ではないデータを期待しているとは言えないことです。それは基本的に言語では表現不可能です。これにより、関数は無用な無関係なnull処理の定型文をたくさん含むようになり、nullチェックを忘れることがバグの一般的な原因になります。

表現力の根本的な欠如をハックするために、一部のコミュニティ(たとえば、Scalaコミュニティ)はnullを使用しません。Scalaでは、T型のnull許容値が必要な場合、 _Option[T]_型の引数を受け取り、null不可の値が必要な場合は、Tを受け取ります。誰かがコードにnullを渡すと、コードが爆発します。ただし、呼び出しコードはバグのあるコードであると見なされます。Optionは、nullの代替として非常に優れています。これは、一般的なnull処理パターンが.map(f)または.getOrElse(someDefault)

0
user311781

Javaには、明示的にOptional +ラムダを含むifPresentクラスがあり、任意の値に安全にアクセスできます。これはJSでも行うことができます:

このStackOverflowの質問 を参照してください。

ちなみに、多くの純粋主義者はこの使用法を疑わしいと思いますが、私はそうは思いません。オプション機能は存在します。

0
Joop Eggen