無効なユーザー入力をどのように処理すればよいですか?


12

私はしばらくこの問題について考えていましたが、他の開発者から意見を聞きたいと思います。

私は非常に防御的なスタイルのプログラミングをする傾向があります。私の典型的なブロックまたはメソッドは次のようになります。

T foo(par1, par2, par3, ...)
{
    // Check that all parameters are correct, return undefined (null)
    // or throw exception if this is not the case.

    // Compute and (possibly) return result.
}

また、計算中に、参照解除する前にすべてのポインターをチェックします。私の考えは、バグがあり、どこかにNULLポインターが表示される場合、私のプログラムはこれをうまく処理し、単純に計算の継続を拒否するということです。もちろん、ログまたはその他のメカニズムのエラーメッセージで問題を通知できます。

もっと抽象的に言えば、私のアプローチは

if all input is OK --> compute result
else               --> do not compute result, notify problem

他の開発者、中でも私の同僚は、別の戦略を使用しています。たとえば、ポインタをチェックしません。彼らは、コードの一部に正しい入力が与えられるべきであり、入力が間違っている場合に何が起こるかについて責任を負うべきではないと想定しています。また、NULLポインター例外がプログラムをクラッシュさせた場合、テスト中にバグがより簡単に発見され、修正される可能性が高くなります。

これに対する私の答えは通常です。しかし、テスト中にバグが見つからず、製品が顧客によって既に使用されているときにバグが表示された場合はどうなりますか?バグが顕在化する好ましい方法は何ですか?特定のアクションを実行しないが、それでも動作を継続できるプログラム、またはクラッシュして再起動が必要なプログラムでしょうか?

要約する

間違った入力を処理する2つのアプローチのうち、どちらを勧めますか?

Inconsistent input --> no action + notification

または

Inconsistent input --> undefined behaviour or crash

編集

回答と提案をありがとう。私も契約によるデザインのファンです。しかし、メソッドを呼び出すコードを書いた人(おそらく自分自身)を信頼していても、バグが存在する可能性があり、誤った入力につながります。したがって、私のアプローチは、メソッドに正しい入力が渡されると決して想定しないことです。

また、メカニズムを使用して問題をキャッチし、それについて通知します。開発システムでは、たとえばダイアログを開いてユーザーに通知します。本番システムでは、ログに情報を書き込むだけです。余分なチェックがパフォーマンスの問題につながるとは思いません。実稼働システムでアサーションがオフになっている場合、アサーションが十分かどうかはわかりません。テスト中に発生しなかった状況が実稼働で発生する可能性があります。

とにかく、多くの人が反対のアプローチを取っていることに本当に驚きました。テスト中にバグを見つけやすくすることを維持しているため、アプリケーションを「意図的に」クラッシュさせます。


常に防御的にコーディングします。最終的に、パフォーマンス上の理由から、リリースモードの一部のテストを無効にするスイッチを配置できます。
deadalnix

今日、私はNULLポインターチェックの欠落に関連するバグを修正しました。アプリケーションのログアウト中にオブジェクトが作成され、コンストラクターはゲッターを使用して、存在しない他のオブジェクトにアクセスしました。オブジェクトはその時点で作成されることを意図していませんでした。別のバグが原因で作成されました:ログアウト中にタイマーが停止しませんでした->シグナルが送信されました->受信者がオブジェクトを作成しようとしました->コンストラクターが照会され、別のオブジェクトを使用しました-> NULLポインター->クラッシュ)。このような厄介な状況でアプリケーションがクラッシュするのは本当に嫌です。
ジョルジオ

1
修理のルール:失敗する必要がある場合は、うるさく、できるだけ早く失敗します。
-deadalnix

「修理のルール:失敗しなければならない場合、うるさく、できるだけ早く失敗します。」:これらのWindowsのBSODはすべて、このルールの適用だと思います。:
ジョルジオ

回答:


8

あなたはそれを正しく持っています。妄想してください。自分のコードであっても、他のコードを信頼しないでください。物事を忘れ、変更を加え、コードが進化します。外部コードを信頼しないでください。

上記の良い点は、入力は無効だがプログラムがクラッシュしない場合はどうでしょうか?次に、データベース内のガベージとエラーを取得します。

数字(たとえば、ドルでの価格またはユニット数)を求められたら、「1e9」と入力してコードが何をするのかを確認します。有りうる。

