1

例外を使用せずに、C# コードでエラー通信と回復を行おうとしています。例を挙げると、Func A があり、Func B または Func C またはその他の関数によって呼び出すことができるとします。Func A は、再利用を念頭に置いて設計する必要があります。(このアプリケーションには、新しい機能が一定期間追加され続ける進化するライブラリがあります)

Func A が想定されていることを実行できない場合、int を返します。0 以外の値は失敗を示します。また、失敗の理由も伝えたいと思います。呼び出し元関数は、この情報を複数の方法で使用できます。

  1. ユーザーにエラーメッセージを表示できます。
  2. コンテキストにより関連性の高い独自のエラー メッセージが表示される場合があります。
  3. それ自体が、祖先の呼び出し元関数への失敗を示す int 値を返す場合があります。
  4. インテリジェントなアルゴリズムを使用して、エラーからの回復を試みる場合があります。

仮説として、他の関数が依存する関数は、ステータス コード、エラー メッセージ、データの状態を示すその他の変数など、適切なアクションを実行するために、呼び出し元関数に複数のことを伝える必要がある場合があります。すべてを区切られた文字列として返すと、呼び出し元の関数が文字列を解析せずに情報を取得できない場合があります (これは独自の問題につながるため、お勧めしません)。

それ以外の唯一の方法は、必要なすべての情報のメンバー変数を含むオブジェクトを返すことです。これは、各関数がその状態オブジェクトを持つ必要があるため、「状態」オブジェクトが多すぎる可能性があります。

この要件を最もエレガントな方法で設計する方法を理解したいと思います。コーディングの時点で、Func A は、呼び出し元の関数がエラーから回復するためのインテリジェンスを持っているかどうかわからない可能性があるため、例外をスローしたくないことに注意してください。また、例外を使用せずにそのような設計が可能である (同時にエレガントである) かどうかを確認したいと思います。

関数ごとにデータ オブジェクトを使用して通信することが唯一の方法である場合、それが専門的なライブラリの作成方法です。汎用データ オブジェクトは存在できますか? 将来的に新しい関数が追加される可能性があることに注意してください。これらの関数には、異なる状態変数、またはエラーに関するサポート情報が含まれる場合があります。

また、関数の戻り値は「状態」オブジェクトであるため、関数が返すはずの実際のデータを ref または out パラメータとして渡す必要がある場合があることにも注意してください。

このための設計パターンはありますか?

この質問を投稿する前に、次の記事を読みました。

http://blogs.msdn.com/b/ricom/archive/2003/12/19/44697.aspx

例外がスローされない場合、try/catch ブロックはパフォーマンスを低下させますか?

例外のないエラー処理

コードフロー制御や回復可能なエラーには例外を使用しないことを提案している他の多くの記事も読みました。また、例外のスローには独自のコストがあります。さらに、呼び出し元の関数が、呼び出された関数のそれぞれによってスローされた例外から回復したい場合は、各関数呼び出しを try catch ブロックで囲む必要があります。汎用の try catch ブロックでは次の行から「続行」できないためです。エラー行の。

編集:

いくつかの具体的な質問: 2 つの異なるデータベースを同期するアプリケーションを作成する必要があります。1 つは専有データベースで、もう 1 つは SQL Server データベースです。再利用可能な関数を別のレイヤーにカプセル化したい。

機能は次のとおりです。独自のアプリケーションは多くのデータベースを持つことができます。これらの各データベースからの情報の一部は、単一の共通 SQL Server データベースにプッシュする必要があります。専有アプリケーションのデータベースは、アプリケーションの GUI が開いているときにのみ読み取ることができ、XML を介してのみ読み取ることができます。

アルゴリズムは次のようになります。

  1. 独自のアプリケーションで公開されているデータベースのリストを読む
  2. データベースごとに、同期プロセスを開始します。
  3. このデータベースに現在ログインしているユーザーが同期権限を持っているかどうかを確認します。(注: 各データベースは、異なるユーザー ID を使用して開くことができます)。
  4. このデータベースからデータを読み取ります。
  5. データを SQL Server に転送する
  6. 次のデータベースに進みます。

このアプリケーションの開発中に、ReadUserPermission、ReadListOfDatabases など、いくつかの再利用可能な関数を作成します。

この場合、権限が存在しないことを ReadUserPermission が検出した場合、呼び出し元はこれをログに記録し、次に開いているデータベースに進む必要があります。ReadListOfDatabases が独自のアプリケーションとの接続を確立できない場合、呼び出し元は自動的にアプリケーションを起動する必要があります。

