null参照は本当に悪いことですか?


161

プログラミング言語にnull参照を含めることは「10億ドルの間違い」だと言ったと聞きました。しかし、なぜ?確かに、それらはNullReferenceExceptionsを引き起こす可能性がありますが、それではどうでしょうか?不適切に使用すると、言語の要素がエラーの原因になる可能性があります。

そして、代替手段は何ですか?私はこれを言う代わりに:

Customer c = Customer.GetByLastName("Goodman"); // returns null if not found
if (c != null)
{
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

あなたはこれを言うことができます:

if (Customer.ExistsWithLastName("Goodman"))
{
    Customer c = Customer.GetByLastName("Goodman") // throws error if not found
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!"); 
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

しかし、それはどのように優れていますか?いずれにしても、顧客の存在を確認するのを忘れると、例外が発生します。

CustomerNotFoundExceptionは、説明的であるため、NullReferenceExceptionよりもデバッグが少し簡単だと思います。それだけです。


54
「顧客が存在する場合/次に顧客を取得する」アプローチには注意が必要です。このような2行のコードを記述するとすぐに、競合状態の扉が開かれます。取得してから、null /キャッチ例外をチェックする方が安全です。
カーソン63000

26
すべてのランタイムエラーの原因を排除できれば、コードは完璧であることが保証されます。C#のNULL参照があなたの脳を破壊するなら、Cで無効だが、非NULLのポインターを試してください;)
LnxPrgr3

ただし、一部の言語にはヌル合体および安全なナビゲーション演算子があります
jk。

35
null自体は悪くありません。型Tと型T + nullの間に違いがない型システムは悪いです。
インゴ14年

27
数年後、私自身の質問に戻って、私は完全にOption / Maybeアプローチの転換者になりました。
ティムグッドマン

回答:


91

ヌルは悪

このトピックに関するInfoQのプレゼンテーションがあります:Null References:The Billion Dollar Mistake by Tony Hoare

オプションタイプ

関数型プログラミングの代替は、使用しているオプションのタイプ含めることができ、SOME valueまたはNONE

Option型について説明し、Javaの実装を提供する優れた記事「Option」パターン

また、この問題に関するJavaのバグレポートを見つけました。NullPointerExceptionsを防ぐために、JavaにNice Optionタイプを追加します要求された機能は、 Javaの8で導入されました。


5
C#のNullable <x>も同様の概念ですが、オプションパターンの実装には程遠いです。主な理由は、.NET System.Nullableでは値の型に制限されていることです。
MattDavey

27
+1オプションタイプの場合。Haskellのに慣れて取得した後多分、ヌルは奇妙な探し始める...
アンドレスF.

5
開発者はどこでもエラーを起こすことができるため、nullポインター例外の代わりに空オプション例外が発生します。前者よりも後者の方が優れているのでしょうか?
greenoldman

7
@greenoldman「空のオプション例外」のようなものはありません。NOT NULLとして定義されたデータベース列にNULL値を挿入してみてください。
ジョナス

36
@greenoldman nullの問題は、何でも nullになる可能性があることであり、開発者としては特に注意する必要があります。Stringタイプではありません、本当に文字列。それはだString or Nullタイプ。オプション型の概念に完全に準拠している言語では、型システムでnull値が許可されないため、String(または他の)を必要とする型シグニチャを定義する場合は値が必要です。Optionタイプは、物事のタイプレベルの表現与えることがあるか、ないかもしれ値を持っているが、あなたは、あなたが明示的な状況に対処しなければならない場所を知っています。
KChaloux 14年

127

問題は、理論上、オブジェクトはnullになり、使用しようとすると例外を投げることができるため、オブジェクト指向のコードは基本的に不発弾のコレクションであるということです。

正常なエラー処理は、nullチェックifステートメントと機能的に同じである可能性があります。しかし、あなたが自分がヌルである可能性がないと確信したものが、実際にはヌルである場合、どうなりますか?ケルビム。次に何が起こっても、私は1)それは優雅でなく、2)あなたはそれを気に入らないだろうと賭けるつもりです。

「デバッグしやすい」という値を無視しないでください。成熟したプロダクションコードは、狂ったように広がっています。何がうまくいかなかったのか、どこで何時間掘りを節約できるかについてのより多くの洞察を与えるもの。


27
1通常の生産コードでエラーがNEEDできるだけ速く、彼らが固定できるように(通常はデータエラー)を同定すること。デバッグが簡単であればあるほど良いです。

4
OPの例のいずれかに適用した場合、この答えは同じです。エラー状態をテストするかしないかのどちらかであり、それらを適切に処理するかしないかのいずれかです。言い換えれば、言語からnull参照を削除しても、発生するエラーのクラスは変更されず、それらのエラーの発現が微妙に変更されるだけです。
ダッシュトムバン

19
爆発しない爆弾の場合は+1。null参照では、public Foo doStuff()「何かをしてFooの結果を返す」という意味ではなく、「何かをしてFooの結果を返す、またはnullを返すが、それが起こるかどうかわからない」という意味です。その結果、確実にするためにどこでもnullチェックする必要があります。同様にヌルを削除し、特別なタイプまたはパターン(値タイプなど)を使用して無意味な値を示すこともできます。
マットオレニック

