web-dev-qa-db-ja.com

例外またはエラーコードの規則

昨日、私は同僚と、好ましいエラー報告方法について熱烈な議論をしていた。主に、アプリケーションレイヤーまたはモジュール間のエラーを報告するための例外またはエラーコードの使用について説明していました。

エラー報告のために例外をスローするかエラーコードを返すかを決定するためにどのルールを使用しますか?

109
Jorge Ferreira

私は通常、例外を好む。なぜなら、それらはより文脈的な情報を持ち、プログラマーにエラーを明確に伝えることができるからだ。

一方、エラーコードは例外よりも軽量ですが、保守が困難です。エラーチェックは誤って省略される可能性があります。エラーコードは、すべてのエラーコードを含むカタログを保持し、結果をオンにしてスローされたエラーを確認する必要があるため、保守が難しくなります。ここでエラー範囲が役立つ場合があります。関心があるのは、エラーが存在するかどうかだけである場合、チェックが簡単です(たとえば、0以上のHRESULTエラーコードが成功し、ゼロ未満は失敗です)。開発者がエラーコードをチェックするプログラム上の強制がないため、これらは誤って省略される可能性があります。一方、例外を無視することはできません。

要約すると、私はほとんどすべての状況でエラーコードよりも例外を好みます。

58
Jorge Ferreira

高レベルのものでは、例外;低レベルのものでは、エラーコード。

例外のデフォルトの動作は、スタックを巻き戻してプログラムを停止することです。スクリプトを作成していて、辞書にないキーを探している場合はおそらくエラーであり、プログラムを停止してそれについてすべて知っています。

ただし、あらゆる状況での動作を(知っておく必要があるコードを記述している場合は、エラーコードが必要です。)それ以外の場合、関数のすべての行でスローされる可能性があるすべての例外を知って、それが何をするのかを知る必要があります(これがいかに難しいのかを知るために 航空会社を接地した例外 を読んでください)。あらゆる状況(不幸な状況を含む)に適切に反応するコードを書くのは退屈で難しいですが、エラーコードを渡すからではなく、エラーのないコードを書くのは退屈で難しいからです。

両方 Raymond ChenandJoel は、すべてに対して例外を使用することに対して雄弁な議論を行っています。

78
Tom Dunham

私は例外を好む

  • 彼らは論理の流れを妨害する
  • より多くの機能/機能を提供するクラス階層の恩恵を受ける
  • 適切に使用すると、さまざまなエラーを表すことができます(たとえば、InvalidMethodCallExceptionはLogicExceptionでもあります。両方とも、実行前に検出可能なコードにバグがある場合に発生します)。
  • エラーを強化するために使用できます(つまり、FileReadExceptionクラス定義には、ファイルが存在するか、ロックされているかなどをチェックするコードを含めることができます)
23
JamShady

関数の呼び出し元はエラーコードを無視できます(多くの場合、!)。例外は、少なくとも何らかの方法でエラーに対処することを強制します。それを扱う彼らのバージョンが空のキャッチハンドラ(ため息)を持っていることであっても。

19
Maxam

エラーコードの例外、それについては間違いありません。エラーコードの場合と同じように例外から多くの利点を得ることができますが、エラーコードの欠点がなくても、はるかに多くの利点があります。唯一の例外は、オーバーヘッドがわずかに大きいことです。しかし、今日では、そのオーバーヘッドはほとんどすべてのアプリケーションにとって無視できると考えられるべきです。

以下に、2つの手法を議論、比較、対照する記事をいくつか紹介します。

さらに読むために役立つリンクがいくつかあります。

16
Pistos

2つのモデルを混在させることは決してありません...エラーコードを使用しているスタックの一部から例外を使用している上位の部分に移動するときに、一方から他方に変換するのは非常に困難です。

例外は、「メソッドまたはサブルーチンが要求したことを行うことを停止または禁止するもの」に対するものです...異常または異常な状況、またはシステムの状態などに関するメッセージを返さないこと。戻り値またはrefを使用します。そのための(または出力)パラメーター。

例外を使用すると、メソッドの機能に依存するセマンティクスでメソッドを記述(および利用)できます。つまり、EmployeeオブジェクトまたはEmployeesのリストを返すメソッドは、それを行うために入力することができ、呼び出して利用できます。

Employee EmpOfMonth = GetEmployeeOfTheMonth();

エラーコードでは、すべてのメソッドがエラーコードを返すため、呼び出しコードで使用するために何かを返す必要があるメソッドについては、そのデータを入力する参照変数を渡し、その戻り値をテストする必要がありますすべての関数またはメソッド呼び出しでエラーコードを処理します。

