実行を停止してはならないいくつかのエラーを処理するためにerror
、クライアントがチェックして例外をスローするために使用できる変数があります。これは反パターンですか?これを処理するより良い方法はありますか?この動作の例については、PHPのmysqli APIをご覧ください。可視性の問題(アクセサ、パブリックスコープとプライベートスコープ、クラス内の変数はグローバルですか?)が正しく処理されると仮定します。
実行を停止してはならないいくつかのエラーを処理するためにerror
、クライアントがチェックして例外をスローするために使用できる変数があります。これは反パターンですか?これを処理するより良い方法はありますか?この動作の例については、PHPのmysqli APIをご覧ください。可視性の問題(アクセサ、パブリックスコープとプライベートスコープ、クラス内の変数はグローバルですか?)が正しく処理されると仮定します。
回答:
言語が本質的に例外をサポートしている場合、例外をスローすることをお勧めします。クライアントは、例外を発生させたくない場合に例外をキャッチできます。実際、コードのクライアントは例外を予期しており、戻り値をチェックしないため、多くのバグに遭遇します。
選択肢がある場合、例外を使用することにはかなりの利点があります。
メッセージ
例外には、開発者がデバッグに使用したり、必要に応じてユーザーに表示したりできる、ユーザーが読み取り可能なエラーメッセージが含まれています。消費するコードが例外を処理できない場合、常にログに記録できるため、開発者は他のすべてのトレースで停止して戻り値が何であるかを把握し、テーブルにマッピングして何が何であるかを把握する必要がありません実際の例外。
戻り値では、追加情報を簡単に提供することはできません。一部の言語は、最後のエラーメッセージを取得するためのメソッド呼び出しをサポートするため、この懸念は少し緩和されますが、呼び出し元が追加の呼び出しを行う必要があり、時にはこの情報を運ぶ「特別なオブジェクト」へのアクセスが必要になります。
例外メッセージの場合、次のような可能な限り多くのコンテキストを提供します。
ユーザーのプロファイルで参照されているユーザー「bar」の名前「foo」のポリシーを取得できませんでした。
これを戻りコード-85と比較してください。どっちがいい?
呼び出しスタック
また、例外には通常、コードをより迅速かつ迅速にデバッグするのに役立つ詳細なコールスタックがあり、必要に応じて呼び出し元のコードで記録することもできます。これにより、開発者は通常、正確な行に問題を特定できるため、非常に強力です。もう一度、これを戻り値(-85、101、0など)を含むログファイルと比較します。どちらを好むでしょうか。
フェイルファーストバイアスアプローチ
失敗した場所でメソッドが呼び出されると、例外がスローされます。呼び出しコードは、明示的に例外を抑制するか、失敗します。開発およびテスト中(および実稼働中)にコードがすぐに失敗し、開発者が修正することを余儀なくされるため、私はこれが実際に驚くべきものであることに気付きました。戻り値の場合、戻り値のチェックに失敗した場合、エラーは黙って無視され、バグは予想外のどこかで表面化します。通常、デバッグと修正に非常に高いコストがかかります。
例外のラッピングとアンラッピング
例外は他の例外の中にラップし、必要に応じてアンラップできます。たとえばArgumentNullException
、呼び出し元のコードUnableToRetrievePolicyException
で操作が失敗したため、呼び出し元のコードがラップする可能性があるコードがスローされる場合があります。上記の例に似たメッセージがユーザーに表示される場合がありますが、一部の診断コードは例外をラップ解除しArgumentNullException
、問題の原因であることがわかります。つまり、消費者のコードのコーディングエラーです。これにより、アラートが発生し、開発者がコードを修正できます。このような高度なシナリオは、戻り値を使用して実装するのは簡単ではありません。
コードのシンプルさ
これは説明するのが少し難しいですが、このコーディングを通して、戻り値と例外の両方を学びました。通常、戻り値を使用して記述されたコードは呼び出しを行い、戻り値が何であるかについて一連のチェックを行います。場合によっては、別のメソッドを呼び出し、そのメソッドからの戻り値に対する別の一連のチェックを行うようになります。例外がある場合、例外処理は、すべてではないにしてもほとんどの場合、はるかに簡単です。try / catch / finallyブロックがあり、クリーンアップのためにfinallyブロックのコードを実行するためにランタイムが最善を尽くしています。ネストされたtry / catch / finallyブロックでさえ、複数のメソッドからのネストされたif / elseおよび関連する戻り値よりも、フォローおよび保守が比較的簡単です。
結論
使用しているプラットフォームが例外(特にJavaや.NETなど)をサポートしている場合、これらのプラットフォームには例外をスローするためのガイドラインがあり、クライアントは期待しているため、例外をスローする以外に方法がないことを明確に想定する必要がありますそう。あなたのライブラリを使用していた場合、例外がスローされることを期待しているため、戻り値を確認することはありません。これが、これらのプラットフォームの世界です。
ただし、C ++の場合、大きなコードベースが既にリターンコードとともに存在し、多数の開発者が例外ではなく値を返すように調整されているため(たとえば、WindowsにHRESULTがあふれているため)、判断が少し難しくなります。さらに、多くのアプリケーションでは、パフォーマンスの問題になる可能性があります(少なくとも認識されています)。
ErrorStateReturnVariable
スーパークラスを作成することであり、そのプロパティの1つはInnerErrorState
(のインスタンスですErrorStateReturnVariable
)、サブクラスを実装することでエラーのチェーンを表示するように設定できます... :p
エラー変数は、例外が利用できなかったCのような言語の遺物です。現在、Cプログラム(または例外処理のない同様の言語)から使用される可能性のあるライブラリを作成する場合を除き、それらを避ける必要があります。
もちろん、「警告」としてより適切に分類できるタイプのエラーがある場合(=ライブラリは有効な結果を提供でき、呼び出し側はそれが重要ではないと考える場合は警告を無視できます)、フォームのステータスインジケーター変数の例外は、例外のある言語でも意味があります。しかし、注意してください。ライブラリの呼び出し元は、そうすべきではない場合でも、このような警告を無視する傾向があります。そのため、このような構成をライブラリに導入する前によく考えてください。
エラーを通知する方法は複数あります。
エラー変数の問題は、チェックするのを忘れやすいことです。
例外の問題は、実行の隠されたパスを作成することであり、try / catchは簡単に記述できますが、catch句で適切なリカバリを確実に実行することは非常に困難です(型システム/コンパイラからのサポートはありません)。
条件ハンドラーの問題は、それらがうまく構成されないことです。動的コード実行(仮想関数)がある場合、どの条件を処理する必要があるかを予測することは不可能です。さらに、同じ条件が複数のスポットで発生する可能性がある場合、毎回均一な解決策を適用できるということはわからず、すぐに面倒になります。
ポリモーフィックリターン(Either a b
Haskell)は、これまでのところ私のお気に入りのソリューションです。
唯一の問題は、過剰なチェックにつながる可能性があることです。それらを使用する言語には、それらを使用する関数の呼び出しをチェーン化するイディオムがありますが、それでももう少し入力/整理が必要な場合があります。Haskellでは、これはモナドになります。ただし、これは思ったよりもはるかに怖いです。鉄道指向プログラミングを参照してください。
ひどいと思います。現在、例外の代わりに戻り値を使用するJavaアプリをリファクタリングしています。Javaを使用しているわけではないかもしれませんが、それでもこれは当てはまると思います。
次のようなコードになります。
String result = x.doActionA();
if (result != null) {
throw new Exception(result);
}
result = x.doActionB();
if (result != null) {
throw new Exception(result);
}
またはこれ:
if (!x.doActionA()) {
throw new Exception(x.getError());
}
if (!x.doActionB()) {
throw new Exception(x.getError());
}
私はむしろアクションが例外をスローするようにしたいので、あなたは次のようなものになります:
x.doActionA();
x.doActionB();
これをtry-catchでラップし、例外からメッセージを取得するか、たとえば既になくなっている可能性のあるものを削除する場合など、例外を無視することを選択できます。スタックトレースがある場合は、それも保持します。メソッド自体も簡単になります。例外自体を処理する代わりに、何が間違っていたかを投げます。
現在の(恐ろしい)コード:
private String doActionA() {
try {
someOperationThatCanGoWrong1();
someOperationThatCanGoWrong2();
someOperationThatCanGoWrong3();
return null;
} catch(Exception e) {
return "Something went wrong!";
}
}
新しくなり改善された:
private void doActionA() throws Exception {
someOperationThatCanGoWrong1();
someOperationThatCanGoWrong2();
someOperationThatCanGoWrong3();
}
Strackトレースは保持され、メッセージは、役に立たない「何かがうまくいきませんでした!」ではなく、例外で利用可能です。
もちろん、より良いエラーメッセージを提供することができます。しかし、私が取り組んでいる現在のコードは苦痛であり、同じことをすべきではないので、この投稿はここにあります。
throw new Exception("Something went wrong with " + instanceVar, ex);
「発生する可能性のあるいくつかのエラーを処理するために、実行を停止しないでください」
エラーが現在の関数の実行を停止してはならないが、何らかの方法で呼び出し元に報告する必要があることを意味する場合は、実際には言及されていないいくつかのオプションがあります。このケースは、実際にはエラーというよりも警告です。スロー/リターンは現在の機能を終了するため、オプションではありません。単一のエラーメッセージパラメーターまたは戻り値では、これらのエラーのうち1つだけが発生します。
私が使用した2つのパターンは次のとおりです。
エラー/警告コレクション。渡されるか、メンバー変数として保持されます。どれを追加して処理を続けるか。私は個人的にこのアプローチが好きではないので、それが発信者の力を弱めると感じています。
エラー/警告ハンドラーオブジェクトを渡します(または、メンバー変数として設定します)。そして、各エラーはハンドラーのメンバー関数を呼び出します。これにより、呼び出し側は、このような非終了エラーの処理方法を決定できます。
これらのコレクション/ハンドラーに渡すものには、エラーを「正しく」処理するのに十分なコンテキストが含まれている必要があります。 。
エラーハンドラを使用した典型的なコードは次のようになります
class MyFunClass {
public interface ErrorHandler {
void onError(Exception e);
void onWarning(Exception e);
}
ErrorHandler eh;
public void canFail(int i) {
if(i==0) {
if(eh!=null) eh.onWarning(new Exception("canFail shouldn't be called with i=0"));
}
if(i==1) {
if(eh!=null) eh.onError(new Exception("canFail called with i=1 is fatal");
throw new RuntimeException("canFail called with i=2");
}
if(i==2) {
if(eh!=null) eh.onError(new Exception("canFail called with i=2 is an error, but not fatal"));
}
}
}
warnings
この問題の別のパターンを提供するPythonのパッケージに言及する価値があるかもしれません。
他の人が使用するパターンを使用している限り、このパターンまたはそのパターンを使用しても何も問題はありません。でObjective-Cの開発、多くの好ましいパターンが呼び出されるメソッドがNSErrorオブジェクトを堆積させることができるポインタを渡すことです。例外はプログラミングエラーのために予約されており、クラッシュを引き起こします(Javaまたは.NETプログラマーが最初のiPhoneアプリを作成している場合を除く)。そして、これは非常にうまく機能します。
質問はすでに答えられていますが、私は自分自身を助けることができません。
例外がすべてのユースケースのソリューションを提供することを本当に期待することはできません。誰でもハンマー?
例外がすべてではなくすべてである場合があります。たとえば、メソッドがリクエストを受信し、最初のフィールドだけでなく、渡されたすべてのフィールドの検証を担当する場合、複数のフィールドのエラーの原因を示します。検証の性質により、ユーザーがそれ以上進むことができないかどうかを示すことも可能です。その例は、強力ではないパスワードです。入力したパスワードはそれほど強力ではないが、十分強力であることを示すメッセージをユーザーに表示できます。
これらの検証はすべて、検証モジュールの最後に例外としてスローされる可能性があると主張できますが、名前以外のエラーコードになります。
したがって、ここでの教訓は次のとおりです。エラーコードと同様に、例外には場所があります。賢明に選んだ。
Validator
問題のメソッド(またはその背後のオブジェクト)に(インターフェース)を挿入する必要があります。挿入されValidator
た方法に応じて、メソッドは不正なパスワードを使用して処理を行います-または処理しません。周囲のコードはWeakValidator
、ユーザーがWeakPasswordException
最初に試したによってスローされた場合などに、ユーザーが要求した場合に試行できStrongValidator
ます。
MiddlyStrongValidator
何かを持っています。そして、それが実際にフローを中断しなかった場合、Validator
事前に呼び出されている必要があります。これは、ユーザーがまだパスワード(または同様)を入力している間にフローを進める前です。しかし、そもそも検証は問題のメソッドの一部ではありませんでした。:)おそらく、すべての後に好みの問題...
AggregateException
(または同様のValidationException
)を作成し、InnerExceptionsに各検証問題の特定の例外を入れます。たとえば、BadPasswordException
「ユーザーのパスワードは最小長の6未満です」またはMandatoryFieldMissingException
「ユーザーの名を指定する必要があります」などです。これはエラーコードとは異なります。これらのメッセージはすべて、ユーザーが理解できる方法で表示できますNullReferenceException
。代わりにa がスローされると、バグが発生します。
エラーコードが例外よりも望ましいユースケースがあります。
エラーにもかかわらずコードを続行できるが、レポートが必要な場合、例外はフローを終了するため、例外は適切ではありません。たとえば、データファイルを読み込んでいて、そこに不正なデータの非終端部分が含まれていることがわかった場合、完全に失敗するのではなく、ファイルの残りの部分を読み込んでエラーを報告する方がよい場合があります。
他の回答では、一般にエラーコードよりも例外を優先する理由について説明しました。
AcknowledgePossibleCorruption
メソッドを呼び出さずにデータを読み取れないようにすることができます。 。
例外が適切でない場合、例外を使用しないことで間違いはありません。
コードの実行が中断してはならない場合(例えば、コンパイルするプログラムまたはプロセスへのフォームのように、複数のエラーが含まれていてもよい、ユーザ入力に作用する)、私のようなエラー変数のエラーを収集することを見つけるhas_errors
とすると、error_messages
実際に投げるよりもはるかにエレガントなデザインです最初のエラーでの例外。ユーザーが不必要に再送信することを強制することなく、ユーザー入力のすべてのエラーを見つけることができます。
一部の動的プログラミング言語では、エラー値と例外処理の両方を使用できます。これは、通常の戻り値の代わりにスローされない例外オブジェクトを返すことで行われます。エラー値のようにチェックできますが、チェックされない場合は例外をスローします。
Perl 6のそれを介して行われるfail
withing場合れ、no fatal;
スコープがスローされない特別な例外リターンFailure
オブジェクト。
Perl 5を使用できコンテキスト::リターンをあなたがこれを行うことができますreturn FAIL
。
非常に具体的なものがない限り、検証用のエラー変数を持つことは悪い考えだと思います。目的は、検証に費やした時間を節約することであるようです(変数値を返すだけです)
ただし、何かを変更した場合は、とにかくその値を再計算する必要があります。ただし、停止と例外のスローについては言えません。
編集:私はこれが特定のケースではなくソフトウェアパラダイムの問題であることを理解していませんでした。
私の答えが理にかなっている私の特定のケースで私のポイントをさらに明確にしましょう
エラーには次の2種類があります。
サービス層では、エラー変数と同等の結果オブジェクトをラッパーとして使用する以外に選択肢はありません。httpのようなプロトコルでサービス呼び出しを介して例外をシミュレートすることは可能ですが、間違いなく良いことではありません。私はこの種のエラーについて話しているのではなく、これがこの質問で尋ねられている種類のエラーだとは思わなかった。
2番目の種類のエラーについて考えていました。そして私の答えは、この2番目の種類のエラーについてです。エンティティオブジェクトには、私たちのための選択肢があり、そのうちのいくつかは
検証変数を使用することは、各エンティティオブジェクトに対して単一の検証メソッドを使用することと同じです。特に、ユーザーはセッターを純粋なセッターとして維持する方法で値を設定でき、副作用はありません(これは多くの場合良い方法です)。これの利点は、時間を節約することです。検証結果は検証変数にキャッシュされるため、ユーザーがvalidation()を複数回呼び出したときに複数の検証を行う必要はありません。
この場合の最善の方法は、検証エラーをキャッシュするために検証を使用しなくても、単一の検証メソッドを使用することです。これは、セッターを単なるセッターとして保持するのに役立ちます。
try
/catch
のために存在します。また、あなたは置くことができますtry
/catch
(懸念の大きい分離を可能にする)、それを処理するためのより適切な場所に多くの更なるアップスタックを。