質問

昨日、私は同僚とどのようなエラー報告方法が望ましいかについて白熱した議論をしていました。主に、アプリケーション層またはモジュール間のエラーを報告するための例外またはエラーコードの使用法について議論していました。

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

役に立ちましたか?

解決 2

例外は、コンテキスト情報が多く、(適切に使用すると)エラーをより明確にプログラマに伝えることができるため、通常は例外を好みます。

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

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

他のヒント

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

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

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

Raymond Chen および Joel は、すべてに対して例外を使用することに対して雄弁な主張をしています。

例外を好むのは

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

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

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

2つの技術を議論、比較、対照する記事があります:

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

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

例外は、<!> quot;メソッドまたはサブルーチンが要求したことを実行するのを停止または禁止するもの<!> quot; ...異常や異常な状況、システムの状態などに関するメッセージを返送しないでください。そのために戻り値またはref(またはout)パラメーターを使用してください。

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

Employee EmpOfMonth = GetEmployeeOfTheMonth();

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

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

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

過去に私はエラーコードキャンプに参加しました(Cプログラミングが多すぎました)。しかし今、私は光を見ました。

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

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

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

クリーンで明確で正しい方法で例外を使用するのが面倒な場合がありますが、例外の大部分は明白な選択です。例外処理がエラーコードよりも優れている最大の利点は、実行フローを変更することです。これは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ステートメントのネズミの巣よりもはるかに信頼しています。コードレビューよりも時間をかけて、ラットの巣を言った。

両方を使用する必要があります。重要なのは、それぞれをいつ使用するかを決めることです.

あります 例外を選択することが明らかなシナリオはほとんどありません:

  1. 状況によっては エラーコードでは何もできません, 、そしてあなたはただ コールスタックの上位レベルで処理する必要がある, 、通常はエラーをログに記録するか、ユーザーに何かを表示するか、プログラムを閉じるだけです。このような場合、エラー コードをレベルごとに手動でバブルアップする必要がありますが、例外を使用する方が明らかに簡単です。ポイントは、これは 予想外で手に負えない 状況。

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

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

    一例としては、トランザクション ブロック内の一連の SQL ステートメントが挙げられます。繰り返しますが、これも「対処できない」状況です。たとえ早期に発見することに決めたとしても(上部に泡立つのではなく局所的に処理する)、依然として問題です。 致命的な状況 最良の結果は、すべてを中止するか、少なくともプロセスの大部分を中止することです。
    (*) これは次のようなものです on error goto 古い Visual Basic で使用されていたもの

  4. コンストラクターでは例外をスローすることしかできません。

そうは言っても、情報を返す他のすべての状況では、 発信者は何らかのアクションを起こすことができます/すべきです, 、リターンコードを使用する方がおそらくより良い代替手段です。これにはすべてが含まれます 予想される「エラー」, なぜなら、おそらくそれらは直接の呼び出し元によって処理されるべきであり、スタック内であまりにも多くのレベルをバブルアップする必要はほとんどないからです。

もちろん、予期されるエラーを例外として扱い、すぐに 1 つ上のレベルでキャッチすることは常に可能です。また、try catch 内のコードのすべての行を包含し、考えられるエラーごとにアクションを実行することも可能です。IMO、これは悪い設計です。はるかに冗長であるだけでなく、スローされる可能性のある例外がソース コードを読まないと明らかではないためです。また、例外はあらゆる深いメソッドからスローされる可能性があり、 目に見えない後藤. 。これらは、コードの読み取りと検査を困難にする、目に見えない出口ポイントを複数作成することによってコード構造を破壊します。つまり、決して使用してはいけないのです 例外として フロー制御, なぜなら、それは他の人にとって理解して維持するのが難しいからです。テストで考えられるすべてのコード フローを理解することはさらに困難になる場合があります。
また: 正しくクリーンアップ/破棄するには、何もキャッチせずに try-finally を使用できます.

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

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

例外とエラー コードのどちらを判断するかはグレーゾーンです。再利用可能なビジネス メソッドからエラー コードを取得する必要があり、それを例外にラップして (場合によっては情報を追加して)、それをバブルアップさせることを決定することもあります。しかし、すべてのエラーが例外としてスローされるべきであると想定するのは設計上の間違いです。

要約すると次のようになります。

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

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

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

GO については、次のことも可能です。 複数の戻り値 , これは、エラーと他のものを同時に返すことができるため、リターン コードを使用する場合に非常に役立ちます。C#/Java では、out パラメーター、タプル、または (私のお気に入りの) ジェネリクスを使用せずにこれを実現できます。これらを enum と組み合わせると、呼び出し元に明確なエラー コードを提供できます。

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 ステートメントでその新しい値をカバーしているかどうかを確認することもできます。例外を除いて実際にはそれはできません。リターン コードを使用する場合、通常は、起こり得るすべてのエラーを事前に把握し、それらをテストします。例外はあるものの、通常は何が起こるかわかりません。(ジェネリックではなく) 列挙型を例外内にラップすることは代替手段です (各メソッドがスローする例外の種類が明らかである限り) が、IMO では依然として悪い設計です。

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

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

Pythonでは、例外の使用は標準的な方法であり、独自の例外を定義できて非常にうれしいです。 Cでは、例外はまったくありません。

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

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

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

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

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

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

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

私のアプローチでは、例外コードとエラーコードを同時に使用できます。

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

}

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

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

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

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

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

ただし、例外を使用すると、エラー処理のさらに高いレベルの抽象化を構築でき、コードをより表現力豊かで自然なものにすることができます。 C ++の専門家であるAndrei Alexandrescuによる、彼が呼んでいるもの、つまり<!> quot; Enforcements <!> quot;に関する http://www.ddj.com/cpp/184403864 。これはC ++の記事ですが、原則は一般的に適用可能であり、施行の概念をC#に非常にうまく変換しています。

まず、トムの回答に同意します。これは、高レベルのものでは例外を使用し、低レベルのものでは使用するサービス指向アーキテクチャ(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
    };
}

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

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

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

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

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

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

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

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

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

だから、例外とエラーコードの両方を同時に使用するのが普通だと思う。

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

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

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

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

私の一般的なルールは:

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

エラーコードは、メソッドが数値以外を返す場合にも機能しません...

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top