none
エラー番号とメッセージを出力したい RRS feed

  • 質問

  • こんにちは。

    作成中のアプリで、エラー時にイベントログにエラー番号とメッセージを出力させたいと思っています。
    MS Office製品などがエラー時に決まった番号とエラーメッセージ、および状況を示す詳細情報を出しますが、これに近いことができればと思っています。

    前任者がC++で書いたコードでは

    #define MYLIB_ERR_FILENOTFOUND            ( -1 )
    #define MYLIB_ERRMSG_FILENOTFOUND            _T( "ファイルが見つかりません。" )

    という組み合わせが50個くらい列挙されていて、毎回両者と追加情報からメッセージを作成しています。
    できれば両者の情報を1個にしてMy○○Exception のようなものを定義して、

    throw new MyLibErrxxxxException("reason")

    もしくは両者を構造体などにしておいて

    throw new MyLibException(MyLibErr.xxxx, "reason")

    みたいな操作をするとイベントログに○○エラー番号とメッセージを吐くような動作ができたらいいなと思っています。

    class MyLibErrxxxxException: Exception
        {
            const int errCode = 1;
            const string message = "ファイルがありません";
            private string reason;
            public PlntFileNotFoundException(string path, string reason)
            {
                this.reason = reason;
            }
        }

    というのを考えてみましたが、これを50個書くのはスマートではないような気がします。よいお手本などはないでしょうか?

    どうぞよろしくお願いいたします。
    2013年8月16日 10:14

回答

  • enum の値にカスタム属性をつける、という方法もあります。

    「今回は上位とのインターフェースのため昔のエラーログ等の書式を踏襲しなければならない」のなら、例外の出番ではないのでは?


    Jitta@わんくま同盟

    • 回答としてマーク DOD_Watanabe 2017年5月2日 6:45
    2013年8月19日 13:16
  • イメージとしてはこんなものなのでしょうかね?

    {
        throw new MyCustomException(ExceptionCode.FileNotFound);
    }
    
    enum ExceptionCode
    {
        Unknown,
        InvalidPath,
        FileNotFound,
    }
    
    class MyCustomException : Exception
    {
        private static readonly Dictionary<ExceptionCode, string> _messages =
            new Dictionary<ExceptionCode, string>
                {
                    { ExceptionCode.FileNotFound, "ファイルが見つかりません" },
                    { ExceptionCode.InvalidPath, "パスを間違えています"}
                };
    
        public ExceptionCode ExceptionCode { get; private set; }
    
        public MyCustomException(ExceptionCode exceptionCode)
            : base(_messages[exceptionCode])
        {
            ExceptionCode = exceptionCode;
        }
    }

    ただ、このコードを見ていると次のようなコードを書くのかと疑問に感じます。

    try
    {
        using (new FileStream(@"hogehoge.txt", FileMode.Open))
        { }
    }
    catch (FileNotFoundException)
    {
        // .NETの例外をキャッチして自作の例外で投げ直すのか?
        throw new MyCustomException(ExceptionCode.FileNotFound);
    }
    catch (PathTooLongException)
    {
        // 個別のエラーごとにcatchブロックを書かないといけないけど、そうするのか?
        throw new MyCustomException(ExceptionCode.InvalidPath);
    }

    あと考えられる課題としていくつか列挙しておきます。

    • 想定される例外はキャッチして自作の例外をスローし直すとして、想定していなかった例外はどうしますか?
    • スローした後、どこでキャッチしますか?ログに書き込む処理は各所に用意するのですか?

    どういった形で実装側がスローするか、どういった形で利用者がキャッチするか、キャッチした後の処理で楽な形は何かという風に、最終的にどのように扱うかも含めて、検討・設計した方がよいように感じています。
    「C++ がこうだったからこうしなきゃいけない」ということがないのであれば、C# としてどういう風に作ると楽になるかを考えた方が良さそうです。

    // .NET の例外の時点で多数に分岐してるので、すでに面倒くさいというのも事実。
    // 個人的には Exception で集約して、GetType().Name と Message, StackTrace あたりをログに残したい。
    // でも、Exception だと StackOverflowException とか重大な例外もくるので難儀。

    2013年8月16日 13:10
    モデレータ

