web-dev-qa-db-ja.com

彼がnullを期待していない場合、nullをチェックする必要がありますか?

先週、アプリケーションのサービス層でnullを処理することについて激しい議論がありました。質問は.NETのコンテキストにありますが、Javaおよび他の多くのテクノロジーでも同じです。

問題は、常にnullをチェックし、コードが何に関係なく機能するようにするか、またはnullが予期せず受信されたときに例外をバブルアップさせるかということです。

一方では、期待していない場所(つまり、それを処理するユーザーインターフェイスがない場所)でnullをチェックすることは、私の見解では、空のcatchを使用してtryブロックを作成することと同じです。エラーを非表示にしているだけです。エラーは、コードで何かが変更され、nullが予期される値になったこと、または他のエラーがあり、間違ったIDがメソッドに渡されたことが考えられます。

一方、nullをチェックすることは、一般的に良い習慣です。さらに、チェックがある場合、アプリケーションは機能し続ける可能性がありますが、機能のほんの一部が影響を与えていません。次に、「ページXを開けません」などのより深刻なバグではなく、「コメントを削除できません」などの小さなバグを顧客が報告する場合があります。

あなたはどのような慣習に従っていますか?また、どちらのアプローチに対する賛成か反対か?

更新:

特定のケースに関する詳細を追加したいと思います。データベースからいくつかのオブジェクトを取得し、それらに対していくつかの処理を行いました(たとえば、コレクションを作成します)。コードを記述した開発者は、オブジェクトがnullになる可能性があることを想定していなかったため、チェックを含めず、ページが読み込まれたときにエラーが発生し、ページ全体が読み込まれませんでした。

明らかに、この場合、チェックがあったはずです。次に、処理されるすべてのオブジェクトをチェックする必要があるかどうか、それが欠落していないと想定されていないかどうか、および最終的な処理をサイレントに中止する必要があるかどうかについての議論に入りました。

架空の利点は、ページが引き続き機能することです。 Stack Exchangeの検索結果をさまざまなグループ(ユーザー、コメント、質問)で考えてください。このメソッドはnullをチェックしてユーザーの処理を中止できますが(バグのためnullです)、「コメント」セクションと「質問」セクションを返します。 「ユーザー」セクションが欠落していることを除いて、ページは機能し続けます(これはバグです)。早期に失敗してページ全体を壊すのか、それとも作業を続けて「ユーザー」セクションが欠落していることに誰かが気付くのを待つべきなのでしょうか。

119
Stilgar

nullを確認するか、ランタイムに例外をスローさせるかは、それほど問題ではありません。それはあなたがそのような予期しない状況にどう対応すべきかです。

次に、オプションは次のとおりです。

  • 一般的な例外(NullReferenceException)をスローし、バブルアップさせます。 nullを自分で行わない場合は、これが自動的に行われます。
  • より高いレベルで問題を説明するカスタム例外をスローします。これは、nullチェックをスローするか、NullReferenceExceptionをキャッチしてより具体的な例外をスローすることで実現できます。
  • 適切なデフォルト値に置き換えて回復します。

どちらが最善の解決策であるかについての一般的なルールはありません。個人的には、私は言うでしょう:

  • 一般的な例外は、エラー状態がコードの深刻なバグの兆候である場合に最適です。つまり、nullの値がそもそもそこに到達することを許可してはなりません。このような例外が発生すると、設定したエラーログにすべてが行き渡り、誰かに通知されてバグが修正されます。
  • 技術的に正しくないHTMLを受け入れ、賢明にレンダリングするために最善の努力をするWebブラウザーなど、正確性よりも半有用な出力の提供が重要である場合は、デフォルト値のソリューションが適しています。
  • それ以外の場合は、特定の例外を使用して、適切な場所で処理します。
109
tdammers

IMHOが予期しないnull値を処理しようとすると、コードが過度に複雑になります。 nullを期待しない場合は、ArgumentNullExceptionをスローして明確にします。値がnullかどうかを確認し、意味のないコードを記述しようとすると、私は本当にイライラします。同じことが、誰かが本当にSingleOrDefaultを期待しているときにSingle(またはさらに悪いことにはコレクションを取得する)を使用する場合、および人々がロジックを述べることを恐れている(私は本当に何を知っているのか)場合、他の多くの場合にも当てはまります。明らかに。

60
empi