12
@greenoldman、あなたの例は、セマンティックモデルとしてのプリミティブ型(nullまたは整数)の使用が不十分なプラクティスであるという点を示すのに二重に効果的です。負の数が有効な答えではない型がある場合、その意味を持つセマンティックモデルを実装するために、新しい型のクラスを作成する必要があります。これで、その非負の型のモデリングを処理するすべてのコードは、そのクラスにローカライズされ、システムの他の部分から分離され、負の値を作成しようとするとすぐにキャッチできます。
-Huperniketes

9
@greenoldman、プリミティブ型はモデリングには不十分であるという点を引き続き証明します。まず、負の数を超えていました。除数がゼロの場合、除算演算子を超えています。ゼロで除算できないことがわかっている場合は、結果がきれいにならないことを予測でき、その場合に適切な「有効な」値をテストし、提供する責任があります。ソフトウェア開発者は、コードが動作するパラメーターを把握し、適切にコーディングする責任があります。エンジニアリングといじくりを区別するものです。
Huperniketes

50

コードでのnull参照の使用にはいくつかの問題があります。

まず、通常は特別な状態を示すために使用されます。特殊化が通常行われているように、各状態に新しいクラスまたは定数を定義するのではなく、null参照を使用すると、損失の多い非常に一般化されたtype / valueが使用されます

第二に、ヌル参照が表示され、それが生成されたもの、有効な状態とその原因を特定しようとすると、コードのデバッグがより難しくなります。

第三に、null参照はtestに追加のコードパスを導入します

第4に、null参照がパラメーターと戻り値の有効な状態として使用されると、防御プログラミング(設計によって引き起こされる状態の場合)では、念のためにさまざまな場所でさらにnull参照チェックを行う必要があります

5番目に、オブジェクトのメソッドテーブルでセレクタールックアップを実行するときに、言語のランタイムは既に型チェックを実行しています。そのため、オブジェクトの型が有効か無効かをチェックし、有効なオブジェクトの型をランタイムがチェックしてメソッドを呼び出すことで、作業を複製しています。

NullObjectパターンを使用して、ランタイムのチェックを利用して、その状態に固有のNOPメソッドを呼び出して(通常の状態のインターフェイスに準拠)、コードベース全体でnull参照の余分なチェックをすべて削除しませんか?

特別な状態を表現したいインターフェイスごとにNullObjectクラスを作成することで、より多くの作業が必要になります。ただし、少なくとも特殊化は、状態が存在する可能性のあるコードではなく、各特殊状態に分離されています。IOWでは、メソッドの代替実行パス少ないため、テストの数が削減されます。


7
...おそらく有用なオブジェクトを埋めているガベージデータを誰が生成したか(または、変更または初期化することを気にしなかった)を把握することは、NULL参照を追跡するよりもはるかに簡単だからですか?私はそれを購入しませんが、ほとんど静的に型付けされた土地で立ち往生しています。
ダッシュトムバン

ただし、ガベージデータはnull参照よりもはるかにまれです。特に管理言語で。
ディーンハーディング

5
Nullオブジェクトの場合は-1。
DeadMG

4
ポイント(4)と(5)は堅実ですが、NullObjectは救済策ではありません。あなたはさらに悪いトラップに陥ります-論理(ワークフロー)エラー。現在の状況を正確に知っていると、確かに助けになりますが、盲目的に(nullの代わりに)使用すると、さらにコストがかかります。
greenoldman

1
@ gnasher729、Objective-Cのランタイムは、Javaや他のシステムのようにnullポインター例外をスローしませんが、答えで説明した理由により、null参照を使用することはできません。たとえば[self.navigationController.presentingViewController dismissViewControllerAnimated: YES completion: nil];、ランタイムにnavigationControllerが存在しない場合、 それは一連のノーオペレーションとして扱われますが、ビューはディスプレイから削除されず、Xcodeの前に座って何時間も頭をひっかいて理由を突き止めようとします。
-Huperniketes

48

期待していない限り、ヌルはそれほど悪くありません。あなたは必要があります明示的に、言語設計上の問題である、あなたはnullを期待しているコードで指定する必要があります。このことを考慮:

