web-dev-qa-db-ja.com

メソッドの入力値が範囲外の場合、例外をスローする必要がありますか?

メソッドの入力値が範囲外の場合、例外をスローする必要がありますか?例えば

//no imaginary numbers
public int MySquareRoot(int x)
{
    if (x<0)
    {
    throw new ArgumentOutOfBoundsException("Must be a non-negative integer"); 
    }

    //our implementation here 

}

現在、このメソッドは負でない数で呼び出されるべきではありませんが、ちょっとプログラマーは間違いを犯します。ここで例外をスローすることは正しいことですか?

16
dwjohnston

はい、私はあなたが例外をスローするべきだと思います。仲間のプログラマーがコンパイル時にエラーに気付くのを助けるために、throws ArgumentOutOfBoundsExceptionをメソッド宣言で使用します。

つまり、プロジェクトで虚数を使用しない限り、-1は平方根メソッドの完全に有効な引数です。 :)

20

例外条件が発生しないようにメソッドまたはそのシグネチャをコーディングできる場合そうするのが間違いなく最良のアプローチです。

たとえば、あなたが挙げた特定の例で、あなたの言語には適切な範囲の符号なし整数データ型がありますか?その場合、対応する符号付き整数型ではなくその型を使用し、渡された値が0未満の場合は例外をスローします。その場合、整形式プログラムのその位置で負の値を使用できません。コンパイラは実行前に間違いをキャッチし、少なくとも警告を発行します(明示的にバイパスする方法を逸脱しない限り、(署名付き/署名なしの型の不一致がある場合))そのセーフティネット;そして、値がその関数の許容範囲内にあることを確認せずに、メソッドシグネチャが符号なし整数のみを示す関数に符号付き変数を強制する場合、プログラマーはそうすることで例外がスローされるに値すると主張します彼らの顔に。

別の例として、妥当に小さい値のセットのみが入力として有効である場合、それらは、可能な各値をエンコードするためにプレーン整数値を使用するのではなく、特定の値のセットを持つ列挙として表現できますか?たとえ内部で列挙型がintに崩壊したとしても、列挙型を使用することでプログラマのミスから保護されます。

できなかった場合整形式プログラムで例外条件が発生しないようにメソッドシグネチャを記述できる場合、これは正確には ArgumentOutOfRangeExceptionIllegalArgumentException および同様の例外タイプが意図されています。これは簡単にTDDワークフローに組み込むこともできます。 エラーが何であるかを明確にすることを忘れないでください;特にソースコードに簡単にアクセスできない場合は、非特定のArgumentOutOfRangeExceptionが返されます非常にイライラします。これらの例外タイプのドキュメントに注意してください。

