web-dev-qa-db-ja.com

カスタム例外をスロー

まず、私は知っています-例外処理のトピックについて説明しているオンラインリソースはたくさんありますが、それでも私には不明確な点があります。

別のプロジェクトから呼び出された外部ライブラリにこのコードを含めることを検討してください。

public Task<List<Ident>> GetFoo(){
    List<Ident> idents = new List<Ident>();
    idents = await IdentRepo.GetIdents(); // some rest-API call with HttpClient..
    return idents;
} 

ある時点で、ある種の例外処理を実装したいと思います。たとえば、接続しているWebサービスが利用できない場合などです。私は次のようなことができました

public Task<List<Ident>> GetFoo(){
    List<Ident> idents = new List<Ident>();
    try{ 
        idents = await IdentRepo.GetIdents(); // some rest-API call with HttpClient..
    } catch (HttpRequestException e){
        Diag.Log(e);
    }

    return idents;
} 

呼び出し元はWebサービス要求が失敗したことを知らず、リストが実際に空であるかどうか、または何か他のことが起こったかどうかを知らないため、私はそのアプローチが好きではありません。

より良い解決策はこのようなものです

public Task<List<Ident>> GetFoo(){
    List<Ident> idents = new List<Ident>();
    try{ 
        idents = await IdentRepo.GetIdents(); // some rest-API call with HttpClient..
    } catch (HttpRequestException e){
        Diag.Log(e);
        throw;
    }
    return idents;
} 

ここで、呼び出し元も例外を処理する必要があります。これでうまくいきますが、呼び出し元が特定の種類の例外を知っているのは好きではありません。呼び出し元は、HTTPを使用したREST APIリクエストであるか、ローカルファイルからデータをロードしているかを気にしません。失敗すると失敗します。

だから-imo-最善の解決策は、カスタム例外をスローすることです。

public Task<List<Ident>> GetFoo(){
    List<Ident> idents = new List<Ident>();
    try{ 
        idents = await IdentRepo.GetIdents(); // some rest-API call with HttpClient..
    } catch (HttpRequestException e){
        Diag.Log(e);

        throw new IdentProviderNotAvailable();
    }
    return idents;
} 

そのコードを使用すると、IdentProviderNotAvailable例外に注意するだけで済みます。 IdentProviderNotAvailableをスローするだけなので、catchブロックに新しい例外タイプを追加してもかまいません。

それは良いアプローチですか?元の例外のスタックトレースを含める必要がありますか、これを処理するためのより良い方法はありますか?

2
Shawn

あなたの例では、

_public Task<List<Ident>> GetFoo(){
    List<Ident> idents = new List<Ident>();
    try{ 
        idents = await IdentRepo.GetIdents(); // some rest-API call with HttpClient..
    } catch (HttpRequestException e){
        Diag.Log(e);
        throw;
    }
    return idents;
}
_

このアプローチには大きな利点が1つあります。ただし、throwを介して例外を再スローすると、完全なスタックトレースが保持されます。

しかし、あなたが言うように、それは効果的に実装詳細であるという欠点があります。スローされる正確な例外は、情報を取得する基本的なメカニズムによって異なります。これにより、開発者はExceptionをキャッチするだけで済むようになる可能性があり、これは一般に悪い習慣と見なされています。

したがって、カスタム例外を導入することは、これらの状況で良い考えです。ただし、サンプルコードについて注意すべき点が2つあります。

  1. すべての例外クラスの末尾にExceptionを付けることは、.NETの強力な規則です。その名前をIdentProviderNotAvailableExceptionにします。あなたは醜いと不必要だと思うかもしれません、そしてあなたがそう思うなら私はあなたに同意します。しかし、「正しいことをする」よりも慣例に従うと、他の人がコードを理解しやすくなるので、このコードのフローに進んでください。

  2. その例外がスローされる原因となった情報(スタックトレースを含む)が失われるため、カスタム例外の内部例外にHttpRequestException値を含めることをお勧めします。したがって、コンストラクタを次のように変更します。

    public IdentProviderNotAvailableException(Exception inner) : base("some message", inner) {}

その情報をキャプチャします。

例外を飲み込んで空のリストを返すことは悪い考えかもしれませんが、この場合に例外をスローする代わりに、tryパターンがあります。 C#では、規則はブールリターンとoutパターンを介してtryパターンを実装することです:

_public bool TryGetFoo(out List<Ident> idents)
{
    List<Ident> idents = new List<Ident>();
    try
    { 
        idents = await IdentRepo.GetIdents();
        return true;
    } 
    catch (HttpRequestException e)
    {
        Diag.Log(e);
        idents = null;
        return false;
    }
}
_
2
David Arno

常に覚えているthe構造化例外の最も重要な部分Handlingはその最後のビットです-handling 例外、または私の言い方では、「何かを行う役に立つ」。

ある時点で、ある種の例外処理を実装したいと思います...

そして、それは結構です。
yourコードが例外をキャッチして処理する場合、クライアントはそれについて何も知る必要はありません。あなたはそれを「処理」しました。

ここで、呼び出し元も例外を処理する必要があります。

これは、yourの家を整然と維持し、リソースとそのすべての優れたものをクリーンアップするためにできる限りのことを行った場合であり、おそらく、診断、目的のために、完全な例外をどこかに記録します。ただし、クライアントはこの例外を「処理」できません。 通知された何かがおかしいだけです「内部」例外の詳細をすべて送信しても意味がないため、プロセス境界インターフェースでnew例外を作成してスローします。詳細な例外を作成してから、代わりに穏やかなComputerSaysNoExceptionを作成してスローします。これにより、スタックトレースも切り捨てられ、実装の詳細もすべて非表示になります。

クライアントが(おそらく)できることは、そのメッセージをdisplayすることだけです。エラーから「回復」するためにできることは、あるとしてもほとんどないので、[安全に]表示できる単一のクラスがおそらく行うことになります。

0
Phill W.