私の経験でnullをチェックする習慣は、以前のCまたはC++開発者によるものです。これらの言語では、をチェックしないときに重大なエラーを非表示にする可能性が高くなりますNULLの場合。ご存知のように、JavaまたはC#では状況が異なります。nullをチェックせずにnull参照を使用すると、例外が発生します。したがって、エラーが秘密に隠されない限り、呼び出し側では無視しないでください。したがって、ほとんどの場合、nullを明示的にチェックしても意味がなく、コードが必要以上に複雑になっています。そのため、私はしないでください =これらの言語では、nullチェックを習慣にすることを検討してください(少なくとも、一般的にはそうではありません)。

もちろん、その規則には例外があります。ここに私が考えることができるものがあります:

  • プログラムのどの部分で誤ったnull参照が発生したかをユーザーに知らせる、より人間が読める形式のエラーメッセージが必要です。そのため、nullのテストは、異なるエラーテキストとともに、異なる例外をスローします。明らかに、エラーはそのようにマスクされません。

  • あなたはあなたのプログラムを「もっと早く失敗する」ようにしたいです。たとえば、コンストラクターパラメーターのnull値を確認する場合、オブジェクト参照はメンバー変数にのみ格納され、後で使用されます。

  • 後続の障害のリスクなしに、また重大なエラーを隠さずに、null参照の状況に安全に対処できます。

  • 現在のコンテキストで完全に異なる種類のエラーシグナリング(エラーコードを返すなど)が必要な場合

37
Doc Brown

assertsを使用して、コードの事前/事後条件と不変条件をテストして示します。

これにより、コードが何を期待し、何を処理することが期待できるかを理解しやすくなります。

次の理由により、アサートIMOは適切なチェックです。

  • 効率的(通常、リリースでは使用されません)、
  • 簡単(通常は1行)および
  • クリア(前提条件違反、これはプログラミングエラーです)。

自己文書化するコードは重要なので、チェックすることは良いことだと思います。防御的でフェイルファストのプログラミングにより、通常はほとんどの時間が費やされるメンテナンス方法が簡単になります。

更新

あなたの手の込んだこと。通常、バグが発生しても機能する、つまり可能な限り多くのアプリケーションをエンドユーザーに提供することをお勧めします。堅牢性は良いです。なぜなら、天国は重要な瞬間に何が壊れるかを知っているだけだからです。

ただし、開発者とテスターはバグをできるだけ早く認識している必要があるため、問題が発生したことをユーザーに警告するフックを備えたログフレームワークがおそらく必要です。アラートはおそらくすべてのユーザーに表示されるはずですが、ランタイム環境に応じて異なる方法で調整することができます。

15
Macke

早く失敗し、頻繁に失敗します。 nullは、使用しようとするとすぐに失敗する可能性があるため、予期しない最高の値の1つです。他の予期しない値は、検出がそれほど簡単ではありません。 nullを使用しようとすると、自動的に失敗するので、明示的なチェックは必要ありません。

8
DeadMG
  • Nullのチェックは2番目の性質であるべきです

  • あなたのAPIは他の人々によって使用され、彼らの期待はあなたのものとは異なる可能性があります

  • 完全な単体テストは、通常、null参照チェックの必要性を浮き彫りにします

  • Nullのチェックは、条件付きステートメントを意味するものではありません。 nullチェックによってコードが読みにくくなることが心配な場合は、.NETコードコントラクトを検討することをお勧めします。

  • ReSharper(または同様のもの)をインストールして、コードでnull参照チェックがないことを検索することを検討してください

6
CodeART

私は、セマンティクスの観点から質問を検討します。つまり、NULLポインターまたはnull参照引数が何を表すかを自分自身に問いかけます。

関数またはメソッドfoo()にT型の引数xがある場合、xは必須またはオプションのいずれかになります。

C++では必須引数を

  • 値。 void foo(T x)
  • 参照、例えばvoid foo(T&x)。

どちらの場合も、NULLポインターをチェックする問題はありません。そのため、多くの場合、必須の引数については、NULLポインタチェックまったくは必要ありません。

オプション引数を渡す場合は、ポインターを使用し、NULL値を使用して、値が指定されなかったことを示すことができます。

void foo(T* x)

別の方法は、次のようなスマートポインタを使用することです。

void foo(shared_ptr<T> x)

この場合、xが値を含むか、まったく値を含まないかによって、メソッドfoo()に違いが生じるため、コード内のポインターを確認する必要があるでしょう。