Employee EmpOfMonth; 
if (getEmployeeOfTheMonth(ref EmpOfMonth) == ERROR)
    // code to Handle the error here

各メソッドがたった1つの単純なことを行うようにコーディングする場合、メソッドがメソッドの目的を達成できない場合は常に例外をスローする必要があります。例外は、このようにエラーコードよりもはるかに豊富で使いやすいです。あなたのコードはずっときれいです-「通常の」コードパスの標準フローは、メソッドISあなたがやりたいことを達成できる場合に限って...クリーンアップするためのコード、またはメソッドが正常に完了するのを妨げるような何か悪いことが起こった場合の「例外的な」状況を処理するために、通常のコードからサイロ化することができます。スタックをUIに(または、さらに悪いことに、中間層コンポーネントからUIに至るまで)、例外モデルを使用すると、スタック内のすべての介在メソッドをコーディングして認識し、渡す必要はありません。スタックの例外...例外モデルはこれを自動的に行います。エラーコードを使用すると、このパズルのピースは非常に急速に厄介になります。

14
Charles Bretana

過去に私はerrorcode campに参加しました(Cプログラミングが多すぎました)。しかし今、私は光を見ました。

はい、例外はシステムに少し負担をかけます。しかし、それらはコードを単純化し、エラー(およびWTF)の数を減らします。

そのため、例外を使用しますが、賢明に使用します。そして、彼らはあなたの友達になります。

補足として。どの例外をどのメソッドでスローできるかを文書化することを学びました。残念ながら、これはほとんどの言語では必要ありません。ただし、適切なレベルで適切な例外を処理する機会が増えます。

11
Toon Krijthe

明確で明確で正しい方法で例外を使用するのが面倒な場合がありますが、例外の大部分は明白な選択です。例外処理がエラーコードよりも優れている最大の利点は、実行フローを変更することです。これは2つの理由で重要です。

例外が発生すると、アプリケーションは「通常の」実行パスをたどりません。これが非常に重要である最初の理由は、コードの作者がうまく行かず、本当に邪魔にならない限り、プログラムは停止し、予測できないことを続けないことです。エラーコードがチェックされず、不良なエラーコードへの対応として適切なアクションが実行されない場合、プログラムは実行中の処理を実行し続け、そのアクションの結果が誰であるかを認識します。プログラムに「何でも」をさせると、非常に高価になりかねない状況がたくさんあります。会社が販売するさまざまな金融商品のパフォーマンス情報を取得し、その情報をブローカー/卸売業者に配信するプログラムを検討してください。何かがうまくいかず、プログラムが引き続き動作する場合、誤ったパフォーマンスデータをブローカーと卸売業者に送信する可能性があります。私は他の誰についても知りませんが、VPのオフィスに座って、なぜ私のコードが会社に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ステートメントのネズミの巣よりもはるかに信頼しています。コードのレビューよりも時間をかけて、ネズミの巣を言った。

10
Dogs

両方を使用する必要があります。問題は、それぞれをいつ使用するかを決定することです

例外が明らかな選択であるいくつかのシナリオ

  1. 状況によってはエラーコードでは何もできないで、ただコールスタックの上位レベルで処理する必要がある、通常はエラーを記録するだけで、ユーザーに何かを表示するか、プログラムを閉じます。このような場合、エラーコードを使用すると、エラーコードをレベルごとに手動でバブルアップする必要があります。ポイントは、これがnexpected and unhandleableシチュエーション用であるということです。

  2. しかし、状況1(予期せず処理できないことが発生した場合はログに記録したくない)については、例外が役立つ場合がありますコンテキスト情報の追加。たとえば、低レベルのデータヘルパーでSqlExceptionを取得した場合、そのエラーを低レベル(エラーの原因となったSQLコマンドを知っている場所)でキャッチして、その情報をキャプチャできるようにしますrethrow追加情報付き。ここで魔法の言葉に注意してください:飲み込みではなく、再スロー例外処理の最初のルール:例外を飲み込まない 。また、外側のキャッチにはスタックトレース全体が含まれ、ログに記録される可能性があるため、内側のキャッチには何も記録する必要がないことに注意してください。

  3. 状況によっては、一連のコマンドがあり、コマンドのいずれかが失敗した場合する必要がありますリソースのクリーンアップ/破棄(*)、これが回復不可能な状況(スローされる必要があります)または回復可能な状況(この場合、ローカルまたは呼び出し元コードで処理できますが、例外は必要ありません)。明らかに、各メソッドの後にエラーコードをテストし、finallyブロックでクリーンアップ/破棄する代わりに、すべてのコマンドを1回の試行で配置する方がはるかに簡単です。 エラーをバブルアップさせたい場合(おそらくこれが望んでいることです)、キャッチする必要さえありません-クリーンアップ/破棄に最終的に使用するだけですコンテキスト情報を追加する場合は、catch/retrowを使用します(箇条書き2を参照)。

    1つの例は、トランザクションブロック内のSQLステートメントのシーケンスです。繰り返しになりますが、これも「手に負えない」状況です。早期に捕まえようと決めたとしても(トップにバブリングするのではなくローカルで処理する)、それでも致命的な状況ですすべてまたは少なくともプロセスの大部分を中断します。
    (*)これはon error goto古いVisual Basicで使用したもの

  4. コンストラクターでは、例外のみをスローできます。