40年前、UCBerkeleyでコンピューターサイエンスの学士号を取得し、良いプログラムは50%のエラー処理であると言われました。妄想してください。


はい、私見はこれがパラノイアであることは機能であり問題ではない数少ない状況の1つです。
ジョルジオ

「入力は無効であるが、プログラムがクラッシュしない場合はどうなるでしょうか。その後、データベースにガベージが発生し、エラーが発生します。」:クラッシュする代わりに、プログラムの実行を拒否して未定義の結果を返します。未定義は計算を通じて伝播し、ガーベッジは生成されません。ただし、これを実現するためにプログラムをクラッシュさせる必要はありません。
ジョルジオ

はい、しかし-私のポイントは、プログラムが無効な入力を検出し、それに対処しなければならないということです。入力がチェックされていない場合、システムへの入力方法が機能し、後で厄介なことが起こります。クラッシュすればそれよりも優れています!
アンディキャンフィールド

私はあなたに完全に同意します。私の典型的な方法または機能は、入力データが正しいことを確認するための一連のチェックから始まります。
ジョルジオ

今日、私は再び「すべてをチェックし、何も信頼しない」戦略が良いアイデアであることを確認しました。私の同僚は、チェックが欠落しているためにNULLポインター例外がありました。そのコンテキストでは、一部のデータがロードされていないため、NULLポインターを持つことが正しいことがわかりました。また、ポインターをチェックし、NULLの場合は何もしないことが正しいことがわかりました。:
ジョルジオ

7

あなたはすでに正しい考えを持っています

間違った入力を処理する2つのアプローチのうち、どちらを勧めますか?

一貫性のない入力->アクションなし+通知

またはそれ以上

一貫性のない入力->適切に処理されたアクション

プログラミングにクッキーカッターアプローチを実際に使用することはできません(可能性はあります)が、意識的な選択からではなく習慣から物事を行う定型的なデザインになります。

プラグマティズムによる独断的な独断。

スティーブ・マッコネルが一番いいと言った

Steve McConnellは、防衛プログラミングに関する本(Code Complete)をほぼ執筆しました。これは、入力を常に検証する必要があることをアドバイスした方法の1つです。

スティーブがこれについて言及したかどうかは思い出せませんが、プライベートでないメソッドと関数、および必要と思われる他のメソッドとのみに対してこれを行うことを検討する必要があります。


2
パブリックではなく、アクセス制限の保護、共有、または概念のない言語(すべてが暗黙的に暗黙的に公開されている言語)を防御的にカバーするすべての非プライベートメソッドをお勧めします。
-JustinC

3

ここでは、「言語」、コードの種類、およびコードが入る可能性のある製品の種類を指定しない限り、「正しい」答えはありません。考慮してください:

  • 言語が重要です。Objective-Cでは、多くの場合、nilにメッセージを送信しても問題ありません。何も起こりませんが、プログラムはクラッシュしません。Javaには明示的なポインターがないため、nilポインターは大きな問題ではありません。Cでは、もう少し注意する必要があります。

  • 妄想的であるとは、不合理で不当な疑惑や不信感です。それはおそらく、人にとってよりもソフトウェアにとって良いことではありません。

  • 懸念のレベルは、コード内のリスクのレベルと、発生する問題を特定する可能性が高いことと釣り合っている必要があります。最悪の場合はどうなりますか?ユーザーはプログラムを再起動し、中断したところから続行しますか?会社は数百万ドルを失いますか?

  • 不正な入力を常に特定できるとは限りません。ポインタをnilと宗教的に比較することはできますが、2 ^ 32の可能性のある値のうち1つしか検出できず、そのほとんどが不良です。

  • エラーを処理するためのさまざまなメカニズムがあります。繰り返しますが、それは言語にある程度依存します。アサートマクロ、条件ステートメント、単体テスト、例外処理、注意深い設計、およびその他の手法を使用できます。それらのどれも絶対確実ではなく、すべての状況に適切なものはありません。

だから、それは主にあなたが責任を置きたい場所に要約されます。他の人が使用するライブラリを作成している場合は、取得する入力について合理的にできるだけ注意を払い、可能な場合は有用なエラーを出力するように最善を尽くす必要があります。独自のプライベート関数とメソッドでは、アサーションを使用して愚かな間違いをキャッチしますが、それ以外の場合は呼び出し元(つまり、あなた)にゴミを渡さないように責任を負わせます。