3番目と最後のケースは、必須引数にポインタを使用する場合です。この場合、x == NULLでfoo(x)を呼び出すことはerrorであり、これを処理する方法を決定する必要があります(範囲外インデックスまたは無効な入力の場合と同じ) :

  1. チェックなし:バグがある場合はクラッシュさせて、このバグが(テスト中)十分早く表示されるようにしてください。表示されない場合(十分に早く失敗しなかった場合)は、アプリケーションのクラッシュが発生するというユーザーエクスペリエンスが発生するため、運用システムに表示されないことを期待してください。
  2. メソッドの先頭ですべての引数を確認し、無効なNULL引数を報告します。 foo()から例外をスローし、他のメソッドに処理させます。おそらく最善の方法は、アプリケーションに内部エラーが発生して閉じようとしていることを通知するダイアログウィンドウをユーザーに表示することです。

2番目のアプローチの利点は、より良い失敗の方法(ユーザーに詳細な情報を提供する)とは別に、バグが見つかる可能性が高くなります。メソッドが間違った引数で呼び出されるたびにプログラムが失敗しますが、アプローチ1バグは、ポインターを逆参照するステートメントが実行された場合にのみ表示されます(たとえば、ifステートメントの右側の分岐が入力された場合)。したがって、アプローチ2は、「早期に失敗」のより強力な形式を提供します。

Javaでは、すべてのオブジェクトは常にnullになる可能性がある参照を使用して渡されるため、選択肢が少なくなります。ここでも、nullがオプションの引数のNONE値を表す場合、nullをチェックすると(おそらく)実装ロジックの一部になります。

Nullが無効な入力である場合、エラーが発生します。C++ポインターの場合と同様の考慮事項を適用します。 Javaでは、nullポインタ例外をキャッチできるため、メソッドfoo()がすべての実行パスのすべての入力引数を逆参照する場合、上記のアプローチ1はアプローチ2と同等です(小さなメソッドではおそらく非常に頻繁に発生します)。 。