そうは言っても、呼び出し元が何らかのアクションをとる必要があるの情報を返す他のすべての状況では、リターンコードを使用する方がおそらくより良い代替手段です。これには、すべてのexpected "errors"が含まれます。これは、おそらく直接の呼び出し元によって処理される必要があり、スタック内でレベルを上げすぎる必要がほとんどないためです。

もちろん、予想されるエラーを例外として処理し、すぐに1レベル上のレベルでキャッチすることは常に可能です。また、コードのすべての行をtry catchに含めて、考えられるエラーごとにアクションを実行することもできます。 IMO、これは悪いデザインです。それははるかに冗長であるだけでなく、スローされる可能性のある例外がソースコードを読まないと明らかではないためです-そして、例外はディープメソッドからスローされる可能性があります - 見えないゴトス 。これらは、コードの読み取りと検査を困難にする複数の不可視の出口点を作成することにより、コード構造を破壊します。つまり、例外を flow-controlとして使用しないでください。他の人が理解して維持するのは難しいからです。テストのために考えられるすべてのコードフローを理解することはさらに難しくなります。
もう一度:正しいクリーンアップ/破棄のために、何もキャッチせずにtry-finallyを使用できます

リターンコードに関する最も一般的な批判は、「誰かがエラーコードを無視できますが、同じ意味で誰かが例外を飲み込むこともできます。 悪い例外処理は簡単です両方の方法で。 優れたエラーコードベースのプログラムを書くことは、例外ベースのプログラムを書くよりもずっと簡単です 。そして、何らかの理由ですべてのエラーを無視することに決めた場合(古いon error resume next)、リターンコードを使用して簡単に実行できますが、多くのtry-catchsボイラープレートなしでは実行できません。

リターンコードに関する2番目に人気のある批判は、「バブルアップするのは難しい」というものです。しかし、エラーコードはそうではないのに、例外は回復不可能な状況のためであることを人々は理解していないからです。

例外とエラーコードを決定するのは灰色の領域です。再利用可能なビジネスメソッドからエラーコードを取得し、それを例外にラップして(場合によっては情報を追加して)、それをバブルアップさせることにすることも可能です。しかし、すべてのエラーが例外としてスローされるべきであると仮定するのは設計上の誤りです。

まとめると:

  • 予期せぬ状況が発生した場合に例外を使用するのが好きです。この状況では、やるべきことはあまりありません。通常、大きなコードブロックや、操作やプログラム全体を中止したいのです。これは、古い「on error goto」に似ています。

  • 呼び出し元のコードが何らかのアクションを実行できる/すべきであると予想される状況がある場合、リターンコードを使用します。これには、ほとんどのビジネスメソッド、API、検証などが含まれます。

例外とエラーコードのこの違いは、GO言語の設計原則の1つであり、致命的な予期しない状況に「パニック」を使用し、通常の予期される状況はエラーとして返されます。

ただし、GOについては、 複数の戻り値 も使用できます。これは、エラーと何かを同時に返すことができるため、戻りコードの使用に非常に役立ちます。 C#/ Javaでは、列挙型と組み合わせて発信者に明確なエラーコードを提供できるoutパラメーター、Tuples、または(私のお気に入りの)Genericsでそれを実現できます。

public MethodResult<CreateOrderResultCodeEnum, Order> CreateOrder(CreateOrderOptions options)
{
    ....
    return MethodResult<CreateOrderResultCodeEnum>.CreateError(CreateOrderResultCodeEnum.NO_DELIVERY_AVAILABLE, "There is no delivery service in your area");

    ...
    return MethodResult<CreateOrderResultCodeEnum>.CreateSuccess(CreateOrderResultCodeEnum.SUCCESS, order);
}

var result = CreateOrder(options);
if (result.ResultCode == CreateOrderResultCodeEnum.OUT_OF_STOCK)
    // do something
else if (result.ResultCode == CreateOrderResultCodeEnum.SUCCESS)
    order = result.Entity; // etc...

