昨日、私は同僚とどのようなエラー報告方法が望ましいかについて白熱した議論をしていました。主に、アプリケーション層またはモジュール間のエラーを報告するための例外またはエラー コードの使用について説明していました。
エラー報告のために例外をスローするか、エラー コードを返すかを決定するために、どのような規則を使用しますか?
昨日、私は同僚とどのようなエラー報告方法が望ましいかについて白熱した議論をしていました。主に、アプリケーション層またはモジュール間のエラーを報告するための例外またはエラー コードの使用について説明していました。
エラー報告のために例外をスローするか、エラー コードを返すかを決定するために、どのような規則を使用しますか?
高レベルのものでは、例外です。低レベルのものでは、エラーコード。
例外のデフォルトの動作は、スタックをアンワインドしてプログラムを停止することです。スクリプトを作成していて、辞書にないキーを使用した場合、おそらくエラーであり、プログラムを停止して許可したいそれについてすべて知っています。
ただし、考えられるあらゆる状況での動作を知っておく必要があるコードを書いている場合は、エラー コードが必要です。それ以外の場合は、関数内のすべての行でスローされる可能性のあるすべての例外を知って、それが何をするかを知る必要があります (これがどれほどトリッキーであるかについては、航空会社を着陸させた例外を読んでください)。すべての状況 (不幸な状況を含む) に適切に反応するコードを書くのは面倒で難しいことですが、それはエラー コードを渡すからではなく、エラーのないコードを書くのが面倒で難しいからです。
Raymond Chen と Joelはどちらも、すべてに例外を使用することに反対する雄弁な議論を行っています。
私は通常、例外の方がコンテキスト情報が多く、(適切に使用された場合) エラーをより明確な方法でプログラマーに伝えることができるため、例外を好みます。
一方、エラー コードは例外よりも軽量ですが、保守が困難です。エラーチェックはうっかり省略してしまうことがあります。すべてのエラー コードを含むカタログを保持し、結果をオンにしてスローされたエラーを確認する必要があるため、エラー コードを維持するのは困難です。ここでエラー範囲が役立ちます。エラーが発生しているかどうかだけに関心がある場合は、チェックが簡単になるためです (たとえば、0 以上の HRESULT エラー コードは成功であり、ゼロ未満は失敗です)。開発者がエラー コードをチェックするプログラムによる強制がないため、誤って省略される可能性があります。一方、例外を無視することはできません。
要約すると、ほとんどすべての状況で、エラー コードよりも例外を好みます。
私は例外を好みます。
関数の呼び出し元は、エラー コードを無視することができます (多くの場合はそうです!)。例外は、少なくとも何らかの方法でエラーを処理するように強制します。それを扱う彼らのバージョンが空のキャッチハンドラーを持つことであっても(ため息)。
エラーコードの例外、間違いありません。例外からは、エラー コードの場合と同じ利点が得られますが、エラー コードの欠点がなくても、それ以上の利点があります。唯一の例外は、オーバーヘッドがわずかに増えることです。しかし、この時代では、そのオーバーヘッドはほとんどすべてのアプリケーションで無視できると考えるべきです。
以下に、2 つの手法について議論、比較、対比する記事をいくつか示します。
さらに読むことができるリンクがいくつかあります。
2 つのモデルを混在させることはありません...エラー コードを使用しているスタックの一部から、例外を使用している上位の部分に移動するときに、一方から他方に変換するのは非常に困難です。
例外は、「メソッドまたはサブルーチンが要求したことを実行するのを停止または阻害するもの」です...不規則性または異常な状況、またはシステムの状態などについてメッセージを返さないことです。戻り値または参照を使用してください(またはアウト)そのためのパラメーター。
例外を使用すると、メソッドの機能に依存するセマンティクスを使用してメソッドを記述 (および利用) できます。つまり、Employee オブジェクトまたは Employees のリストを返すメソッドを型指定して、それを行うことができ、呼び出すことで利用できます。
Employee EmpOfMonth = GetEmployeeOfTheMonth();
エラー コードを使用すると、すべてのメソッドがエラー コードを返すため、呼び出し元のコードで使用するために何か他のものを返す必要があるメソッドについては、参照変数を渡してそのデータを入力し、その戻り値をテストする必要があります。すべての関数またはメソッド呼び出しで、エラー コードとそれを処理します。
Employee EmpOfMonth;
if (getEmployeeOfTheMonth(ref EmpOfMonth) == ERROR)
// code to Handle the error here
各メソッドが単純なことを 1 つだけ実行するようにコーディングする場合、メソッドがメソッドの目的を達成できない場合は常に例外をスローする必要があります。このように、例外はエラー コードよりもはるかに豊富で使いやすいです。あなたのコードははるかにきれいです - 「通常の」コードパスの標準フローは、メソッドがやりたいことを達成できる場合に厳密に専念できます...そして、クリーンアップするコード、または処理するコードメソッドが正常に完了するのを妨げる何か悪いことが起こる「例外的な」状況は、通常のコードからサイロ化することができます。さらに、例外が発生した場所で例外を処理できず、それをスタックから UI に渡さなければならない場合 (さらに悪いことに、中間層コンポーネントから UI へのワイヤを介して渡す必要がある場合)、例外モデルを使用すると、
クリーンで明確な正しい方法で例外を使用するのが面倒な状況がいくつかあるかもしれませんが、ほとんどの場合、例外は当然の選択です。例外処理がエラー コードに勝る最大の利点は、実行の流れが変わることです。これは 2 つの理由で重要です。
例外が発生すると、アプリケーションは「通常の」実行パスをたどらなくなります。これが非常に重要である最初の理由は、コードの作成者がうまくやって本当に悪いことをしない限り、プログラムは停止し、予測できないことを続けないからです。エラー コードがチェックされず、不適切なエラー コードに対応して適切なアクションが実行されない場合、プログラムは実行中の処理を続行し、そのアクションの結果がどうなるかは誰にもわかりません。プログラムに「何でも」させると非常にコストがかかる状況がたくさんあります。企業が販売するさまざまな金融商品のパフォーマンス情報を取得し、その情報をブローカー/卸売業者に配信するプログラムを考えてみましょう。何か問題が発生してもプログラムが続行された場合、ブローカーや卸売業者に誤ったパフォーマンス データを送信する可能性があります。他の誰かについては知りませんが、副社長のオフィスに座って、私のコードが原因で会社が 7 桁の規制上の罰金を科された理由を説明したくありません。一般に、顧客にエラー メッセージを配信することは、「本物」に見える可能性のある間違ったデータを配信することよりも好まれます。
私が例外と通常の実行の中断を好む 2 番目の理由は、「通常のことが起こっている」ロジックを「何か問題が発生したロジック」から分離しておくことがはるかに簡単になるためです。私には、これ:
try {
// Normal things are happening logic
catch (// A problem) {
// Something went wrong logic
}
...これよりも望ましい:
// Some normal stuff logic
if (errorCode means error) {
// Some stuff went wrong logic
}
// Some normal stuff logic
if (errorCode means error) {
// Some stuff went wrong logic
}
// Some normal stuff logic
if (errorCode means error) {
// Some stuff went wrong logic
}
例外については、他にも細かい点があります。関数内で呼び出されたメソッドのいずれかがエラー コードを返したかどうかを追跡し、そのエラー コードを上位に返すための一連の条件付きロジックを用意することは、多くのボイラー プレートです。実際、失敗する可能性があるのは多くのボイラープレートです。私は、ほとんどの言語の例外システムを、「大学を出たばかりの」フレッドが書いた if-else-if-else ステートメントのネズミの巣よりもはるかに信頼しており、やるべきことがたくさんあります。コードレビューよりも私の時間で、ネズミの巣は言いました。
過去に、エラーコード キャンプに参加しました (C プログラミングをやりすぎました)。しかし今、私は光を見ました。
はい、例外はシステムに少し負担をかけます。しかし、それらはコードを単純化し、エラー (および WTF) の数を減らします。
したがって、例外を使用しますが、賢明に使用してください。そして、彼らはあなたの友達になります。
補足として。どの例外がどのメソッドによってスローされるかを文書化することを学びました。残念ながら、これはほとんどの言語では必要ありません。ただし、適切な例外を適切なレベルで処理できる可能性が高くなります。
私はここのフェンスに座っているかもしれませんが...
Python では、例外の使用は標準的な慣行であり、私は自分自身の例外を定義することに非常に満足しています。C では、例外はまったくありません。
C++ (少なくとも STL) では、例外は通常、真に例外的なエラーに対してのみスローされます (実際には、自分自身でそれらを見ることはありません)。自分のコードで何か違うことをする理由がわかりません。はい、戻り値を無視するのは簡単ですが、C++ では例外をキャッチする必要もありません。習慣化するしかないと思います。
私が取り組んでいるコード ベースはほとんどが C++ であり、ほぼどこでもエラー コードを使用していますが、非常に例外的でないものも含め、あらゆるエラーに対して例外を発生させるモジュールが 1 つあります。そのモジュールを使用するすべてのコードは非常に恐ろしいものです。しかし、それは単に例外とエラー コードが混在しているからかもしれません。一貫してエラー コードを使用するコードは、作業がはるかに簡単です。私たちのコードが一貫して例外を使用していれば、それほど悪くはないかもしれません。2つを混ぜてもうまくいかないようです。
私は C++ を扱っており、安全に使用できるように RAII を使用しているため、ほぼ例外なく例外を使用しています。通常のプログラム フローからエラー処理を引き出し、意図をより明確にします。
ただし、例外的な状況については例外を残します。特定のエラーが頻繁に発生することが予想される場合は、操作を実行する前に操作が成功することを確認するか、代わりにエラー コードを使用するバージョンの関数を呼び出します (Like TryParse()
)
私の推論は、本当にパフォーマンスが必要な低レベルのドライバーを作成している場合は、エラー コードを使用することです。ただし、そのコードを上位レベルのアプリケーションで使用していて、多少のオーバーヘッドを処理できる場合は、エラー コードをチェックして例外を発生させるインターフェイスでそのコードをラップします。
それ以外の場合は、おそらく例外が有効です。
私のアプローチは、例外コードとエラー コードの両方を同時に使用できるというものです。
私はいくつかのタイプの例外 (例: DataValidationException または ProcessInterruptExcepion) を定義することに慣れており、各例外内で各問題のより詳細な説明を定義しています。
Java での簡単な例:
public class DataValidationException extends Exception {
private DataValidation error;
/**
*
*/
DataValidationException(DataValidation dataValidation) {
super();
this.error = dataValidation;
}
}
enum DataValidation{
TOO_SMALL(1,"The input is too small"),
TOO_LARGE(2,"The input is too large");
private DataValidation(int code, String input) {
this.input = input;
this.code = code;
}
private String input;
private int code;
}
このようにして、例外を使用してカテゴリ エラーを定義し、エラー コードを使用して問題に関するより詳細な情報を定義します。
メソッド シグネチャは、メソッドが何をするかを伝える必要があります。long errorCode = getErrorCode(); のようなもの 問題ないかもしれませんが、長い errorCode = fetchRecord(); 紛らわしいです。
例外は、例外的な状況、つまり、コードの通常の流れの一部ではない場合に使用されます。
例外とエラー コードを混在させることは非常に正当です。エラー コードは、コード自体の実行中のエラーではなく、何かのステータスを表します (たとえば、子プロセスからの戻りコードのチェック)。
しかし、例外的な状況が発生した場合、例外が最も表現力のあるモデルであると私は信じています。
例外の代わりにエラー コードを使用することを好む、または使用しなければならない場合があり、これらは既に適切にカバーされています (コンパイラ サポートなどの他の明白な制約を除く)。
しかし、別の方向に進むと、例外を使用すると、エラー処理に対してさらに高いレベルの抽象化を構築でき、コードをより表現力豊かで自然なものにすることができます。C++ の専門家である Andrei Alexandrescu による、彼が「施行」と呼んでいるものに関するこの優れた、しかし過小評価されている記事を読むことを強くお勧めします: http://www.ddj.com/cpp/184403864。これは C++ の記事ですが、原則は一般的に適用できます。私は強制の概念を C# にうまく翻訳しました。
失敗が、プリミティブ データ型を返す関数の予想されるバグのない結果である場合を除いて、すべてのエラー ケースに対して例外を優先します。たとえば、より大きな文字列内の部分文字列のインデックスを見つけると、見つからない場合、NotFoundException を発生させる代わりに、通常は -1 を返します。
逆参照される可能性のある無効なポインターを返すこと (たとえば、Java で NullPointerException を引き起こす) は受け入れられません。
クライアントが「< 0」ではなく「== -1」チェックを行う可能性があるため、同じ関数の戻り値として複数の異なる数値エラー コード (-1、-2) を使用することは、通常は不適切なスタイルです。
ここで留意すべきことの 1 つは、時間の経過に伴う API の進化です。優れた API を使用すると、クライアントを壊すことなく、いくつかの方法で障害の動作を変更および拡張できます。たとえば、クライアント エラー ハンドルが 4 つのエラー ケースをチェックし、関数に 5 番目のエラー値を追加した場合、クライアント ハンドラはこれをテストせず、中断する可能性があります。例外を発生させると、通常はクライアントが新しいバージョンのライブラリに簡単に移行できるようになります。
考慮すべきもう 1 つのことは、チームで作業する場合、すべての開発者がそのような決定を下すための明確な線をどこに引くかです。たとえば、「高レベルのものには例外、低レベルのものにはエラーコード」は非常に主観的です。
いずれにせよ、複数の些細なタイプのエラーが発生する可能性がある場合、ソース コードは数値リテラルを使用してエラー コードを返したり、エラーを処理したりしないでください (x == -7 の場合は -7 を返します ...)。常に名前付き定数 (x == NO_SUCH_FOO の場合、NO_SUCH_FOO を返す) 。
大規模なプロジェクトでは、例外だけやエラー コードだけを使用することはできません。場合によっては、異なるアプローチを使用する必要があります。
たとえば、例外のみを使用するとします。ただし、非同期イベント処理を使用することにした場合。この状況でエラー処理に例外を使用するのはお勧めできません。しかし、アプリケーションのあらゆる場所でエラー コードを使用するのは面倒です。
したがって、例外とエラー コードの両方を同時に使用するのが正常であるという私の意見です。
ほとんどのアプリケーションでは、例外の方が適しています。例外は、ソフトウェアが他のデバイスと通信する必要がある場合です。私が働いている分野は産業用制御です。ここでは、エラー コードが優先され、期待されます。だから私の答えは、それは状況に依存するということです。
また、結果からスタックトレースなどの情報が本当に必要かどうかにもよると思います。はいの場合は、問題に関する多くの情報でいっぱいのオブジェクトを提供する例外に間違いなく行きます。ただし、結果だけに興味があり、その結果の理由を気にしない場合は、エラー コードを使用してください。
たとえば、ファイルを処理していて IOException に直面している場合、クライアントは、ファイルを開いたり、ファイルを解析したりする際に、これがどこからトリガーされたかを知りたい場合があります。そのため、IOException またはその特定のサブクラスを返す方がよいでしょう。ただし、ログインメソッドがあり、それが成功したかどうかを知りたい場合は、ブール値を返すか、正しいメッセージを表示してエラーコードを返します。ここで、クライアントは、ロジックのどの部分がそのエラー コードを引き起こしたかを知ることに関心がありません。彼は、資格情報が無効か、アカウントがロックされているかなどを知っているだけです。
私が考えることができる別のユースケースは、データがネットワーク上を移動する場合です。リモート メソッドは、データ転送を最小限に抑えるために、Exception ではなくエラー コードのみを返すことができます。
メソッドが数値以外のものを返す場合、エラーコードも機能しません...