要約

  • C++とJavaの両方で、オプションの引数がnullかどうかのチェックはプログラムのロジックの一部です。
  • C++では、常に必須の引数をチェックして、適切な例外がスローされ、プログラムがそれを確実に処理できるようにします。
  • Java(またはC#)で、より強力な形式の「早期に失敗する」ためにスローされない実行パスがあるかどうかを確認します必須の引数

[〜#〜]編集[〜#〜]

詳細についてはスティルガーに感謝します。

あなたの場合、メソッドの結果としてnullがあるようです。繰り返しになりますが、決定を下す前に、まずメソッドのセマンティクスを明確化(および修正)する必要があると思います。

したがって、メソッドm2()を呼び出すメソッドm1()があり、m2()が何らかのオブジェクトへの参照(Javaの場合)またはポインタ(C++の場合)を返します。

M2()のセマンティクスは何ですか? m2()は常にnull以外の結果を返す必要がありますか?この場合、nullの結果は内部error(in m2())になります。 m1()の戻り値を確認すると、より堅牢なエラー処理を行うことができます。そうでない場合は、遅かれ早かれ、おそらく例外が発生します。そうでないかもしれない。例外はデプロイ後にではなくテスト中にスローされることを願っています。 質問:m2()がnullを返さない場合、nullを返すのはなぜですか?おそらくそれは代わりに例外をスローするべきですか? m2()はおそらくバグがあります。

別の方法として、nullを返すことはメソッドm2()のセマンティクスの一部です。つまり、オプションの結果があり、メソッドのJavadocドキュメントにドキュメント化する必要があります。この場合、null値を指定しても問題ありません。メソッドm1()はそれを他の値としてチェックする必要があります。ここではエラーはありませんが、結果がnullかどうかのチェックはプログラムロジックの一部にすぎません。

4
Giorgio

私の一般的なアプローチは、処理方法がわからないエラー条件をテストしないことです。次に、特定の各インスタンスで回答する必要がある質問は、次のようになります。予期しないnull値が存在する場合に、妥当な何かを行うことができますか

別の値(空のコレクション、たとえば、またはデフォルト値またはインスタンス)を代入して安全に続行できる場合は、必ずそうしてください。 @ ShahbazのWorld of Warcraftの例 ある画像を別の画像に置き換える方法は、そのカテゴリに分類されます。画像自体はおそらく単なる装飾であり、機能的な影響はありません。 (デバッグビルドでは、スクリーミングカラーを使用して置換が発生したという事実に注意を向けますが、それはアプリケーションの性質に大きく依存します。)エラーの詳細をログに記録すると、発生していることがわかります。それ以外の場合は、おそらく知りたいエラーを非表示にします。ユーザーから非表示にすることは1つのことです(ここでも、処理が安全に続行できると想定しています)。開発者からそれを隠すことは全く別です。

Null値が存在する状態で安全に続行できない場合は、実用的なエラーで失敗します。一般に、操作が成功しないことが明らかな場合は、できるだけ早く失敗することを好みます。つまり、前提条件のチェックを意味します。すぐに失敗させたくないが、失敗はできるだけ延期したい場合があります。この考慮事項は、たとえば、暗号化関連のアプリケーションで発生します。この場合、サイドチャネル攻撃は、知らないうちに情報を漏らしてしまう可能性があります。最後に、エラーをいくつかの一般的なエラーハンドラーに送ってください。これにより、関連する詳細がログに記録され、ニース(ただしニースであってもかまいません)エラーメッセージがユーザーに表示されます。

また、テスト/ QA /デバッグビルドと本番/リリースビルドでは、適切な応答が大きく異なる場合があることにも注意してください。デバッグビルドでは、非常に詳細なエラーメッセージを使用して早期にハードに失敗し、問題があることを完全に明らかにし、事後分析で修正のために何を実行する必要があるかを判断できるようにします。 安全に回復可能なエラーに直面した場合、本番ビルドはおそらく回復を優先するはずです。

4
a CVn

他の回答が指摘したように、NULLを期待しない場合は、ArgumentNullExceptionをスローして明確にします。私の見解では、プロジェクトをデバッグしているときこれは、プログラムのロジックの障害をより早く発見するのに役立ちます。

したがって、ソフトウェアをリリースする予定です。NullRefrencesチェックを復元すると、ソフトウェアのロジックに何も見落とさず、重大なバグがポップアップしないことを確認できます。

もう1つのポイントは、NULLチェックが関数のパラメーターに対して行われる場合、関数がそれを行うのに適切な場所かどうかを判断できるということです。そうでない場合は、関数へのすべての参照を見つけてから、パラメーターを関数に渡す前に、null参照につながる可能性のあるシナリオを確認してください。

2
Ahmad

確かにNULL期待されていませんが、常にバグがあります!

私はあなたを信じていますすべき期待しないかどうかにかかわらず、NULLを確認してください。例を挙げましょう(ただしJavaにはありません)。

  • World of Warcraftでは、バグが原因で、開発者の1人のイメージが多くのオブジェクトの立方体に表示されました。グラフィックエンジンがexpectNULLポインタを使用しなかった場合、クラッシュが発生し、それほど致命的ではない(おもしろい)エクスペリエンスになったと想像してください。ユーザーのために。少なくとも、接続やセーブポイントが予期せず失われることはありません。

    これは、NULLのチェックがクラッシュを防ぎ、ケースが黙って処理された例です。

  • 銀行窓口プログラムを想像してみてください。インターフェイスはいくつかのチェックを行い、送金を実行するために入力データを基になる部分に送信します。コア部分NULLを受信しないことを期待している場合、インターフェースのバグがトランザクションに問題を引き起こし、途中のお金が失われる可能性があります(または悪いことに、複製)。

    「問題」とは、(crashクラッシュ(たとえば、プログラムがC++で記述されている)のように)クラッシュを意味し、nullポインタ例外ではありません。この場合、NULLが検出された場合、トランザクションはロールバックする必要があります(もちろん、変数をNULLに対してチェックしなかった場合、これは不可能です)。

きっとあなたはそのアイデアを理解するでしょう。

関数の引数の有効性をチェックしても、コードが煩雑になることはありません。これは、関数の上部で(途中で広がることなく)いくつかのチェックが行われ、堅牢性が大幅に向上するためです。

「プログラムが失敗したことをユーザーに通知し、正常にシャットダウンするか、一貫性のある状態にロールバックしてエラーログを作成する」と「アクセス違反」のどちらかを選択する必要がある場合、最初のエラーを選択しませんか?つまり、すべての関数は期待するすべてが正しいというよりは、バグのあるコードから呼び出されるように準備する必要があります。

2
Shahbaz

システム内のすべてのnullは、発見されるのを待っている時限爆弾です。コードが制御できないコードと相互作用する境界でnullを確認し、独自のコード内でnullを禁止します。これにより、単純に外部コードを信頼せずに問題を取り除くことができます。代わりに、例外またはある種のMaybe/Nothingタイプを使用してください。

1
Doval

Nullポインタ例外は最終的にスローされますが、nullポインタを取得したポイントから遠くにスローされることがよくあります。できるだけ早くヌルポインタをGitしたという事実をログに記録して、バグを修正するのにかかる時間を短縮してください。

今何をすべきかわからない場合でも、イベントをログに記録すると、後で時間を節約できます。そして、おそらくチケットを取得した人は、節約された時間を使用して適切な応答を把握することができます。

0
Jon Strayer