Customer? c = Customer.GetByLastName("Goodman");
// note the question mark -- 'c' is either a Customer or null
if (c != null)
{
    // c is not null, therefore its type in this block is
    // now 'Customer' instead of 'Customer?'
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

でメソッドを呼び出そうとするとCustomer?、コンパイル時エラーが発生します。より多くの言語がこれを行わない(IMO)理由は、変数の型がどのスコープにあるかに応じて変化することを提供しないためです。言語がそれを処理できる場合、問題は完全に型システム。

オプションタイプとを使用して、この問題を処理する機能的な方法もありますMaybeが、私はそれについてあまり詳しくありません。理論的には正しいコードは正しくコンパイルするために1つの文字を追加するだけでよいため、この方法を好みます。


11
うわー、それはかなりクールです。コンパイル時に多くのエラーをキャッチすることに加えて、それはかどうかを把握するためにドキュメントをチェックすることから私を惜しみGetByLastName、それが返された場合、私はちょうど見ることができます- (例外をスローではなく)が見つからない場合はnullを返しますが、Customer?Customer
ティムグッドマン

14
@JBRWilkinson:これはアイデアを示すための簡単な例であり、実際の実装の例ではありません。私は実際にそれはかなりきちんとしたアイデアだと思います。
ディーンハーディング

1
ただし、nullオブジェクトパターンではないことは確かです。
ディーンハーディング

13
これは、Haskellがaと呼ぶもののように見えますMaybe-A Maybe CustomerJust c(where cはa Customer)またはNothing。他の言語では、それは呼ばれていますOption-この質問に関する他の回答のいくつかを見てください。
MatrixFrog

2
@SimonBarker:(でなく)型であるかどうかを確認できます。それが本当の利点です-意味のある「なし」の値を持つ変数/プロパティのみをnull可能として宣言する必要があります。Customer.FirstNameStringString?
ヨグ

20

の不幸な症状をカバーする優れた答えがたくさんありますnullので、別の引数を提示したいと思います:Nullは型システムの欠陥です。

型システムの目的は、プログラムのさまざまなコンポーネントが適切に「適合する」ようにすることです。適切に型付けされたプログラムは、未定義の動作に「外れ」ることはできません。

Javaの仮想の方言、または静的に型指定された言語が何であれ、文字列"Hello, world!"を任意の型の任意の変数に割り当てることができます。

Foo foo1 = new Foo();  // Legal
Foo foo2 = "Hello, world!"; // Also legal
Foo foo3 = "Bonjour!"; // Not legal - only "Hello, world!" is allowed

そして、次のような変数を確認できます。

if (foo1 != "Hello, world!") {
    bar(foo1);
} else {
    baz();
}

これについて不可能なことは何もありません。誰かがそうしたければ、そのような言語を設計することができます。特別な値である必要はありません"Hello, world!"-数値42、タプル(1, 4, 9)、またはなどnullでした。しかし、なぜこれを行うのでしょうか?型の変数はs Fooのみを保持する必要がありますFoo-これが型システムのポイントです!以上ではnullありません。さらに悪いことに、どんな型の値でありません。あなたがそれでできることは何もありません!Foo"Hello, world!"null

プログラマーは、変数が実際にを保持していること、またプログラムが保持していることを確信することはできませんFoo。未定義の動作を避けるために、変数をs "Hello, world!"として使用する前にチェックする必要がありますFoo。前のスニペットの文字列のチェックを行うことは本当にfoo1があるという事実を伝播しないことに注意してくださいFoo- barそうちょうど安全のために、だけでなく、独自のチェックを持っています。

パターンマッチングでMaybe/ Optionタイプを使用する場合と比較してください。

case maybeFoo of
 |  Just foo => bar(foo)
 |  Nothing => baz()

Just foo句内では、ユーザーとプログラムの両方が、Maybe Foo変数に値が本当に含まれていることを確実に知っています。Fooその情報はコールチェーンに沿って伝播され、barチェックを行う必要はありません。はとMaybe Fooは異なるタイプであるためFoo、それが含まれる可能性を処理することを余儀なくされているNothingので、あなたはNullPointerException。プログラムについてより簡単に推論でき、コンパイラは、型のすべての変数がFoo実際にFoosを含むことを知っているため、nullチェックを省略できます。誰もが勝ちます。


Perl 6のnullのような値はどうですか?常にタイプされることを除いて、それらはまだヌルです。それはあなたが書くことができ、問題はまだあるmy Int $variable = IntIntタイプのNULL値があるのInt
コンラッドボロウスキ14

@xfix私はPerlのに慣れていないんだけど、限り、あなたは強制する方法はありませんようthis type only contains integersに対立するものとしてのthis type contains integers and this other value、あなたが問題にあなたはすべての時間があるでしょう唯一の整数をしたいが。
ドーバル14

1
パターンマッチングの場合は+1。オプションの種類について言及している上記の回答の数を考えると、パターンマッチャーのような単純な言語機能を使用すると、nullよりもはるかに直感的に操作できるという事実に誰かが言及したと思うでしょう。
ジュール

1
モナドバインディングはパターンマッチングよりもさらに便利だと思います(もちろん、Maybeのバインドはパターンマッチングを使用して実装されます)。私はそれが一種のC#のような面白い見言語は、物事の多くのために特別な構文を入れていると思います(???.Haskellのような言語は、ライブラリ関数として実装する事のため、など)。もっときちんとした方法だと思います。null/ Nothing伝播は決して「特別」ではないので、なぜそれを得るためにもっと構文を学ぶ必要があるのでしょうか?
サラ

14

(古い質問のためにリングに帽子を投げる;))

nullの特定の問題は、静的型付けを壊すことです。

私が持っている場合Thing t、コンパイラは私が呼び出すことができることを保証することができますt.doSomething()。さて、t実行時のUNLESS はnullです。今、すべての賭けはオフです。コンパイラーはそれは大丈夫だと言ったがt、実際にはそうではないことがずっと後になってわかったdoSomething()。したがって、型エラーをキャッチするためにコンパイラを信頼できるのではなく、実行時までエラーをキャッチする必要があります。Pythonを使用することもできます!

そのため、ある意味では、nullは静的型付けシステムに動的型付けを導入し、期待される結果をもたらします。

ゼロによる除算や負の対数などの違いは、i = 0のとき、まだ整数であることです。コンパイラは依然としてその型を保証できます。問題は、ロジックで許可されていない方法でロジックがその値を誤って適用することですが、ロジックがそれを行う場合、それはほとんどバグの定義です。

(コンパイラー、これらの問題のいくつかをキャッチできます。BTW。i = 1 / 0コンパイル時のように式にフラグを立てるのと同じです。しかし、コンパイラーが関数に従って、パラメーターがすべて関数のロジックと一致することを保証することは本当に期待できません)