では、どのエラー状態を例外として通知し、どのエラー状態をリターン コードを使用して通知する必要があるのでしょうか?

再利用可能な関数は、呼び出し元が異なるエラー回復要件または機能を持っている可能性がある他のプロジェクトで使用される可能性があるため、留意する必要があることに注意してください。

編集:

ファンク A がファンク B、C、D、E、F、G を呼び出し、ファンク B が何らかのエラー状態で例外をスローした場合、ファンク A はこのエラーから回復でき、続行したいと考えています。残りの実行、つまりFunc B、C、D、...を呼び出します。例外処理により、これを「エレガントに」行うにはどうすればよいですか? 唯一の解決策は、残りのステートメントが実行されるように、try catch ブロック内で B、C、D などのそれぞれへの呼び出しをラップすることです。

次の 2 つのコメントもお読みください。

https://stackoverflow.com/a/1279137/1113579

https://stackoverflow.com/a/1272547/1113579

エラーの回復と残りのコードの実行が、パフォーマンスに影響を与えることなくエレガントに達成できる場合、私は例外の使用を嫌いませんまた、わずかなパフォーマンスへの影響は問題ではありませんが、デザインはスケーラブルで洗練されている必要があります。

編集:

わかりました、「Zdeslav Vojkovic」のコメントに基づいて、私は今、例外の使用を考えています。

例外を使用する場合、例外を使用せずにリターン コードを使用するユース ケースを教えてください。注:関数が返すはずのデータではなく、戻りコードについて話している。リターンコードを使用して成功/失敗を示すユースケースはありますか、またはユースケースはありませんか? それは私がよりよく理解するのに役立ちます。

「Zdeslav Vojkovic」から理解した例外の使用例の 1 つは、呼び出し先関数が呼び出し元関数に何らかの条件を強制的に通知し、呼び出し元の実行を中断したい場合です。例外がない場合、呼び出し元は戻りコードを調べることを選択する場合としない場合があります。ただし、例外が発生した場合、実行を継続したい場合、呼び出し元の関数は必ず例外を処理する必要があります。

編集:

もう一つ面白いアイデアがありました。呼び出し元関数がエラーから回復するという考えをサポートしたい呼び出し先関数は、イベントを発生させ、イベントが処理された後にイベント データをチェックし、例外をスローするかどうかを決定できます。エラーコードは一切使用されません。回復されないエラーには例外が使用されます。基本的に、呼び出し先関数がコントラクトの内容を実行できない場合、使用可能なイベント ハンドラーの形式で「ヘルプ」を要求します。それでもコントラクトを実行できない場合は、例外がスローされます。利点は、例外をスローする追加のオーバーヘッドが削減され、呼び出し先関数またはその呼び出し元関数のいずれかがエラーから回復できない場合にのみ例外がスローされることです。

呼び出し元の関数がエラーを処理するのではなく、呼び出し元の呼び出し元の関数がエラーを処理したい場合、カスタム イベント ディスパッチャーは、イベント ハンドラーがイベント登録の逆順、つまり最後に登録されたイベントで呼び出されるようにします。 handler は、他の登録済みイベント ハンドラーより前に呼び出す必要があります。このイベント ハンドラーがエラーを解決できる場合、後続のイベント ハンドラーはまったく呼び出されません。一方、最新のイベント ハンドラーがエラーを解決できない場合、イベント チェーンは次のハンドラーに伝達されます。

このアプローチについてフィードバックをお寄せください。

4

3 に答える 3

1

「それはプロのライブラリが書かれた方法ですか?」

いいえ、プロのライブラリはエラー処理の例外を使用して作成されています。提案されたアプローチを使用するためのパターンがあるかどうかはわかりませんが、(.NET では) アンチパターンと考えています。結局、.NET 自体は専門的なフレームワークであり、例外を使用します。さらに、.NET 開発者は例外に慣れています。あなたのライブラリは、ユーザーにまったく異なるエラー処理の方法を学ばせるほど特別なものだと思いますか?

あなたがしたことは、COM エラー処理を再発明したことです。それがあなたのやりたいことなら、これISupportErrorInfoインターフェースをチェックしていくつかのアイデアを見つけてください。

なぜこれをしたいのですか?パフォーマンスの「最適化」だと思います。

例外処理に関するパフォーマンスの問題に対する懸念は、ほとんどの場合、時期尚早の最適化です。おそらくまったく存在しない問題を解決するために、各戻り値を ref/out パラメータで処理する必要があり、ライブラリのすべてのユーザーを傷つける扱いにくい API を作成します。

