C# で重複するエラー処理コードを減らすには?
-
08-06-2019 - |
質問
私は例外処理の仕組みに完全に満足したことはありません。多くの例外が発生し、try/catch がテーブルに持ち込まれます (スタックの巻き戻しなど)。しかし、その過程で OO モデルの多くが壊れているようです。
とにかく、問題は次のとおりです。
ネットワーク化されたファイル IO 操作をラップまたはインクルードするクラスがあるとします (例:どこかの特定の UNC パスにあるファイルの読み取りと書き込み)。さまざまな理由から、これらの IO 操作が失敗することは望ましくないため、失敗を検出した場合は再試行し、成功するかタイムアウトに達するまで再試行を続けます。私はすでに、再試行の間に現在のスレッドをスリープさせたり、タイムアウト期間がいつ経過したかを判断したりするためにインスタンス化して使用できる便利な RetryTimer クラスを持っています。
問題は、このクラスのいくつかのメソッドに多数の IO 操作があり、それらのそれぞれを try-catch / 再試行ロジックでラップする必要があることです。
コード スニペットの例を次に示します。
RetryTimer fileIORetryTimer = new RetryTimer(TimeSpan.FromHours(10));
bool success = false;
while (!success)
{
try
{
// do some file IO which may succeed or fail
success = true;
}
catch (IOException e)
{
if (fileIORetryTimer.HasExceededRetryTimeout)
{
throw e;
}
fileIORetryTimer.SleepUntilNextRetry();
}
}
では、クラス全体のすべてのファイル IO 操作でこのコードの大部分が重複することを避けるにはどうすればよいでしょうか?私の解決策は、匿名のデリゲート ブロックと、それに渡されたデリゲート ブロックを実行するクラス内の単一のメソッドを使用することでした。これにより、他のメソッドで次のようなことを行うことができるようになりました。
this.RetryFileIO( delegate()
{
// some code block
} );
これはある程度気に入っていますが、まだ物足りない点がたくさんあります。他の人がこの種の問題をどのように解決するかを聞きたいです。
解決
これはアスペクト指向プログラミングについて学ぶ絶好の機会のように思えます。ここに良い記事があります .NET の AOP. 。一般的な考え方は、部門横断的な懸念事項 (つまり、x 時間再試行) を別のクラスに追加し、そのように動作を変更する必要があるメソッドにアノテーションを付けます。次のようになります (Int32 の優れた拡張メソッドを使用)
[RetryFor( 10.Hours() )]
public void DeleteArchive()
{
//.. code to just delete the archive
}
他のヒント
ちょっと疑問に思ったのですが、あなたのやり方にまだ何が足りないと思いますか?匿名デリゲートを..名前は?代理人、のようなもの
public delegate void IoOperation(params string[] parameters);
public void FileDeleteOperation(params string[] fileName)
{
File.Delete(fileName[0]);
}
public void FileCopyOperation(params string[] fileNames)
{
File.Copy(fileNames[0], fileNames[1]);
}
public void RetryFileIO(IoOperation operation, params string[] parameters)
{
RetryTimer fileIORetryTimer = new RetryTimer(TimeSpan.FromHours(10));
bool success = false;
while (!success)
{
try
{
operation(parameters);
success = true;
}
catch (IOException e)
{
if (fileIORetryTimer.HasExceededRetryTimeout)
{
throw;
}
fileIORetryTimer.SleepUntilNextRetry();
}
}
}
public void Foo()
{
this.RetryFileIO(FileDeleteOperation, "L:\file.to.delete" );
this.RetryFileIO(FileCopyOperation, "L:\file.to.copy.source", "L:\file.to.copy.destination" );
}
よりオブジェクト指向のアプローチを使用することもできます。
- エラー処理を行い、抽象メソッドを呼び出して具体的な作業を実行する基本クラスを作成します。(テンプレートメソッドパターン)
- 操作ごとに具体的なクラスを作成します。
これには、実行する各タイプの操作に名前を付けることができ、コマンド パターンが得られるという利点があります。操作はオブジェクトとして表現されます。
最近私がやったことは次のとおりです。おそらく他の場所でもっとうまく行われていると思いますが、かなりクリーンで再利用可能だと思われます。
次のようなユーティリティ メソッドがあります。
public delegate void WorkMethod();
static public void DoAndRetry(WorkMethod wm, int maxRetries)
{
int curRetries = 0;
do
{
try
{
wm.Invoke();
return;
}
catch (Exception e)
{
curRetries++;
if (curRetries > maxRetries)
{
throw new Exception("Maximum retries reached", e);
}
}
} while (true);
}
次に、私のアプリケーションでは、物事を整理するために C# の Lamda 式構文を使用します。
Utility.DoAndRetry( () => ie.GoTo(url), 5);
これにより、私のメソッドが呼び出され、最大 5 回再試行されます。5 回目の試行で、元の例外が再試行例外内で再スローされます。