3

この投稿では、@Eric Lippert による例外の分類を使用します。これは、ここで見つけることができます: Vexing exceptions

この場合の最も重要なもの:

骨の折れる例外はあなた自身の過ちです。それらを防ぐことができたので、コードのバグです。それらを捕まえてはいけません。そうすることは、コードのバグを隠すことです。むしろ、そもそも例外が発生しないようにコードを記述して、キャッチする必要がないようにする必要があります。

外因性例外は、不幸な設計上の選択の結果ではないことを除けば、やや厄介な例外に似ているように見えます。むしろ、それらは、整頓されていない外部の現実が、美しく鮮明なプログラム ロジックに影響を与えた結果です。予期しない外因性条件を示す例外を常に処理します。一般に、考えられるすべての障害を予測することは価値がなく、実用的でもありません。操作を試して、例外を処理する準備をしてください。

おそらくすべての開発者が経験したように、大規模なエンタープライズ ソフトウェアでは骨の折れる例外を 100% 回避することはできません。

骨の折れる例外がスローされるという不幸な状況では、私はユーザーに知らせたいので、彼は私たちにバグを報告します (第 3 レベルのサポート)。また、この場合、ログレベル「エラー」のメッセージをログに記録したいと考えています。

外因性の例外については、ヒントを含むより具体的なメッセージをユーザーに表示したいと思います。おそらく、ユーザーは自分で問題を解決できるからです (おそらく、第 1 レベルまたは第 2 レベルのサポートの助けを借りて)。

私が現在これを達成する方法は、低レベルのコンポーネントで明示的に外因性の例外のみをキャッチし、それらをカスタム例外にラップすることです。最上層 (私の場合は MVVM WPF アプリケーションの ViewModel) で、カスタム例外を明示的にキャッチして、警告を表示します。2 番目の catch ブロックでは、一般的な例外をキャッチしてエラーを表示します。

これは、エンタープライズ アプリケーションで骨の折れる例外と外因性の例外を区別するための一般的で適切な方法ですか? より良いアプローチはありますか?それとも、まったく必要ありませんか?

この記事dotnetpro - Implementierungsausnahmen を読んだ後、ログに記録するときにより多くのコンテキスト情報を提供するために、すべての (骨の折れる) 例外をカスタム例外にラップする必要があるかどうかも疑問に思っています。

すべての例外のラップについて、次の投稿を見つけました: stackoverflow - 一般的な例外をキャッチしてラップする必要がありますか? およびstackoverflow - 考えられるすべての特定の例外をキャッチする必要がありますか、それとも一般的な例外だけをキャッチしてカスタム例外でラップする必要がありますか? それはかなり議論の余地があり、ユースケースに依存しているように見えるので、私の場合はわかりません.

ViewModel での高レベルの catch ハンドラーの例:

public class MainWindowViewModel
{
    private readonly ICustomerRepository _customerRepository;

    public MainWindowViewModel(ICustomerRepository customerRepository)
    {
        _customerRepository = customerRepository;
        PromoteCustomerCommand = new DelegateCommand(PromoteCustomer);
    }

    public ICommand PromoteCustomerCommand { get; }

    private void PromoteCustomer()
    {
        try
        {
            Customer customer = _customerRepository.GetById(1);
            customer.Promote();
        }
        catch (DataStoreLoadException ex)
        {
            // A expected exogenous exception. Show a localized message with some hints and log as warning.
            Log(LogLevel.Warning, ex);
            ShowMessage("Unable to promote customer. It could not be loaded. Try to...", ex);
        }
        catch (Exception ex)
        {
            // A unexpected boneheaded exception. Show a localized message, so that the users contacts the support and log as error.
            Log(LogLevel.Error, ex);
            ShowMessage("Unable to promote customer because of an unknown error. Please contact support@example.com", ex);
        }
    }
}

低レベルの例外ラッピングの例:

public class SqlCustomerRepository : ICustomerRepository
{
    public Customer GetById(long id)
    {
        try
        {
            return GetFromDatabase(id);
        }
        catch (SqlException ex)
        {
            // Wrap the exogenous SqlException in a custom exception. The caller of ICustomerRepository should not depend on any implementation details, like that the data is stored in a SQL database.
            throw new DataStoreLoadException($"Unable to get the customer with id {id} from SQL database.", ex);
        }

        // All other exceptions bubble up the stack without beeing wrapped. Is it a good idea, or should I do something like this to provide additional context? (Like the id in this case)
        /*catch (Exception ex)
        {
            throw new DataStoreException($"Unknown error while loading customer with id {id} from SQL database.", ex);
        }*/
    }
}
4

1 に答える 1