実際的な問題は、多くの余分な作業を行い、実行時に自分自身を保護するためにnullチェックを追加することですが、忘れた場合はどうなりますか?コンパイラは次の割り当てを停止します。

文字列s = new Integer(1234);

では、なぜ、逆参照を壊す値(null)への代入を許可する必要があるのsでしょうか?

コード内で「値なし」と「型付き」参照を混在させると、プログラマーに余分な負担がかかります。また、NullPointerExceptionsが発生すると、それらの追跡にさらに時間がかかる可能性があります。「これ期待される何かへの参照です」と言うために静的な型付けに頼るのではなく、「これ期待される何かへの参照である可能性があります」と言語に言わせます。


3
Cでは、型の変数は、またはを*int識別します。同様に、C ++では、型の変数はa 、型がから派生するもの、またはを識別します。JavaとC#のような派生物の問題は、それらが保管場所を使用して意味することです。解決策は、を保持できないように見せかけることではなく、適切なストレージロケーションタイプを持つことです。intNULL*WidgetWidgetWidgetNULLWidget*Widget*WidgetNULLWidget
supercat

14

nullポインタツールであります

敵ではありません。あなたはちょうどそれらを正しく使用する必要があります。

nullポインターのバグを見つけて修正する場合と比較して、典型的な無効なポインターのバグを見つけて修正するのにかかる時間について少し考えてください。に対してポインタをチェックするのは簡単nullです。非NULLポインターが有効なデータを指しているかどうかを検証するのはPITAです。

それでも納得できない場合は、シナリオに十分な量のマルチスレッドを追加してから、もう一度考え直してください。

物語の教訓:子供に水をかけないでください。そして、事故のツールを非難しないでください。ずっと前に数字のゼロを発明した男は非常に賢かった、なぜならその日はあなたが「何もない」と名づけることができたからだ。nullポインタは遠く離れているからではありません。


EDIT:もののNullObjectパターンはnullも参照より良い解決策であるように思わ、それは自分自身で問題を紹介します。

  • NullObjectメソッドを呼び出すときに、(理論によると)shouldを保持する参照は何もしません。したがって、誤って未割り当ての参照が原因で微妙なエラーが発生する可能性があります。これは、nullではないことが保証されていますが(yehaa!)、不要なアクションを実行します。NPEを使用すると、どこに問題があるかがすぐにわかります。でNullObject何とか(間違った)振る舞うこと、我々は(あまりにも)遅検出されたエラーのリスクを紹介します。これは誤って無効なポインターの問題と同様ではなく、同様の結果をもたらします。参照は、有効なデータのように見えますが、データエラーを引き起こし、追跡するのが難しい何かを指します。正直に言うと、これらの場合、まばたきなしで、すぐに失敗するNPEを好むでしょう論理的/データエラーが発生し、それが後で私を突然襲いました。エラーのコストがエラーが検出される段階の関数であるというIBMの研究を覚えていますか?

  • メソッドがで呼び出されたときに何もしないという概念はNullObject保持されません。プロパティゲッターまたは関数が呼び出されたとき、またはメソッドがを介して値を返すときoutです。戻り値はanでintあり、「何もしない」とは「0を返す」ことを意味するとします。しかし、0が正しい「何も」返されない(返されない)ことをどのように確認できますか?結局のところ、NullObject何もするべきではありませんが、値を求められたとき、どうにか反応しなければなりません。失敗はオプションではありません。NPE NullObjectを防ぐためにを使用しますが、別の例外(実装されていない、ゼロで除算するなど)とは確実に交換しませんか?それで、何かを返さなければならないときに、何を正しく返さないのですか?

私は仕方がありませんが、誰かがNullObjectすべての問題にパターンを適用しようとすると、1つの間違いを別の間違いで修復しようとしているように見えます。これは、中くらいの疑いもなく良いと便利なソリューションですいくつかの例が、それは確かにすべてのケースのための特効薬ではありません。


なぜnull存在するべきかについての議論を提示できますか?「問題ではなく、間違いを犯さないで」という言語の欠陥を手作業で波打つことができます。
ドーバル14年

無効なポインターをどの値に設定しますか?
JensG

まあ、あなたはそれらをまったく許可すべきではないので、あなたはそれらをどんな値にも設定しません。代わりに、あなたはおそらくと呼ばれる別のタイプの変数を使用Maybe Tいずれか含まれていてもよいNothing、またはT値を。ここで重要なことは、使用できるようになる前にがMaybe本当に含まれているかどうかを確認し、そうでない場合に一連Tのアクションを提供することを強制されることです。また、無効なポインターを許可していないため、a以外のものをチェックする必要がなくなりましたMaybe
ドーバル14年

1
あなたの最新の例の@JensGでは、コンパイラは、すべての呼び出しの前にnullチェックを行いますt.Something()か?それとも、あなたがすでにチェックを行ったことを知っており、あなたに再びそれをさせない方法がありますか?tこのメソッドに渡す前にチェックを行った場合はどうなりますか?Optionタイプを使用すると、コンパイラはのタイプを確認することで、まだチェックを行ったかどうかを判断しますt。オプションの場合はチェックしていませんが、ラップされていない値の場合はチェックします。
ティムグッドマン

1
値の入力を求められたときにnullオブジェクトは何を返しますか?
JensG

8

ヌル参照は無意味なコードを許可するため、間違いです。

foo = null
foo.bar()