メソッドに新しい戻り値を追加すると、たとえばswitchステートメントでその新しい値をカバーしている場合、すべての呼び出し元をチェックすることもできます。あなたは本当に例外でそれをすることはできません。リターンコードを使用する場合、通常、発生する可能性があるすべてのエラーを事前に把握し、それらをテストします。例外を除き、通常は何が起こるかわかりません。 (Genericsの代わりに)例外内に列挙型をラップすることは(各メソッドがスローする例外のタイプが明確である限り)代替手段ですが、IMOはまだ設計が悪いです。

5
drizin

ここのフェンスに座っているかもしれませんが、...

  1. 言語に依存します。
  2. どのモデルを選択する場合でも、その使用方法について一貫性を保ってください。

Pythonでは、例外の使用は標準的な慣行であり、独自の例外を定義できてとてもうれしいです。 Cでは、例外はまったくありません。

C++(少なくともSTLでは)では、例外は通常、真に例外的なエラーに対してのみスローされます(私は実際にそれらを見ることはありません)。私は自分のコードで何か違うことをする理由はないと思います。はい、戻り値を無視するのは簡単ですが、C++は例外をキャッチすることも強制しません。あなたはただそれをする習慣を身につけなければならないと思います。

私が取り組んでいるコードベースはほとんどがC++であり、ほぼすべての場所でエラーコードを使用していますが、非常に例外的でないものを含め、エラーに対して例外を発生させるモジュールが1つあり、そのモジュールを使用するすべてのコードは非常に恐ろしいものです。しかし、それは単に例外とエラーコードが混在しているためかもしれません。エラーコードを一貫して使用するコードは、作業がはるかに簡単です。コードで一貫して例外を使用していれば、それほど悪くはないでしょう。 2つを混ぜてもうまく機能しないようです。

4
jrb

私はC++を使用しており、RAIIを使用して安全に使用できるようにしているため、例外をほぼ排他的に使用しています。通常のプログラムフローからエラー処理を引き出し、意図をより明確にします。

ただし、例外的な状況には例外を残しています。特定のエラーが頻繁に発生することを期待している場合は、実行する前に操作が成功することを確認するか、代わりにエラーコードを使用するバージョンの関数を呼び出します(TryParse()のように)

4
Eclipse

私のアプローチは、例外コードとエラーコードの両方を同時に使用できるということです。

私はいくつかのタイプの例外(例: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;

}

このように、例外を使用してカテゴリエラーを定義し、エラーコードを使用して問題に関するより詳細な情報を定義します。

3
sakana

メソッドの署名は、メソッドが何をするのかを伝える必要があります。長いerrorCode = getErrorCode();のようなもの大丈夫かもしれませんが、長いerrorCode = fetchRecord();紛らわしいです。

3
Paul Croarkin

本当にパフォーマンスが必要な低レベルのドライバーを作成している場合、エラーコードを使用するのが私の推論です。しかし、より高いレベルのアプリケーションでそのコードを使用しており、少しのオーバーヘッドを処理できる場合は、それらのコードを、それらのエラーコードをチェックして例外を発生させるインターフェースでラップします。

他のすべての場合では、例外がおそらく進むべき道です。

3
Claudiu

例外は、exceptional状況、つまり、コードの通常のフローの一部ではない場合です。

例外とエラーコードを混在させることは非常に合法です。エラーコードは、コード自体の実行エラー(たとえば、子プロセスからのリターンコードのチェック)ではなく、何かのステータスを表します。

しかし、例外的な状況が発生した場合、例外が最も表現力のあるモデルだと思います。

例外の代わりにエラーコードを使用することを好む、または持っている場合があり、これらは既に十分にカバーされています(コンパイラサポートなどの他の明らかな制約を除く)。

しかし、別の方向に進むと、例外を使用すると、エラー処理のさらに高いレベルの抽象化を構築でき、コードをより表現力豊かで自然なものにすることができます。 C++の専門家であるAndrei Alexandrescuによる、彼の言う「施行」というテーマに関するこの優れた、しかし過小評価された記事を読むことを強くお勧めします: http://www.ddj.com/cpp/184403864 。これはC++の記事ですが、原則は一般的に適用可能であり、施行の概念をC#に非常にうまく変換しました。

2
philsquared

まず、Tomの answer に同意します。サービス指向アーキテクチャ(SOA)でない限り、高レベルのものには例外を使用し、低レベルのものにはエラーコードを使用します。