+1-良い答え。私の主な関心事は、間違った入力が本番システムに現れる問題を引き起こす可能性があることです(それについて何かをするのが遅すぎる場合)。もちろん、そのような問題がユーザーに与える損害に依存すると言うのは、あなたはまったく正しいと思います。
ジョルジオ

言語が大きな役割を果たします。PHPでは、メソッドコードの半分で変数の型を確認し、適切なアクションを実行します。Javaでは、メソッドがintを受け入れる場合、他に何も渡すことができないため、メソッドはより明確になります。
チャップ

1

スローされた例外などの通知が必ずあるはずです。入力したコードが不正であるか、エラーが発生するという、あなたが書いたコードを誤用している可能性のある他のコーダー(意図しない目的で使用しようとしている)に役立ちます。これはエラーを追跡するのに非常に役立ちますが、単にnullを返す場合、結果を使用して別のコードから例外を取得しようとするまでコードは続行されます。

特定のコードの範囲を超えている他のコードの呼び出し中(おそらくデータベースの更新の失敗)にコードにエラーが発生した場合、それを制御することはできず、唯一の手段は何を説明する例外をスローすることですあなたが知っている(あなたが呼び出したコードによって言われたことだけ)。特定の入力が必然的にそのような結果につながることがわかっている場合は、コードの実行を煩わせずに、どの入力が無効で、なぜなのかを示す例外をスローすることはできません。

よりエンドユーザー関連のメモでは、説明的でシンプルなものを返して、誰でも理解できるようにするのが最善です。クライアントが「プログラムがクラッシュしたのでそれを修正してください」と電話した場合、何が問題で何が原因であるかを追跡し、問題を再現できると期待しています。例外の適切な処理を使用すると、クラッシュを防ぐだけでなく、貴重な情報を提供できます。「プログラムからエラーが発生しました。「XYZはメソッドMの有効な入力ではありません。Zが大きすぎるためです」と言っているクライアントからの呼び出し、またはその意味がわからなくてもどこを見るかを正確に知っている。さらに、あなたの会社のビジネス慣行によっては、これらの問題を修正することすらできないかもしれないので、良い地図を残すことをお勧めします。

したがって、私の答えの短いバージョンは、あなたの最初のオプションが最良であるということです。

Inconsistent input -> no action + notify caller

1

プログラミングの大学の授業を受けながら、この同じ問題に苦労しました。私は偏執的な側に傾いて、すべてをチェックする傾向がありますが、これは見当違いの行動であると言われました。

私たちは「契約による設計」を教えられていました。重要な点は、前提条件、不変条件、および事後条件をコメントおよび設計文書で指定することです。コードの私の部分を実装する人として、ソフトウェアアーキテクトを信頼し、前提条件(メソッドで処理できる入力と送信されない入力)を含む仕様に従うことで権限を与える必要があります。 。すべてのメソッド呼び出しで過剰なチェックを行うと、膨張します。

アサーションは、ビルドの反復中に使用して、プログラムの正確性(前提条件、不変条件、事後条件の検証)を検証する必要があります。アサーションは、プロダクションコンパイルでオフになります。


0

もちろん、「アサーション」を使用すると、仲間の開発者に間違っていることを通知することができます。もちろん「プライベート」な方法でのみです。これらを有効/無効にすることは、コンパイル時に追加/削除するフラグの1つにすぎないため、実稼働コードからアサーションを簡単に削除できます。かどうかを知るための素晴らしいツールもありますが、何らかの形で、独自の方法で間違ったことをやっているが。

public / protectedメソッド内の入力パラメーターの検証に関しては、防御的に作業し、パラメーターをチェックしてInvalidArgumentExceptionなどをスローすることを好みます。それがここにある理由です。また、APIを作成しているかどうかによっても異なります。APIの場合、さらにはクローズドソースの場合は、すべてをより適切に検証して、開発者が何が問題なのかを正確に把握できるようにします。それ以外の場合、ソースが他の開発者に利用可能であれば、それは白黒ではありません。あなたの選択と一致しているだけです。

編集:たとえば、Oracle JDKを見ると、「null」をチェックしてコードがクラッシュしないことがわかります。とにかくNullPointerExceptionをスローするので、なぜnullをチェックして明示的な例外をスローするのかわからない。理にかなっていると思います。


Javaでは、nullポインター例外が発生します。C ++では、nullポインターがアプリケーションをクラッシュさせます。他の例があるかもしれません:ゼロ除算、範囲外のインデックスなど。
ジョルジオ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.