型システムを活用する場合、代替手段があります。

Maybe<Foo> foo = null
foo.bar() // error{Maybe<Foo> does not have any bar method}

一般的には、変数をボックスに入れることです。できることは、ボックス化を解除することだけです。できれば、Eiffelに提案されているようなコンパイラヘルプを登録してください。

Haskellはゼロからそれを持っていMaybeます()、C ++では活用boost::optional<T>できますが、未定義の動作を得ることができます...


6
「レバレッジ」の場合は-1!
マークC

8
しかし、確かに多くの一般的なプログラミング構成体は無意味なコードを許可しているのでしょうか?ゼロによる除算は(ほぼ間違いなく)意味がありませんが、除算は許可されており、プログラマが除数がゼロでないことを確認する(または例外を処理する)ことを忘れないでください。
ティムグッドマン

3
「ゼロから」という意味はわかりMaybeませんが、コア言語には組み込まれていません。その定義はたまたま標準の前奏曲にありますdata Maybe t = Nothing | Just t
fredoverflow

4
@FredOverflow:プレリュードはデフォルトでロードされるため、「ゼロから」と考えます。Haskellには、言語の一般的なルールでこの(およびその他の)機能を定義できる驚くべき十分な型システムがあるためです。 :)
マチューM.

2
@TimGoodmanゼロによる除算はすべての数値型の最良の例ではないかもしれませんが(IEEE 754はゼロによる除算の結果を定義します)、タイプTの値をタイプの他の値で除算する代わりに例外をスローする場合T、操作をtypeの値でT割ったtypeの値に制限しますNonZero<T>
kbolino

8

そして、代替手段は何ですか?

オプションのタイプとパターンマッチング。私はC#を知らないので、ここにScala#という架空の言語のコードを示します:-)

Customer.GetByLastName("Goodman")    // returns Option[Customer]
match
{
    case Some(customer) =>
    Console.WriteLine(customer.FirstName + " " + customer.LastName + " is awesome!");

    case None =>
    Console.WriteLine("There was no customer named Goodman.  How lame!");
}

6

nullの問題は、nullを許可する言語が、nullに対する防御的なプログラミングを強いることです。それを確実にするために多くの努力が必要です(防御的なifブロックを使用しようとするよりもはるかに)

  1. あなたがそれらがnullでないと期待するオブジェクトは実際にnullになることはありません。
  2. あなたの防御メカニズムが実際にすべての潜在的なNPEを効果的に処理すること。

したがって、実際には、nullは最終的にコストのかかるものになります。


1
nullを処理するのにさらに多くの労力がかかる例を含めた場合、役に立つかもしれません。明らかに単純化しすぎた例では、どちらの方法でもほぼ同じです。私はあなたの意見に賛成ではありませんが、特定の(擬似)コードの例が一般的なステートメントよりも明確な絵を描いていることに気づきました。
ティムグッドマン

2
ああ、私は自分自身を明確に説明しなかった。nullを扱うのがより難しいということではありません。潜在的にnullの参照へのすべてのアクセスが既に保護されていることを保証することは(不可能ではないにしても)非常に難しいということです。つまり、null参照を許可する言語では、任意のサイズのコードにnullポインター例外がないことを保証することは不可能であり、大きな困難や努力が必要です。それが、null参照を高価なものにしている理由です。nullポインター例外が完全に存在しないことを保証するのは非常に高価です。
luis.espinal

4

プログラミング言語がプログラムを実行する前にその正当性を証明しようとする度合い。静的に型付けされた言語では、正しい型を持っていることを証明します。nullを許可しない参照のデフォルトに(オプションのnullを許可する参照を使用して)移動することにより、nullが渡されるべきであり、nullが渡されるべきではない場合の多くを排除できます。問題は、nullできない参照の処理に余分な労力を費やすことは、プログラムの正確性の観点からメリットがあるかどうかです。


4

問題はそれほどnullではなく、多くの現代言語でnull以外の参照型を指定できないということです。

たとえば、コードは次のようになります

public void MakeCake(Egg egg, Flour flour, Milk milk)
{
    if (egg == null) { throw ... }
    if (flour == null) { throw ... }
    if (milk == null) { throw ... }

    egg.Crack();
    MixingBowl.Mix(egg, flour, milk);
    // etc
}

// inside Mixing bowl class
public void Mix(Egg egg, Flour flour, Milk milk)
{
    if (egg == null) { throw ... }
    if (flour == null) { throw ... }
    if (milk == null) { throw ... }

    //... etc
}

クラス参照が渡されると、特にテスト中の再利用可能なユニットを構築するときに、事前にヌルをチェックしただけでも、防御プログラミングではすべてのパラメーターを再度ヌルでチェックすることをお勧めします。コードベース全体で同じ参照がnullで10回簡単にチェックできます。

あなたが物を手に入れたときに通常のnullable型を持ち、その場でnullを処理し、nullをチェックせずにnull不可能な型としてすべての小さなヘルパー関数とクラスに渡すことができれば良いのではないでしょうか時間?

これがオプションソリューションのポイントです。エラー状態をオンにできるのではなく、暗黙的にnull引数を受け入れられない関数の設計を許可するためです。型の「デフォルト」実装がヌル可能か非ヌル可能かは、両方のツールを使用できる柔軟性よりも重要ではありません。

http://twistedoakstudios.com/blog/Post330_non-nullable-types-vs-c-fixing-the-billion-dollar-mistake