メソッドが異なるマシン間で呼び出されるSOAでは、例外はネットワーク経由で渡されない可能性があります。代わりに、以下のような構造(C#)で成功/失敗応答を使用します。

public class ServiceResponse
{
    public bool IsSuccess => string.IsNullOrEmpty(this.ErrorMessage);

    public string ErrorMessage { get; set; }
}

public class ServiceResponse<TResult> : ServiceResponse
{
    public TResult Result { get; set; }
}

そして、次のように使用します:

public async Task<ServiceResponse<string>> GetUserName(Guid userId)
{
    var response = await this.GetUser(userId);
    if (!response.IsSuccess) return new ServiceResponse<string>
    {
        ErrorMessage = $"Failed to get user."
    };
    return new ServiceResponse<string>
    {
        Result = user.Name
    };
}

これらをサービス応答で一貫して使用すると、アプリケーションで成功/失敗を処理する非常に素晴らしいパターンが作成されます。これにより、サービス内およびサービス間の非同期呼び出しでのエラー処理が容易になります。

2
orad

エラーがプリミティブデータ型を返す関数の予想されるバグのない結果である場合を除き、すべてのエラーの場合に例外を優先します。例えば。大きな文字列内の部分文字列のインデックスを見つけると、通常、NotFoundExceptionを送出する代わりに、見つからない場合は-1を返します。

間接参照される可能性のある無効なポインター(JavaでNullPointerExceptionが発生するなど)を返すことは受け入れられません。

クライアントが「<0」の代わりに「== -1」チェックを行う可能性があるため、同じ関数の戻り値として複数の異なる数値エラーコード(-1、-2)を使用するのは通常悪いスタイルです。

ここで心に留めておくべきことの1つは、時間の経過に伴うAPIの進化です。優れたAPIにより、クライアントを中断することなく、いくつかの方法で障害動作を変更および拡張できます。例えば。クライアントエラーハンドルが4つのエラーケースをチェックし、5番目のエラー値を関数に追加した場合、クライアントハンドラーはこれをテストせず、中断することがあります。例外を発生させると、通常、クライアントがライブラリの新しいバージョンに移行しやすくなります。

考慮すべきもう1つのことは、チームで作業する場合、すべての開発者がそのような決定を下すために明確な線を引くことです。例えば。 「高レベルのものの例外、低レベルのもののエラーコード」は非常に主観的です。

いずれにせよ、1つ以上の些細なエラーが発生する可能性がある場合、ソースコードはエラーコードを返すまたは処理するために数値リテラルを決して使用してはなりません(x == -7の場合は-7を返します)が、常に名前付き定数(x == NO_SUCH_FOOの場合、NO_SUCH_FOOを返します)。

1
tkruse

大きなプロジェクトの下で作業している場合、例外のみまたはエラーコードのみを使用することはできません。場合によっては、異なるアプローチを使用する必要があります。

たとえば、例外のみを使用することにしました。ただし、非同期イベント処理を使用することにした場合。この状況では、エラー処理に例外を使用することはお勧めできません。しかし、アプリケーションのどこでもエラーコードを使用するのは面倒です。

したがって、例外とエラーコードの両方を同時に使用するのは正常であるという私の意見です。

1
gomons

私の一般的なルールは次のとおりです。

  • 関数に表示されるエラーは1つだけです。エラーコードを使用します(関数のパラメーターとして)
  • 複数の特定のエラーが表示される可能性があります:例外をスローします
0
DEADBEEF

ほとんどのアプリケーションでは、例外が優れています。例外は、ソフトウェアが他のデバイスと通信する必要がある場合です。私が働いているドメインは産業用制御です。ここでは、エラーコードが推奨されます。私の答えは、状況に依存するということです。

0
Jim C

また、結果からのスタックトレースなどの情報が本当に必要かどうかにも依存すると思います。もしそうなら、あなたは間違いなく例外について行き、問題に関する多くの情報をオブジェクトに提供します。ただし、結果だけに興味があり、その結果がなぜかを気にしない場合は、エラーコードを探します。

例えばファイルを処理してIOExceptionに直面している場合、クライアントはこれがトリガーされた場所を知ること、ファイルを開くこと、ファイルを解析することなどに興味があるかもしれません。ただし、ログインメソッドがあり、成功したかどうかを知りたいというシナリオでは、ブール値を返すか、正しいメッセージを表示してエラーコードを返します。ここで、クライアントは、ロジックのどの部分がそのエラーコードを引き起こしたかを知ることに興味がありません。資格情報が無効であるか、アカウントがロックされているかなどを知っています。

私が考えることができる別のユースケースは、データがネットワーク上を移動するときです。リモートメソッドは、データ転送を最小限に抑えるために、例外ではなくエラーコードのみを返すことができます。

0
ashah