「Func A は、呼び出し元の関数がエラーから回復するためのインテリジェンスを持っているかどうかわからない可能性があるため、例外をスローしたくありません」

では、呼び出し元が FuncA がシステムの不変条件を台無しにすることを静かに許可し、呼び出し元がただ幸せに進むことを保証したいですか? これにより、後で別の関数で発生する一見不可能なバグをデバッグすることがはるかに難しくなります。

例外を避けることが理にかなっているシナリオもありますが、それには正当な理由が必要です。例外は良い考えです。

EDIT:「コードフロー制御に例外を使用しないことを提案する他の多くの記事も読んだ」と追加したようです。その通りです。.NET の例外はコード フロー用ではなく、エラー処理用です。

あなたが尋ねる:

Func A が Func B、C、D、E、F を呼び出し、各呼び出しを try catch でカプセル化する必要がある場合、エラーから回復できるか、残りの関数呼び出しを引き続き実行する必要があるため、try catch ステートメントはあまり多くありません。

代替に過ぎません。関数から返されたすべてのエラーを同じ方法で簡単に処理できるという間違いを犯していますが、通常はできません。

すべての関数を個別に処理する必要があるかどうかを検討してください - 最悪のシナリオとコードは通常そのようには書かれていません:

Result x, y;
try {
   x = Function1();
}
catch(SomeException e) {
   // handle error   
}
try {
   y = Function2();
}
catch(SomeOtherException e) {
   // handle error   
}

に対して:

int error;
Result x, y;
error = Function1(out x);
if(error != SOME_KNOWN_ISSUE) {
   // handle error    
}
error = Function2(out y);
if(error != SOME_KNOWN_ISSUE) {
   // handle error
}

大きな違いではありません。エラー コードを確認しないとは言わないでください。ただし、すべてのエラーを無視することにした場合 (恐ろしい考えです)、例外はより単純になります。

try {
    var x = Function1();
    var y = Function2();    
    var z = Function3();
}
catch Exception() { you still can see the message here and possibly rethrow }

Result1 r1;
Function1(out r1);
Result2 r2;
Function2(out r2);
Result3 r3;
Function3(out r3);
// and here you still don't know whether there was an error

「時間の制約に関して予測可能性が必要です」とはどういう意味ですか?

一部のシステムレベルのソフトウェアまたはリアルタイムのものでは、例外処理に関連するスタックの巻き戻しを行う余裕がありません。これは、期間を保証できず、タイミング要件に違反する可能性があるためです。しかし、ガベージ コレクションはこの点ではるかに悪いため、これは .NET には当てはまりません。

また、「.NET では常にエラー処理に例外を使用します」と言う場合、エラー条件としてどのように、または何を定義するのか説明できますか? 回復可能な状態はエラー状態ですか、それともエラー状態ではありませんか? –</p>

@shambulater はすでにコメントで素晴らしい例を挙げています。ではFileStream、不足しているファイルは回復できず、スローされます。クライアントではFileStream、コンテキストに応じて回復可能または不可能な場合があります。一部のクライアントはそれを無視し、一部はアプリを終了し、一部は別の例外でラップして上流の誰かに決定させます。

例外を使用しないのはいつですか?

エラーコードも返さない場合。

于 2013-03-08T16:23:21.197 に答える
1

例外をスローしたくないすべてのメソッドでパラメーターとして使用する共通FunctionResultオブジェクトはどうですか?out

public class FuncResultInfo
    {
        public bool ExecutionSuccess { get; set; }
        public string ErrorCode { get; set; }
        public ErrorEnum Error { get; set; }
        public string CustomErrorMessage { get; set; }

        public FuncResultInfo()
        {
            this.ExecutionSuccess = true;
        }

        public enum ErrorEnum
        {
            ErrorFoo,
            ErrorBar,
        }
    }

    public static class Factory
    {
        public static int GetNewestItemId(out FuncResultInfo funcResInfo)
        {
            var i = 0;
            funcResInfo = new FuncResultInfo();

            if (true) // whatever you are doing to decide if the function fails
            {
                funcResInfo.Error = FuncResultInfo.ErrorEnum.ErrorFoo;
                funcResInfo.ErrorCode = "234";
                funcResInfo.CustomErrorMessage = "Er mah gawds, it done blewed up!";
            }
            else
            {
                i = 5; // whatever.
            }

            return i;
        }
    }

例外なく失敗する可能性のあるすべての関数に、そのoutパラメーターがあることを確認してくださいFuncResultInfo

于 2013-03-08T15:31:14.057 に答える