これは、C#-> https://visualstudio.uservoice.com/forums/121579-visual-studio/category/30931-languages-cで最も人気のある2番目の機能としてもリストされています。


Option <T>についてのもう1つの重要な点は、それがモナド(したがってエンドファンクター)であるためNone、方法の各ステップを明示的にチェックすることなく、オプションタイプを含む値ストリームに対して複雑な計算を構成できることです。
サラ

3

ヌルは悪です。ただし、nullの欠如はより大きな悪である可能性があります。

問題は、現実の世界では、データがない状況に陥ることが多いことです。nullのないバージョンの例は、まだ爆発する可能性があります。論理ミスを犯してGoodmanを確認するのを忘れたか、Goodmanがチェックしてから調べたときに結婚した可能性があります。(Moriartyがコードのあらゆる部分を監視し、あなたをつまずかせようとしていると考えると、ロジックの評価に役立ちます。)

グッドマンのルックアップは、彼女がそこにいないときに何をしますか?nullがなければ、何らかのデフォルトの顧客を返す必要があります-そして今、あなたはそのデフォルトに物を売っています。

基本的には、コードが何に関係なく動作するか、または正しく動作することがより重要かどうかにかかっています。振る舞いをしないよりも不適切な振る舞いが望ましい場合は、nullは必要ありません。(これが正しい選択の例として、アリアンVの最初の打ち上げを考えてみましょう。/0エラーがキャッチされなかったため、ロケットが急旋回し、これによりバラバラになったときに自己破壊が発生しました。ブースターが点灯した瞬間に実際にはもはや役に立たないことを計算しようとしていました-ルーチンがゴミを返すにもかかわらず軌道を作っていたでしょう)

100回のうち少なくとも99回は、nullを使用することを選択します。しかし、私は彼らの自己のバージョンに注意して喜びのためにジャンプします。


1
これは良い答えです。プログラミングのnull構造は問題ではなく、null値のすべてのインスタンスです。デフォルトのオブジェクトを作成すると、最終的にすべてのヌルがそれらのデフォルトに置き換えられ、UI、DB、およびその間のすべての場所が代わりにそれらで埋められます。しかし、少なくともあなたのプログラムがクラッシュしていないので、あなたは夜寝るでしょう。
クリスマッコール

2
あなたはそれnullが値の欠如をモデル化する唯一の(または望ましい)方法であると仮定します。
サラ

1

最も一般的なケース向けに最適化します。

常にnullをチェックするのは退屈です。Customerオブジェクトを取得して、それを操作できるようにしたいだけです。

通常の場合、これは問題なく動作するはずです-ルックアップを行い、オブジェクトを取得して使用します。

例外的なケースあなたは(ランダム)そのレコード/オブジェクトが存在するかどうかを知らない、名前で顧客を探している、あなたは、これが失敗したことを何らかの指示が必要と思います。この状況では、答えはRecordNotFound例外をスローすることです(または、下のSQLプロバイダーにこれを行わせます)。

入ってくるデータ(パラメーター)を信頼できるかどうかわからない場合、おそらくユーザーによって入力されたために、「TryGetCustomer(name、out customer)」を提供することもできます。パターン。int.Parseおよびint.TryParseとの相互参照。


関数に入力され、null許容型であるため、入力されたデータを信頼できるかどうか、あなたは決して知らないと断言します。コードをリファクタリングして、入力をまったく異なるものにすることができます。したがって、nullが発生する可能性がある場合は、常に確認する必要があります。そのため、すべての変数がアクセスされる前に毎回nullがチェックされるシステムになります。チェックされない場合は、プログラムの失敗率になります。
クリスマッコール

0

例外は評価されません!

彼らには理由があります。コードの実行に問題がある場合は、exceptionと呼ばれる天才パターンがあり、何らかの問題があることがわかります。

nullオブジェクトの使用を避けることで、これらの例外の一部を隠しています。私は、彼がNULLポインター例外を適切に型付けされた例外に変換するOPの例を取り上げていません。@Jonasが指摘したように、Optionタイプのソリューションを採用した場合、例外を隠しています。

本番アプリケーションでは、ボタンをクリックして空のオプションを選択したときに例外がスローされるのではなく、何も起こりません。nullポインターの代わりに例外がスローされ、おそらくその例外のレポートを取得します(多くの実稼働アプリケーションでこのようなメカニズムがあります)。実際に修正することはできません。

例外を回避してコードを防弾にすることは悪い考えです。例外を修正してコードを防弾にすることは、これが私が選択する道です。


1
オプションタイプについては、Scalaで定期的に使用するようになったので、数年前に質問を投稿したときよりも、かなり詳しく知っています。Optionのポイントは、Option Noneではないものにコンパイル時エラーを割り当て、Option最初にチェックせずに値があるかのようにを使用してコンパイル時エラーにすることです。コンパイルエラーは、実行時例外よりも確かに優れています。
ティムグッドマン

例:言うことはできませんs: String = None、言う必要がありますs: Option[String] = None。IFおよびsオプションで、あなたが言うことができないs.length、しかし、あなたはそれが価値があることを確認することができ、パターンマッチングで、同時に値を抽出:s match { case Some(str) => str.length; case None => throw RuntimeException("Where's my value?") }
ティム・グッドマン

残念ながらScalaで言うことができますがs: String = null、それはJavaとの相互運用性の代価です。通常、Scalaコードではこのようなことは許可しません。
ティムグッドマン

