例外のスローは問題ないと見なされているのに、なぜnull参照が回避されるのですか?


21

一部のプログラミング言語の人々によるヌル参照の一貫したバッシングについては、よくわかりません。それらの何がそんなに悪いのですか?存在しないファイルへの読み取りアクセスを要求した場合、例外またはnull参照を取得できてうれしく思いますが、例外は良好と見なされますが、null参照は不良と見なされます。この背後にある理由は何ですか?



2
一部の言語は、他の言語よりもnullでクラッシュします。.Net / Javaなどの「マネージコード」にとって、null refは単なる別のタイプの問題ですが、他のネイティブコードではこれを適切に処理できない場合があります(特定の言語については言及していません)。マネージドワールドでも、フェールセーフコード(組み込み?、武器?)を作成したい場合や、ASAP(ユニットテスト)で大騒ぎしたい場合があります。両方のタイプのコードが同じライブラリを呼び出している可能性があります-これは問題になります。一般的に、コンピューターの感情を傷つけないようにするコードは悪い考えだと思います。とにかくフェイルセーフはハードです。
ジョブ

@ジョブ:これは怠を提唱しています。例外の処理方法がわかっている場合は、それを処理します。時には、取り扱いには別の例外をスローすることを伴うかもしれないが、あなたはすべき決してヌル参照例外が未処理手放すません。今まで。それはすべてのメンテナンスプログラマの悪夢です。ツリー全体で最も役に立たない例外です。Stack Overflowに問い合わせてください
アーロンノート

または、別の言い方をすれば、エラー情報を表す何らかのコードを返してはどうでしょうか。この議論は今後数年間激怒するでしょう。
gbjbaanb

回答:


24

少なくとも、私が知っているか読んだことがある人なら誰でも、ヌル参照は例外よりも「回避」されません。あなたは従来の知恵を誤解していると思います。

悪いのは、null参照へのアクセス(またはnullポインターの逆参照など)を試みることです。常にバグを示しているため、これは悪いことです。意図的にこのようなことを行うことは決してありません。また、意図的にそれを行う場合、さらに悪いことになります。予想される動作とバグのある動作を区別することができなくなるからです。

特定のフリンジグループは本当に何らかの理由でNULLかどうかの概念を憎むが、誰がそこにいるエドが指摘するように、あなたが持っていない場合、nullまたはnil何かにつながる可能性がある、そしてあなただけの何か他のものと交換する必要がありますクラッシュ(データ破損など)よりも悪い。

実際、多くのフレームワークは両方の概念を採用しています。たとえば、.NETでは、頻繁に表示されるパターンはメソッドのペアであり、1つは単語の接頭辞Try(などTryGetValue)です。このTry場合、参照はデフォルト値(通常はnull)に設定され、他の場合は例外がスローされます。どちらのアプローチにも問題はありません。どちらも、それらをサポートする環境で頻繁に使用されます。

本当にすべてはセマンティクスに依存しています。Ifはnull、コレクションを検索する一般的な場合のように、有効な戻り値であり、その後、戻りますnull。一方、有効な戻り値ではない場合(たとえば、独自のデータベースから取得した主キーを使用してレコードを検索nullする場合)、呼び出し側はそれを期待しないため、返すことは悪い考えです。それをチェックしません。

どのセマンティックを使用するを理解するのは非常に簡単です。関数の結果が未定義であることは意味がありますか? その場合、null参照を返すことができます。そうでない場合は、例外をスローします。


5
実際、nullやnilを持たず、「他の言語に置き換える必要がない」言語があります。Nullable参照は、そこに何かがあるかもしれないし、そうでないかもしれないことを意味します。ユーザーに明示的に何かがあるかどうかを明示的に確認するように要求するだけで、問題は解決しました。実際の例についてはhaskellをご覧ください。
ジョアンナラーソン

3
@ErikKronberg、はい、「10億ドルの間違い」とそのすべてのナンセンス、これを駆け抜けて新鮮で魅力的であると主張する人々のパレードは決して終わらないので、前のコメントスレッドは削除されました。これらの革新的な代替品は決して失敗することはなく、常にNullオブジェクト、オプション、またはコントラクトのバリアントであり、実際には根本的な論理エラーを魔法のように排除せず、それぞれ延期または昇格します。とにかく、これは言語のプログラミングについての質問は明らかで行う必要がありnullそう本当に、Haskellはここでかなり無関係ですが、。
アーロンノート