ArgumentOutOfRangeException(.NET):「引数の値が、呼び出されたメソッドで定義された値の許容範囲外である場合にスローされる例外。」 ( ここに見られるように

IllegalArgumentException(Java):「メソッドに不正または不適切な引数が渡されたことを示すためにスローされました。」 ( ここに見られるように

例外条件を最初から不可能にするメソッドを作成すると、実行時に例外をスローする以上の効果がありますが、実際にはこれが不可能な場合もあることに注意する必要があります。ただし、実行時に単一のパラメーター値の範囲に直接関連しない不変条件を確認する必要があります。たとえば、x + y> 20はエラーですが、xとyはそれぞれ20以下であれば何でもかまいませんが、メソッドシグネチャで簡単に表現できる方法はないと思います。

15
a CVn

「エラーデータの代わりに例外を使用する必要がありますか?」ではなく、「入力データを検証する必要がありますか?」と質問を理解しました。

外部からの入力パラメーターを受け入れる場合-呼び出しパラメーターを持つメソッド、サービス呼び出しからのXML入力データ、またはユーザーフィールドを持つWebページ-基本的に2つの選択肢があります。

  1. 入力データが予期されるフォーマットと一致するかどうかを厳密にチェックする
  2. 正しいデータでのみあなたを呼び出すようにあなたの発信者を信頼してください

チェックを追加すると複雑さが増します

  1. 最初の開発者は、すべての有効な(または「おそらく無効」な)値を考え、適切なチェックを追加する必要があります。理想的には、これはメソッドの最初に実行されますが、後で実行する必要がある場合もあります(たとえば、「有効な入力値」が「データベースに実際に存在するオブジェクトのID」である場合)。
  2. メソッドの実際のロジックを理解しようとするさらなる開発者は、それが調査する行を追加するためです。
  3. すべての前提条件を確認するためのランタイムオーバーヘッド

一方、入力検証は価値あるメリットをもたらします:

  1. それらはすでに検証されているので、コーディングで仮定をより深く仮定することができます。
  2. さらに多くの開発者が読む必要があるかもしれませんが、入力データに関する仮定を明示的にすることもロジックの理解に役立ちます
  3. 破損したプログラムの状態(クラッシュ、データ破損、セキュリティの脆弱性)が発生しないようにします。

したがって、チェックを追加するという選択を行う必要がありますコンテキスト内メソッドが使用される場所。

  • WebサイトやパブリックAPIなどの信頼できない外部データに公開されていますか?次に、確実に検証を行います。
  • それは、外部コーディングによって保護された、よく呼ばれる内部ループのプライベートメソッドですか?そうすれば、冗長なチェックが不要になるでしょう。期待(JavaDocなど)を文書化することもできますが、実際にはめったに行われません。
  • 一部の言語は、条件付きでコンパイルされたアサーションを提供します。これは、デバッグビルドではプログラムロジックエラーをキャッチするために有効になりますが、生産的な使用ではパフォーマンスのために無効になります
  • ライブラリの場合、呼び出し元をどの程度信頼するかを決定する必要があります。特にライブラリのインバリアントが危険にさらされないようにしたい場合は、注意してエラーをチェックし、チェックを追加してください。ただし、速度に関連する機能については、別の方法で決定することもできます(それを文書化することもできます)。
  • 通常、混合が行われます。外部に面するメソッドの適切な検証、インターフェイスレベルの一部の検証、プログラムの内部動作ではほとんどありません(多くの場合NullPointerChecksのみ)。

しかし、質問が「例外は無効な入力データを通知する正しい方法ですか?」だった場合、答えは次のとおりですおそらくはい

Exceptionsはランタイムパフォーマンスにかなりのオーバーヘッドを課しますが、プログラムフローについての推論が大幅に容易になります。これにより、特にエラーが発生した場合に対処する必要があるため、プログラミングの誤り(セマンティックエラー)が減少します。無視されるとプログラムが終了するため、「安全に失敗」します。それらは「発生するはずのない状況」に理想的です。また、スタックトレースのようにメタデータを転送できます。

エラーコードは、軽量で高速ですが、メソッドの呼び出し元に明示的にチェックを強制します。これを怠ると、プログラムに欠陥が生じ、サイレントデータの破損、セキュリティホールから、プログラムが宇宙ロケットの中で実行されている場合のナイス花火までさまざまです。

私が個人的に例外の代わりに優先エラーコードを使用する1つの例は、JavaのString.parseInt()メソッドです。入力ソース(ユーザーなど)から数字以外の文字列を取得することは完全に予期されることではなく、どのような場合でも対処する必要があります。デフォルト値を使用する場合と同じくらい簡単な場合もあります。呼び出し元として、それが例外を引き起こすかどうかをデータでチェックする方法はありません(検証ロジックを自分で実装するのではないため)、通常のプログラムフローの一部として例外を生成し、try-catch-blocksを使用することがここでの唯一の選択肢です。

エラーコードは、帯域内と帯域外の両方になる可能性があることに注意してください。

  • インバンド:特別な(マジック)値がエラー条件のマーカーとして宣言されます。多くの場合、nullまたは-1がここで使用されます。 string-search-methods
  • 帯域外:別の値が返され、「エラーなし」(通常は0またはnull)または特定のエラーを示します。これは、/追加の戻りパラメーターとして(複数の戻り値をサポートする言語の場合-たとえば、Google GOは特にエラーに対してこれをサポートします)、または特別な「getError()」メソッドを呼び出す要件として行うことができます。
9
Zefiro

関数を呼び出すと、何かが期待されます。私が要求したことを実行できない場合は、通知を受ける必要があります。関数は何も実行したり、何か問題を実行したりしてはなりません。

特定の戻り値を返す(たとえば、浮動小数点のsqrt関数が不正な数値に対してNaNを返す可能性がある)、外部エラー変数を設定する、または例外をスローすることで、通知を受けることができます。 (最初または3番目が推奨されます。)

あなたの場合、xがゼロ未満の場合、関数は要求された機能を実行できません。引数が範囲外であることを示す例外をスローすることは、完全に受け入れ可能であり、有益です。

5
paul

この答えは のように、何かをする必要があります、例外は役に立ちます、フェイルファストなどに良いですなど。あなたの例の一番上のコメントより良いです。

ただし、代替手段があります。言語を正しく推測した場合:Debug.Assert(x > 0)およびContract.Requires( x > 0 );

ただし、 consensus は、リリースビルドでの例外の使用が最善のアプローチであると思われます。

アサートはデバッグを目的としているようで、例外を指定して出荷する方が便利です。また、コントラクトはまだ十分ではなく、どちらもパブリックメソッドには適していません。それは自明ではなく、簡単な要約には向いていません。そのページを読むことをお勧めします。

全体として、私は例外を優先する傾向がありますが、アサーションは内部メソッドにとってもまともな選択かもしれません。

4
Nathan Cooper

ここで、例外をスローするのが最善の選択肢である別の理由を追加したいと思います。ユニットテストとTDDです。

すべての成熟したテストフレームワークは、特定のメソッドが特定の入力を与えるとスローされることを表明できる必要があります。このような例外をチェックする単体テストを作成することで、メソッドの有効な入力と無効な入力を文書化すると同時に、無効な入力が実際に拒否されることを検証できます。

代わりにアサーション(または他のメカニズム)を使用している場合、これははるかに困難です。

4
lethal-guitar