すべての返信

  • イメージとしてはこんなものなのでしょうかね?

    {
        throw new MyCustomException(ExceptionCode.FileNotFound);
    }
    
    enum ExceptionCode
    {
        Unknown,
        InvalidPath,
        FileNotFound,
    }
    
    class MyCustomException : Exception
    {
        private static readonly Dictionary<ExceptionCode, string> _messages =
            new Dictionary<ExceptionCode, string>
                {
                    { ExceptionCode.FileNotFound, "ファイルが見つかりません" },
                    { ExceptionCode.InvalidPath, "パスを間違えています"}
                };
    
        public ExceptionCode ExceptionCode { get; private set; }
    
        public MyCustomException(ExceptionCode exceptionCode)
            : base(_messages[exceptionCode])
        {
            ExceptionCode = exceptionCode;
        }
    }

    ただ、このコードを見ていると次のようなコードを書くのかと疑問に感じます。

    try
    {
        using (new FileStream(@"hogehoge.txt", FileMode.Open))
        { }
    }
    catch (FileNotFoundException)
    {
        // .NETの例外をキャッチして自作の例外で投げ直すのか?
        throw new MyCustomException(ExceptionCode.FileNotFound);
    }
    catch (PathTooLongException)
    {
        // 個別のエラーごとにcatchブロックを書かないといけないけど、そうするのか?
        throw new MyCustomException(ExceptionCode.InvalidPath);
    }

    あと考えられる課題としていくつか列挙しておきます。

    • 想定される例外はキャッチして自作の例外をスローし直すとして、想定していなかった例外はどうしますか?
    • スローした後、どこでキャッチしますか?ログに書き込む処理は各所に用意するのですか?

    どういった形で実装側がスローするか、どういった形で利用者がキャッチするか、キャッチした後の処理で楽な形は何かという風に、最終的にどのように扱うかも含めて、検討・設計した方がよいように感じています。
    「C++ がこうだったからこうしなきゃいけない」ということがないのであれば、C# としてどういう風に作ると楽になるかを考えた方が良さそうです。

    // .NET の例外の時点で多数に分岐してるので、すでに面倒くさいというのも事実。
    // 個人的には Exception で集約して、GetType().Name と Message, StackTrace あたりをログに残したい。
    // でも、Exception だと StackOverflowException とか重大な例外もくるので難儀。

    2013年8月16日 13:10
    モデレータ
  • "ファイルがありません"という例外を定義しているところが気になりました。エラーには基本的に業務エラーとシステムエラーがあります。業務エラーはユーザーのオペレーションでアプリケーションの正常な実行に戻れるもの、システムエラーはアプリケーションを止める必要があるものと大雑把に考えることができます。
    "ファイルがありません"は、どちらのエラーにも成りえるのですが、もし、業務エラーの場合、.NETでは例外を使うのではなく、戻り値で判断することが推薦されています。要するにファイルの存在をエラーチェックの結果として判断するということです。
    この辺りがあやふやだと思われた場合は、以下からのシリーズを読まれることをお勧めします。

    .NETの例外処理 Part.1
    http://blogs.msdn.com/b/nakama/archive/2008/12/29/net-part-1.aspx

    さて、Exceptionを継承した50個のクラスですが、プロパティなどのメンバーやコンストラクタが違うのであれば、そのまま50個定義されても良いように思います。スマートではないかもしれませんが、可読性やメンテナンス性は優れていると思います。ログに記録するのであれば、それ用のクラスを定義し、そこから派生させるようにしても良いと思います。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    2013年8月17日 2:22
    モデレータ
  • Azurean様

    enum, Dictionary について教本を調べ直してみました。

    名前に対して任意の数字を割り振れるとあったのでエラー番号等は従来の製品と合わせることができます。これとDictionaryの組み合わせならスマートに作れそうです。

    今回は上位とのインターフェースのため昔のエラーログ等の書式を踏襲しなければならないのですが、trapemiya様の指摘にもあったように何でも例外というのではなく、事前の存在チェックの類でやるほうが先決です。それでも最終的に成功、失敗にかかわらず最終的に出力を番号、メッセージ、詳細の3点セットにしないといけないので、enum/Dictionary は効果的に使えそうです。

    あとは、呼び出し元に遡るところで楽が出来るように作りたいと思っていますが、頑張ってみます。

    ありがとうございました。

    2013年8月19日 4:36
  • trapemiya様

    ありがとうございます。

    メッセージのまとめ方の質問でFileNotFound を例にしたのは、まずかったかもしれません。

    ところで今回作成しているアプリは一種のプラグインでして、何が起こっても次のジョブが続行できるように後始末してメッセージを出して終わるものなので、何でも業務エラーにまとめてしまっている、ということになるでしょうか。
    でも出力をごっちゃにしている分、中では両者を別物と意識しておく必要があるようですね。

    がんばってみます。


    2013年8月19日 4:53
  • enum の値にカスタム属性をつける、という方法もあります。

    「今回は上位とのインターフェースのため昔のエラーログ等の書式を踏襲しなければならない」のなら、例外の出番ではないのでは?


    Jitta@わんくま同盟

    • 回答としてマーク DOD_Watanabe 2017年5月2日 6:45
    2013年8月19日 13:16
  • 超遅レスですがJittaさん有難うございました。

    enum のカスタム属性というのは、まさに欲しかった解決策のようです。

    こんな感じに定義できるのですね。パラメータのセットがバラけずにまとめられて、

    とてもスマートな解決になりそうです!

    enum Codes {

    [Display(Name = "あんなエラー")]
        0,
    [Display(Name = "こんなエラー")] 1,
    [Display(Name = "そんなエラー")] 2 }


    リンク切れだったので、以下のサイトを参照してみました。

    http://qiita.com/hugo-sb/items/b8f6f09f0c38ff4a520d

    2017年5月2日 6:51