一部のプログラミング言語の人々によるヌル参照の一貫したバッシングについては、よくわかりません。それらの何がそんなに悪いのですか?存在しないファイルへの読み取りアクセスを要求した場合、例外またはnull参照を取得できてうれしく思いますが、例外は良好と見なされますが、null参照は不良と見なされます。この背後にある理由は何ですか?
一部のプログラミング言語の人々によるヌル参照の一貫したバッシングについては、よくわかりません。それらの何がそんなに悪いのですか?存在しないファイルへの読み取りアクセスを要求した場合、例外またはnull参照を取得できてうれしく思いますが、例外は良好と見なされますが、null参照は不良と見なされます。この背後にある理由は何ですか?
回答:
少なくとも、私が知っているか読んだことがある人なら誰でも、ヌル参照は例外よりも「回避」されません。あなたは従来の知恵を誤解していると思います。
悪いのは、null参照へのアクセス(またはnullポインターの逆参照など)を試みることです。常にバグを示しているため、これは悪いことです。意図的にこのようなことを行うことは決してありません。また、意図的にそれを行う場合は、さらに悪いことになります。予想される動作とバグのある動作を区別することができなくなるからです。
特定のフリンジグループは本当に何らかの理由でNULLかどうかの概念を憎むが、誰がそこにいるエドが指摘するように、あなたが持っていない場合、null
またはnil
何かにつながる可能性がある、そしてあなただけの何か他のものと交換する必要がありますクラッシュ(データ破損など)よりも悪い。
実際、多くのフレームワークは両方の概念を採用しています。たとえば、.NETでは、頻繁に表示されるパターンはメソッドのペアであり、1つは単語の接頭辞Try
(などTryGetValue
)です。このTry
場合、参照はデフォルト値(通常はnull
)に設定され、他の場合は例外がスローされます。どちらのアプローチにも問題はありません。どちらも、それらをサポートする環境で頻繁に使用されます。
本当にすべてはセマンティクスに依存しています。Ifはnull
、コレクションを検索する一般的な場合のように、有効な戻り値であり、その後、戻りますnull
。一方、有効な戻り値ではない場合(たとえば、独自のデータベースから取得した主キーを使用してレコードを検索null
する場合)、呼び出し側はそれを期待しないため、返すことは悪い考えです。それをチェックしません。
どのセマンティックを使用するかを理解するのは非常に簡単です。関数の結果が未定義であることは意味がありますか? その場合、null参照を返すことができます。そうでない場合は、例外をスローします。
null
そう本当に、Haskellはここでかなり無関係ですが、。
null
あまりにも長い間非常によく耐えたが、それ以外は言う人々の大半は、不完全な要件や結果整合性などの制約と現実世界のアプリを設計経験の少ない学者であるように見えます。
大きな違いは、NULLを処理するためにコードを省くと、コードが関連性のないエラーメッセージで後の段階でクラッシュする可能性が非常に高くなることです。あなたの例で読むためのファイル)。
ヌル値はプログラミング言語の必須部分ではなく、バグの一貫した原因であるためです。あなたが言うように、ファイルを開くと失敗する可能性があり、それはnull戻り値として、または例外を介して返される可能性があります。null値が許可されなかった場合、障害を伝えるための一貫した特異な方法があります。
また、これはヌルに関する最も一般的な問題ではありません。ほとんどの人は、nullを返す可能性のある関数を呼び出した後、nullをチェックすることを忘れないでください。プログラムの実行のさまざまなポイントで変数をnullにできるようにすることで、独自の設計で問題がさらに大きくなります。null値が許可されないようにコードを設計できますが、nullが言語レベルで許可されない場合、これは必要ありません。
ただし、実際には、変数が初期化されているかどうかを示す何らかの方法が必要です。その場合、プログラムがクラッシュせず、代わりに無効な可能性のあるデフォルト値を使用し続けるというバグが発生します。どちらが良いかわかりません。私のお金のために、私は早く頻繁にクラッシュするのが好きです。
null
、戻り値として取得したときに何が起こったかも知っています。要求した情報は存在しません。違いはありません。いずれにしても、呼び出し側は、特定の目的でその情報を実際に使用する必要がある場合、戻り値を検証する必要があります。null
、nullオブジェクト、またはモナドを検証するかどうかは、実際的な違いはありません。
そもそもヌル参照のアイデアを作成したトニー・ホアは、それを100万ドルの間違いと呼んでいます。
問題は、null参照そのものではなく、ほとんどの(そうでない場合)型安全な言語での適切な型チェックの欠如に関するものです。
この言語のサポートの欠如は、バグ「ヌルバグ」が検出される前にプログラムに長時間潜んでいる可能性があることを意味します。もちろん、これはバグの性質ですが、「ヌルバグ」は回避できることが知られています。
この問題は、特にCまたはC ++(たとえば)で発生します。これは、原因となる「ハード」エラー(プログラムのクラッシュ、即時の復旧なし)が原因です。
他の言語では、それらをどのように扱うかという問題が常にあります。
JavaまたはC#では、null参照でメソッドを呼び出そうとすると例外が発生しますが、大丈夫かもしれません。したがって、ほとんどのJavaまたはC#プログラマーはこれに慣れており、なぜそうしないのかを理解していません(C ++を笑います)。
Haskellでは、nullの場合にアクションを明示的に提供する必要があります。したがって、Haskellプログラマーは同僚が正しいと思うので、同僚をgloめます(右?)。
これは、実際には古いエラーコード/例外の議論ですが、今回はエラーコードの代わりにSentinel Valueを使用します。
いつものように、どちらが最も適切かは、実際に状況と探しているセマンティクスに依存します。
null
ので、本当に悪です。(確かに、少なくとも一部の言語では、この代替には冗長性に欠点があります。)
エラーを通知するためにNULL(または数値のゼロ、またはブール値のfalse)を返すことは、技術的にも概念的にも間違っています。
技術的には、戻り値が返される正確な時点で、戻り値をすぐにチェックする必要があります。20個のファイルを連続して開き、エラーシグナリングがNULLを返すことによって行われる場合、消費するコードは、読み取られた各ファイルを個別にチェックし、ループや同様の構造から抜け出す必要があります。これは、散らかったコードの完璧なレシピです。ただし、例外をスローしてエラーを通知することを選択した場合、消費するコードは、例外をすぐに処理するか、関数呼び出し間でも必要に応じてレベルを上げることができます。これにより、コードがずっときれいになります。
概念的に、ファイルを開いて何かがおかしくなった場合、値(NULLであっても)を返すのは間違っています。操作が完了しなかったため、返品するものがありません。NULLを返すことは、「ファイルを正常に読み取りました。ファイルに含まれているもの-何もありません」と概念的に同等です。それが表現したい場合(つまり、問題の操作の実際の結果としてNULLが理にかなっている場合)、必ずNULLを返しますが、エラーを通知する場合は、例外を使用します。
歴史的に、Cのようなプログラミング言語には言語に例外処理が組み込まれていないため、エラーがこのように報告されました。推奨される方法(長いジャンプを使用)は、少し複雑で直感に反します。
また、問題にはメンテナンスの側面もあります。例外を除き、障害を処理するために追加のコードを記述する必要があります。そうしないと、プログラムは早期にハードに失敗します(これは良いことです)。エラーを通知するためにNULLを返す場合、プログラムのデフォルトの動作は、エラーを無視して、他の問題(破損したデータ、セグメンテーション違反、NullReferenceExceptions、言語に応じて)に至るまで続行します。エラーを早期に大声で通知するには、余分なコードを記述し、何を推測する必要があります。それは、締め切りが厳しいときに除外される部分です。
すでに指摘したように、多くの言語は、nullポインターの逆参照をキャッチ可能な例外に変換しません。それを行うことは、比較的現代的なトリックです。nullポインターの問題が最初に認識されたとき、例外はまだ発明されていませんでした。
NULLポインターを有効なケースとして許可する場合、それは特別なケースです。多くの場合、多くの異なる場所で、特別な場合の処理ロジックが必要です。それは余分な複雑さです。
nullポインターの可能性があるかどうかに関係なく、例外スローを使用して例外的なケースを処理しない場合は、それらの例外的なケースを別の方法で処理する必要があります。通常、すべての関数呼び出しは、関数呼び出しが不適切に呼び出されるのを防ぐため、または関数が終了したときに失敗のケースを検出するために、これらの例外的なケースをチェックする必要があります。これは、例外を使用して回避できる余分な複雑さです。
通常、複雑さが増すとエラーが増えます。
データ構造でヌルポインターを使用する代わりに(たとえば、リンクリストの開始/終了をマークする)、センチネルアイテムを使用することもできます。これらは、同じ機能をはるかに少ない複雑さで提供できます。ただし、複雑さを管理する方法は他にもあります。1つの方法は、nullチェックが1か所でのみ必要になるように、null可能性のあるポインターをスマートポインタークラスにラップすることです。
ヌルポインターが検出されたらどうしますか?例外ケース処理を組み込むことができない場合は、常に例外をスローし、その特殊ケース処理を呼び出し元に効果的に委任できます。そして、それはまさに、いくつかの言語がデフォルトでnullポインターを間接参照するときに行うことです。
言語に依存します。
たとえば、Objective-Cを使用すると、問題なくnull(nil)オブジェクトにメッセージを送信できます。nilを呼び出すと、nilも返され、言語機能と見なされます。
あなたはその振る舞いに依存し、それらすべての複雑なネストされたif(obj == null)
構造を避けることができるので、私は個人的にそれが好きです。
例えば:
if (myObject != nil && [myObject doSomething])
{
...
}
以下に短縮できます。
if ([myObject doSomething])
{
...
}
つまり、コードが読みやすくなります。
あなたがそれを文書化する限り、nullを返すか例外をスローするかどうかについては気にしません。
GetAddressMaybe
またはGetSpouseNameOrNull
。
ヌル参照は非常に便利なことがよくあります。たとえば、リンクリストの要素には、何らかの後継要素がある場合とない場合があります。null
「後継者なし」に使用することは完全に自然です。または、人は配偶者を持つことも持たないこともできます- null
「配偶者がいない」の使用は完全に自然で、Person.Spouse
メンバーが参照できる「配偶者がいない」特別な値を持つよりもはるかに自然です。
しかし:多くの値はオプションではありません。典型的なOOPプログラムでは、参照の半分以上がnull
初期化後にできなくなると、プログラムが失敗します。そうしないと、コードをif (x != null)
チェックで埋める必要があります。では、デフォルトですべての参照をnull可能にする必要があるのはなぜですか?それは本当に逆でなければなりません:変数はデフォルトでnull不可であるべきであり、あなたは明示的に「ああ、この値もである可能性がnull
あります」と言わなければなりません。
あなたの質問は紛らわしく表現されています。null参照例外を意味しますか(これは実際にde参照null)?そのタイプの例外を望まない明確な理由は、プログラムのどの時点でも値がnullに設定された可能性がある場合、または何が間違っていたかについての情報を提供しないことです。「存在しないファイルへの読み取りアクセスをリクエストした場合、例外またはnull参照を取得できてうれしいです」と書いていますが、原因を示さないものを取得しても満足できないはずです。 。「nullを逆参照しようとした」という文字列のどこにも、読み取りまたは存在しないファイルに関する言及はありません。おそらく、読み取り呼び出しから戻り値としてnullを取得することは完全に幸せであることを意味します-それはまったく異なる問題ですが、それでも読み取りが失敗した理由に関する情報を提供しません。それ'