2
しかし、例外(適切に使用される)は悪いことではないという一般的なコメントに同意し、例外を飲み込むことによって例外を「修正」することは一般的にひどい考えです。しかし、Optionタイプの場合、目標は例外をコンパイル時エラーに変えることであり、これはより良いことです。
ティムグッドマン

@TimGoodmanはscalaのみの戦術ですか、JavaやActionScript 3などの他の言語で使用できますか?また、完全な例を使用して回答を編集できると便利です。
イリヤガスマン14年

0

Nulls明示的にチェックする必要があるため問題がありますが、コンパイラは、チェックを忘れたことを警告することはできません。時間のかかる静的解析のみがそれを教えてくれます。幸いなことに、いくつかの良い選択肢があります。

変数をスコープから取り出します。 あまりにも頻繁nullに、プログラマーが変数を宣言するのが早すぎるか、長すぎる場合にプレースホルダーとして使用されます。最善のアプローチは、単に変数を取り除くことです。有効な値を入力するまで宣言しないでください。これはあなたが思うほど難しい制限ではなく、コードをよりきれいにします。

nullオブジェクトパターンを使用します。 欠損値がシステムの有効な状態である場合があります。ここでは、nullオブジェクトパターンが良い解決策です。常に明示的にチェックする必要はありませんが、null状態を表すことができます。一部の人々が主張するように、それは「エラー状態のペーパー処理」ではありません。この場合、null状態は有効なセマンティック状態であるためです。つまり、null状態有効なセマンティック状態でない場合、このパターンを使用しないでください。変数を範囲外にする必要があります。

Maybe / Optionを使用します。まず、これによりコンパイラーは、欠損値をチェックする必要があることを警告しますが、明示的なチェックのタイプを別のタイプに置き換えるだけではありません。を使用するとOptions、コードを連鎖させて、値が存在するかのように続行でき、最後の最後まで実際に確認する必要はありません。Scalaでは、サンプルコードは次のようになります。

val customer = customerDb getByLastName "Goodman"
val awesomeMessage =
  customer map (c => s"${c.firstName} ${c.lastName} is awesome!")
val notFoundMessage = "There was no customer named Goodman.  How lame!"
println(awesomeMessage getOrElse notFoundMessage)

すばらしいメッセージを作成している2行目では、顧客が見つかったかどうかを明示的に確認しません。その美しさは必要ありませんOptionisの場合に何が起こるかを明示的に懸念するのは、最後の行までですNone。それだけでなく、私たちがそれをするのを忘れていた場合、タイプはチェックされません。それでも、それは非常に自然な方法で行われ、明示的なifステートメントはありません。

これとは対照的に、nullタイプチェックは問題なく行われますが、ユニットケースの演習で運が良ければ、道のすべてのステップを明示的にチェックする必要があるか、実行時にアプリケーション全体が爆破する必要があります。手間をかけるだけの価値はありません。


0

NULLの中心的な問題は、システムの信頼性が低くなることです。1980年、Tony Hoareはチューリング賞に捧げられた論文で次のように書いています。

ですから、ADAの創始者とデザイナーに対する私の最高のアドバイスは無視されています。…。信頼性が重要なアプリケーション、つまり原子力発電所、巡航ミサイル、早期警戒システム、対弾道ミサイル防衛システムで、現在の状態でこの言語を使用することを許可しないでください。プログラミング言語のエラーの結果として迷う次のロケットは、金星への無害な旅行での探査宇宙ロケットではないかもしれません。それは、私たち自身の都市の一つで爆発する核弾頭かもしれません。信頼できないプログラムを生成する信頼できないプログラミング言語は、安全でない車、有毒な農薬、または原子力発電所での事故よりも、私たちの環境と社会にとってはるかに大きなリスクを構成します。リスクを高めるのではなく、リスクを減らすことに注意してください。

それ以降、ADA言語は大きく変化しましたが、そのような問題はJava、C#、その他の多くの一般的な言語にまだ存在します。

クライアントとサプライヤの間で契約を作成するのは開発者の義務です。たとえば、C#では、Javaのように、読み取り専用(2つのオプション)を作成することによりGenericsNull参照の影響を最小限に抑えることができますNullableClass<T>

class NullableClass<T>
{
     public HasValue {get;}
     public T Value {get;}
}

そして、それを次のように使用します

NullableClass<Customer> customer = dbRepository.GetCustomer('Mr. Smith');
if(customer.HasValue){
  // one logic with customer.Value
}else{
  // another logic
} 

または、C#拡張メソッドで2つのオプションスタイルを使用します。

customer.Do(
      // code with normal behaviour
      ,
      // what to do in case of null
) 

違いは重要です。メソッドのクライアントとして、あなたは何を期待するかを知っています。チームはルールを持つことができます:

クラスがNullableClass型ではない場合、そのインスタンスはnullでない必要があります

チームは、たとえば前提条件を使用して、Design by Contractおよびコンパイル時の静的チェックを使用して、このアイデアを強化できます。

function SaveCustomer([NotNullAttribute]Customer customer){
     // there is no need to check whether customer is null 
     // it is a client problem, not this supplier
}

または文字列用

function GetCustomer([NotNullAndNotEmptyAttribute]String customerName){
     // there is no need to check whether customerName is null or empty 
     // it is a client problem, not this supplier
}