1
nullのテストを要求しないことは、それを要求することと同じくらい良いと真剣に主張していますか?
ジョアンナラーソン

3
@ErikKronberg:はい、私は「真剣に議論している」のは、nullをテストすることは、(a)Nullオブジェクトの動作を中心にアプリケーションのすべての層を設計すること、(b)パターン一致すること、常にオプション、または(c)呼び出し先が「わからない」と言うことを許可せず、例外またはクラッシュを強制する。あります理由理由はnullあまりにも長い間非常によく耐えたが、それ以外は言う人々の大半は、不完全な要件や結果整合性などの制約と現実世界のアプリを設計経験の少ない学者であるように見えます。
アーロンノート

3
@Aaronaught:時々、コメント用の投票ボタンがあればいいのにと思います。そのように暴言する理由はありません。
マイケルショー

11

大きな違いは、NULLを処理するためにコードを省くと、コードが関連性のないエラーメッセージで後の段階でクラッシュする可能性が非常に高くなることです。あなたの例で読むためのファイル)。


4
NULLの処理に失敗すると、コードとそれを返すものとの間のインターフェースが意図的に無視されます。そのような間違いを犯す開発者は、NULLを実行しない言語で他の人を作成します。
Blrfl

@Blrfl、理想的にはメソッドは非常に短いため、問題が発生している場所を正確に把握するのは簡単です。通常、優れたデバッガーは、コードが長い場合でも、null参照例外を適切にハントできます。そこにないレジストリから重要な設定を読み取ろうとしている場合、どうすればよいですか?レジストリが台無しになり、ノードを静かに再作成してデフォルトに設定するよりも、ユーザーを失敗させてうんざりさせるほうがましです。ウイルスがこれを行った場合はどうなりますか?したがって、nullを取得した場合、特別な例外をスローするか、単にリッピングさせますか?短い方法で大きな違いは何ですか?
ジョブ

@ジョブ:デバッガーがない場合はどうなりますか?アプリケーションがリリース環境で実行される時間の99.99%を認識していますか?その場合、より意味のある例外を使用したいと思うでしょう。それでもアプリは失敗し、ユーザーを悩ませる必要がありますが、少なくともデバッグ情報を出力することで、問題を迅速に追跡できるため、上記の迷惑を最小限に抑えることができます。
アーロンノート

@ Birfl、nullが返されるのが自然な場合を処理したくない場合があります。たとえば、キーを値にマッピングするコンテナがあるとします。私のロジックが、最初に格納しなかった値を読み取ろうとしないことを保証している場合、nullが返されることはありません。その場合、プログラム内のどこかで不可解に失敗するためにnullを返すのではなく、可能な限り多くの情報を提供して例外の原因を特定したいのです。
ウィンストンエバート

別の言い方をすれば、例外を除いて、異常なケースを明示的に処理する必要があります。そうしないと、プログラムがすぐに終了します。null参照を使用すると、コードが異常なケースを明示的に処理しない場合、リンプが試行されます。私は今すぐ失敗したケースを取る方が良いと思います。
ウィンストンエバート

8

ヌル値はプログラミング言語の必須部分ではなく、バグの一貫した原因であるためです。あなたが言うように、ファイルを開くと失敗する可能性があり、それはnull戻り値として、または例外を介して返される可能性があります。null値が許可されなかった場合、障害を伝えるための一貫した特異な方法があります。

また、これはヌルに関する最も一般的な問題ではありません。ほとんどの人は、nullを返す可能性のある関数を呼び出した後、nullをチェックすることを忘れないでください。プログラムの実行のさまざまなポイントで変数をnullにできるようにすることで、独自の設計で問題がさらに大きくなります。null値が許可されないようにコードを設計できますが、nullが言語レベルで許可されない場合、これは必要ありません。

ただし、実際には、変数が初期化されているかどうかを示す何らかの方法が必要です。その場合、プログラムがクラッシュせず、代わりに無効な可能性のあるデフォルト値を使用し続けるというバグが発生します。どちらが良いかわかりません。私のお金のために、私は早く頻繁にクラッシュするのが好きです。


識別文字列を持つ初期化されていないサブクラスを検討し、そのすべてのメソッドは例外をスローします。これらのいずれかが表示された場合、何が起こったかを知っています。メモリが限られている組み込みシステムでは、Uninitializedファクトリの製品バージョンはnullを返すだけです。
ジムBalter

3
@Jim Balter:実際にそれが実際にどのように役立つかについて私は混乱していると思います。重要なプログラムでは、何らかの時点で初期化されていない可能性のある値を処理する必要があります。そのため、デフォルト値を示す何らかの方法が必要です。そのため、続行する前にこれを確認する必要があります。したがって、潜在的なクラッシュの代わりに、潜在的に無効なデータを操作しています。
エドS.

1
またnull、戻り値として取得したときに何が起こったかも知っています。要求した情報は存在しません。違いはありません。いずれにしても、呼び出し側は、特定の目的でその情報を実際に使用する必要がある場合、戻り値を検証する必要があります。null、nullオブジェクト、またはモナドを検証するかどうかは、実際的な違いはありません。
アーロンノート

1
@Jim Balter:確かに、実際的な違いはまだわかりません。学界以外で実際のプログラムを書く人にとって、それがどのように人生を楽にしてくれるかわかりません。利益がないという意味ではなく、単に私には明らかではないように思えます。
エドS.

1
Uninitializedクラスとの実際的な違いを2回説明しました-null項目の起源を特定します-nullの逆参照が発生したときにバグを特定することを可能にします。nullなしのパラダイムを中心に設計された言語に関しては、それらはget-goからの問題を回避します。つまり、初期化されていない変数を回避するためのプログラミングの代替方法を提供します。あなたがそれらに精通していない場合、それは把握するのが難しいように見えるかもしれません。大量のバグも回避する構造化プログラミングも、かつて「アカデミック」と見なされていました。
ジムバルター

5

そもそもヌル参照のアイデアを作成したトニー・ホアは、それを100万ドルの間違いと呼んでます。

問題は、null参照そのものではなく、ほとんどの(そうでない場合)型安全な言語での適切な型チェックの欠如に関するものです。

この言語のサポートの欠如は、バグ「ヌルバグ」が検出される前にプログラムに長時間潜んでいる可能性があることを意味します。もちろん、これはバグの性質ですが、「ヌルバグ」は回避できることが知られています。

この問題は、特にCまたはC ++(たとえば)で発生します。これは、原因となる「ハード」エラー(プログラムのクラッシュ、即時の復旧なし)が原因です。

他の言語では、それらをどのように扱うかという問題が常にあります。

JavaまたはC#では、null参照でメソッドを呼び出そうとすると例外が発生しますが、大丈夫かもしれません。したがって、ほとんどのJavaまたはC#プログラマーはこれに慣れており、なぜそうしないのかを理解していません(C ++を笑います)。

Haskellでは、nullの場合にアクションを明示的に提供する必要があります。したがって、Haskellプログラマーは同僚が正しいと思うので、同僚をgloめます(右?)。

これは、実際には古いエラーコード/例外の議論ですが、今回はエラーコードの代わりにSentinel Valueを使用します。

いつものように、どちらが最も適切かは、実際に状況と探しているセマンティクスに依存します。


正確に。型システムを破壊するnullので、本当に悪です。(確かに、少なくとも一部の言語では、この代替には冗長性に欠点があります。)
機械式カタツムリ

2

人間が読めるエラーメッセージをヌルポインターに添付することはできません。

(ただし、ログファイルにエラーメッセージを残すことができます。)

ポインタ引数のいずれかがnullであり、それは計算に許可されている場合、ポインタ演算を可能にするいくつかの言語/環境では、結果は次のようになり、無効非ヌルポインタ。(*)より強力に。

(*)これはCOMプログラミングで頻繁に発生します。インターフェイスメソッドを呼び出そうとしても、インターフェイスポインターがnullの場合、ゼロとはまったく異なり、まったくではなく、ほぼではない無効なアドレスが呼び出されます。


2

エラーを通知するためにNULL(または数値のゼロ、またはブール値のfalse)を返すことは、技術的にも概念的にも間違っています。