これらのアプローチにより、アプリケーションの信頼性とソフトウェア品質が大幅に向上します。Design by Contractは、1988年にBertrand Meyerが著名なObject-Oriented Software Construction bookとEiffel言語で作成したHoareロジックの例ですが、現代のソフトウェアクラフトでは無効に使用されていません。


0

番号。

言語のような特別な価値を説明できないことnullは、言語の問題です。KotlinSwiftなどの言語を見ると、ライターが遭遇するたびにnull値の可能性を明示的に処理するように強制するため、危険はありません。

問題の1のような言語では、言語ができますnullいずれかの値になるように-ishタイプ、それ以降の認めるための任意の構造を提供していない、という事につながるnull(どこか別の場所からあなたに渡された場合は特に)が予期せず、かつしたがって、クラッシュします。それは悪ですnull。あなたに対処させずに使用させることです。


-1

基本的に中心的な考え方は、nullポインター参照の問題は実行時ではなくコンパイル時にキャッチされるべきだということです。したがって、何らかのオブジェクトを受け取る関数を作成していて、誰もnull参照で呼び出さないようにするには、型システムでその要件を指定して、コンパイラがそれを使用して関数の呼び出しが決して行われないことを保証できるようにする必要があります呼び出し元が要件を満たさない場合はコンパイルします。これは、たとえば、型を装飾したり、オプション型(一部の言語ではNULL入力可能型とも呼ばれます)を使用したりなど、さまざまな方法で実行できますが、明らかにコンパイラ設計者にとってはさらに多くの作業が必要です。

マシンのリソースが限られているため、1960年代と70年代にコンパイラ設計者がこのアイデアを全面的に導入しなかった理由は理解できると思います。数年後。


-1

私は1つの単純な反対がありnullます:

あいまいさを導入することにより、コードのセマンティクスを完全に破壊します

多くの場合、結果は特定のタイプであると予想されます。これは意味的に健全です。idたとえば、特定ののユーザーをデータベースに要求した場合、結果は特定のタイプ(= user)であると予想されます。しかし、そのIDのユーザーがいない場合はどうなりますか?1つの(悪い)ソリューションは、null値を追加することです。だから、結果は不明瞭:それはどちらかuser、またはそれですnull。しかしnull、期待されるタイプではありません。そして、ここからコードの匂いが始まります。

を回避nullするには、コードを意味的に明確にします。

方法は周りに常にありますnull:コレクションにリファクタリング、Null-objectsoptionalsなど


-2

コードを実行して値を計算する前に、参照またはポインター型の格納場所にアクセスすることが意味的に可能な場合、何をすべきかについての完璧な選択はありません。そのような場所をデフォルトでnullポインタ参照に設定すると、自由に読み取りおよびコピーできますが、逆参照またはインデックス付けされた場合にエラーが発生する可能性があります。その他の選択肢は次のとおりです。

  1. デフォルトの場所はnullポインターですが、コードがクラッシュすることなくそれらを見る(またはnullであると判断する)ことを許可しません。ポインターをnullに設定する明示的な手段を提供する可能性があります。
  2. デフォルトの場所はnullポインターになり、読み取りを試みるとクラッシュしますが、ポインターがnullを保持しているかどうかをテストする非クラッシュ手段を提供します。また、ポインターをヌルに設定する明示的な手段も提供します。
  3. 指定されたタイプの特定のコンパイラー提供のデフォルトインスタンスへのポインターにデフォルトで場所を設定します。
  4. 指定されたタイプの特定のプログラム提供のデフォルトインスタンスへのポインタをデフォルトの場所にします。
  5. (操作の間接参照だけでなく)任意のNULLポインターアクセスを使用して、プログラム提供のルーチンを呼び出してインスタンスを提供します。
  6. 初期化ルーチンがすべてのメンバーで実行されるまで、一時的なアクセスできないコンパイラーを除き、ストレージの場所のコレクションが存在しないように言語セマンティクスを設計します(配列コンストラクターのようなものは、デフォルトのインスタンスで提供される必要がありますが、インスタンスを返す関数、または場合によっては関数のペア-インスタンスを返す関数と、配列の構築中に例外が発生した場合に以前に構築されたインスタンスで呼び出される関数)。

最後の選択肢は、特に言語にnull許容型と非null許容型の両方が含まれている場合に魅力があります(任意の型の特別な配列コンストラクターを呼び出すことができますが、null許容型の配列を作成するときに呼び出す必要があるのは1つだけです)しかし、nullポインターが発明された頃にはおそらく実行できなかったでしょう。他の選択肢のうち、nullポインタをコピーすることはできますが、逆参照またはインデックス付けすることはできません。アプローチ#4はオプションとして便利であり、実装するのにかなり安いはずですが、確かに唯一のオプションではありません。デフォルトでポインターが特定の有効なオブジェクトを指している必要があることを要求することは、ポインターをデフォルトでnull値に設定するよりもはるかに悪いです。


-2

はい、NULLはオブジェクト指向の世界ではひどいデザインです。一言で言えば、NULLを使用すると次のようになります。

  • アドホックエラー処理(例外ではなく)
  • あいまいなセマンティック
  • 速く失敗する代わりに遅い
  • コンピューター思考とオブジェクト思考
  • 可変で不完全なオブジェクト

詳細な説明については、このブログ投稿を確認してください:http : //www.yegor256.com/2014/05/13/why-null-is-bad.html

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