技術的には、戻り値が返される正確な時点で、戻り値をすぐにチェックする必要があります。20個のファイルを連続して開き、エラーシグナリングがNULLを返すことによって行われる場合、消費するコードは、読み取られた各ファイルを個別にチェックし、ループや同様の構造から抜け出す必要があります。これは、散らかったコードの完璧なレシピです。ただし、例外をスローしてエラーを通知することを選択した場合、消費するコードは、例外をすぐに処理するか、関数呼び出し間でも必要に応じてレベルを上げることができます。これにより、コードがずっときれいになります。

概念的に、ファイルを開いて何かがおかしくなった場合、値(NULLであっても)を返すのは間違っています。操作が完了しなかったため、返品するものがありません。NULLを返すことは、「ファイルを正常に読み取りました。ファイルに含まれているもの-何もありません」と概念的に同等です。それが表現したい場合(つまり、問題の操作の実際の結果としてNULLが理にかなっている場合)、必ずNULLを返しますが、エラーを通知する場合は、例外を使用します。

歴史的に、Cのようなプログラミング言語には言語に例外処理が組み込まれていないため、エラーがこのように報告されました。推奨される方法(長いジャンプを使用)は、少し複雑で直感に反します。

また、問題にはメンテナンスの側面もあります。例外を除き、障害を処理するために追加のコードを記述する必要があります。そうしないと、プログラムは早期にハードに失敗します(これは良いことです)。エラーを通知するためにNULLを返す場合、プログラムのデフォルトの動作は、エラーを無視して、他の問題(破損したデータ、セグメンテーション違反、NullReferenceExceptions、言語に応じて)に至るまで続行します。エラーを早期に大声で通知するには、余分なコードを記述し、何を推測する必要があります。それは、締め切りが厳しいときに除外される部分です。


1

すでに指摘したように、多くの言語は、nullポインターの逆参照をキャッチ可能な例外に変換しません。それを行うことは、比較的現代的なトリックです。nullポインターの問題が最初に認識されたとき、例外はまだ発明されていませんでした。

NULLポインターを有効なケースとして許可する場合、それは特別なケースです。多くの場合、多くの異なる場所で、特別な場合の処理​​ロジックが必要です。それは余分な複雑さです。

nullポインターの可能性があるかどうかに関係なく、例外スローを使用して例外的なケースを処理しない場合は、それらの例外的なケースを別の方法で処理する必要があります。通常、すべての関数呼び出しは、関数呼び出しが不適切に呼び出されるのを防ぐため、または関数が終了したときに失敗のケースを検出するために、これらの例外的なケースをチェックする必要があります。これは、例外を使用して回避できる余分な複雑さです。

通常、複雑さが増すとエラーが増えます。

データ構造でヌルポインターを使用する代わりに(たとえば、リンクリストの開始/終了をマークする)、センチネルアイテムを使用することもできます。これらは、同じ機能をはるかに少ない複雑さで提供できます。ただし、複雑さを管理する方法は他にもあります。1つの方法は、nullチェックが1か所でのみ必要になるように、null可能性のあるポインターをスマートポインタークラスにラップすることです。

ヌルポインターが検出されたらどうしますか?例外ケース処理を組み込むことができない場合は、常に例外をスローし、その特殊ケース処理を呼び出し元に効果的に委任できます。そして、それはまさに、いくつかの言語がデフォルトでnullポインターを間接参照するときに行うことです。


1

C ++固有ですが、C ++のnullセマンティクスはポインター型に関連付けられているため、null 参照は回避されます。ファイルを開く関数が失敗し、nullポインターを返すことは非常に合理的です。実際、fopen()関数はまさにそれを行います。


実際、C ++でnull参照を使用していることに気付いたのは、プログラムが既に壊れているためです。
カズドラゴン

1

言語に依存します。

たとえば、Objective-Cを使用すると、問題なくnull(nil)オブジェクトにメッセージを送信できます。nilを呼び出すと、nilも返され、言語機能と見なされます。

あなたはその振る舞いに依存し、それらすべての複雑なネストされたif(obj == null)構造を避けることができるので、私は個人的にそれが好きです。

例えば:

if (myObject != nil && [myObject doSomething])
{
    ...
}

以下に短縮できます。

if ([myObject doSomething])
{
    ...
}

つまり、コードが読みやすくなります。


0

あなたがそれを文書化する限り、nullを返すか例外をスローするかどうかについては気にしません。


また、名前を付けます:GetAddressMaybeまたはGetSpouseNameOrNull
-rwong

-1

Null参照は通常、プログラムロジック内の何かが失われたために発生します。つまり、そのコードブロックに必要なセットアップを経ることなくコード行に到達しました。

一方、何かに対して例外をスローすると、プログラムの通常の操作で特定の状況が発生する可能性があることを認識し、処理されていることを意味します。


2
ヌル参照は、多くの理由で「発生」する可能性がありますが、見落とされているものに関連するものはほとんどありませ。また、null参照例外と混同しているかもしれません。
アーロンノート

-1

ヌル参照は非常に便利なことがよくあります。たとえば、リンクリストの要素には、何らかの後継要素がある場合とない場合があります。null「後継者なし」に使用することは完全に自然です。または、人は配偶者を持つことも持たないこともできます- null「配偶者がいない」の使用は完全に自然で、Person.Spouseメンバーが参照できる「配偶者がいない」特別な値を持つよりもはるかに自然です。

しかし:多くの値はオプションではありません。典型的なOOPプログラムでは、参照の半分以上がnull初期化後にできなくなると、プログラムが失敗します。そうしないと、コードをif (x != null)チェックで埋める必要があります。では、デフォルトですべての参照をnull可能にする必要があるのはなぜですか?それは本当に逆でなければなりません:変数はデフォルトでnull不可であるべきであり、あなたは明示的に「ああ、この値もである可能性がnullあります」と言わなければなりません。


このディスカッションから回答に追加したいことはありますか?このコメントスレッドは少し熱くなったので、これを整理したいと思います。どんな長い議論もチャットに取られるべきです。

そのような論争の真っactual中に、実際の関心の1つまたは2つのポイントがあった可能性があります。あいにく、私は拡張された議論を切り詰めるまでマークのコメントを見ませんでした。コメントを確認して回答を適切に編集する時間があるまでコメントを保持したい場合は、今後、モデレーターの注意を引くために回答にフラグを付けてください。
ジョシュK

-1

あなたの質問は紛らわしく表現されています。null参照例外を意味しますか(これは実際にde参照null)?そのタイプの例外を望まない明確な理由は、プログラムのどの時点でも値がnullに設定された可能性がある場合、または何が間違っていたかについての情報を提供しないことです。「存在しないファイルへの読み取りアクセスをリクエストした場合、例外またはnull参照を取得できてうれしいです」と書いていますが、原因を示さないものを取得しても満足できないはずです。 。「nullを逆参照しようとした」という文字列のどこにも、読み取りまたは存在しないファイルに関する言及はありません。おそらく、読み取り呼び出しから戻り値としてnullを取得することは完全に幸せであることを意味します-それはまったく異なる問題ですが、それでも読み取りが失敗した理由に関する情報を提供しません。それ'


いいえ、null参照例外を意味するものではありません。私が知っているすべての言語では、初期化されていないが宣言された変数はnullの形式です。
-davidk01

しかし、あなたの質問は初期化されていない変数に関するものではありません。また、初期化されていない変数にnullを使用しない言語もありますが、代わりに、オプションで値を含むことができるラッパーオブジェクト(ScalaやHaskellなど)を使用します。
ジムバルター

1
「...代わりに、オプションで値を含むことができるラッパーオブジェクトを使用します。」これは明らかに、null許容型のようなものではありません
アーロンノート

1
haskellには、初期化されていない変数などはありません。IORefを宣言し、初期値としてNoneをシードすることもできますが、これは他の言語で変数を宣言し、初期化せずに残しておくのとほぼ同じです。IOモナドhaskellプログラマ以外の純粋に機能的なコアで作業する場合、参照型に頼ることはないため、null参照の問題はありません。
davidk01

1
-あなたが値を「行方不明」の例外を持っている場合、それは正確に例外「ヌル」値と同じである場合は、あなたがそれを処理するために利用できるのと同じツールを持っています。それは重要な「if」です。どちらの方法でもそのケースを処理するには、余分な複雑さが必要です。Haskellでは、パターンマッチングと型システムにより、その複雑さを管理することができます。ただし、他の言語の複雑さを管理するための他のツールがあります。例外はそのようなツールの1つです。
Steve314
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.