Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
186
Help us understand the problem. What is going on with this article?
@tokishirazu

運用者が困る例外処理の書き方

More than 3 years have passed since last update.

はじめに

 運用者はシステムでエラーが発生した際に、プログラムから出力されたログを見て原因の特定作業を行います。このとき重要となるのが例外処理です。適切な例外処理が行われていれば、原因の特定が容易になります。
 ここでは、こんなことをされたら運用者が困るという例を見ていきます。使用している言語はC#です。

1. 例外のメッセージを見ても何もわからない

悪い例
if(name == null)
{
    throw new Exception("例外が発生しました。");
}
良い例
if(name == null)
{
    throw new Exception("パラメータ:nameがnullです。");
}

 例外メッセージはエラーの内容を表す重要な情報です。どのような問題が発生したかがひと目で分かるような情報を記述しましょう。「例外が発生しました。」「処理エラー」「Fatal」といったメッセージではどのような例外が発生したを知ることはできません。
 「良い例」では手書きでメッセージを記述していますが、throw new ArgumentNullException("name");と書いても同じようなメッセージが出力されます。出力されるメッセージのフォーマットが整っていると、ログが把握しやすくなる場合があります。

2. 例外が握りつぶされている

悪い例
try
{
    // 何らかの処理
}
catch (Exception ex)
{
}
良い例
try
{
    // 何らかの処理
}
catch (Exception ex)
{
    logger.Error(ex.Message);
    logger.Error(ex.StackTrace);
}

 「悪い例」では発生した例外に対して処理を何も行っていません。本来は例外内容のログを残すか、上位層に例外を再スローしログ処理を任せるかの、どちらかの処理を行わなければいけません。ログがなければどのようなエラーが起きたのか分かりません、ログは必ず残してください。

悪い例
try
{
    // 何らかの処理
}
catch (Exception ex)
{
    throw ex;
}
良い例
try
{
    // 何らかの処理
}
catch (Exception ex)
{
    throw;
}

 「悪い例」は一見問題なさそうに見えますが、この処理ではキャッチした例外のStackTraceが消えてしまうという問題点があります(C#の仕様です)。StackTraceが消えてしまうと例外がどこで発生したのかが分かりません。「良い例」のようにthrow;と書くとStackTraceを消さずに再スローすることができます。

Catch句の外で再スローする場合 2017.1.14追記

悪い例
Exception exStack == null;
try
{
    // 何らかの処理
}
catch (Exception ex)
{
  exStack = ex;
}

throw exStack;
良い例
Exception exStack = new Exception();
try
{
    // 何らかの処理
}
catch (Exception ex)
{
    exStack = ex;
}

ExceptionDispatchInfo.Capture(exStack).Throw();

 "throw;"の記述はCatch句でしか行なえません。Catch句外で例外を再スローするとスタックトレースが消えます。.NetFrameWork4.5以降ではExceptionDispatchInfoクラスを上記のように利用することでスタックトレースを保持したまま例外の再スローが行えます。

3. キャッチする必要のない例外もキャッチしている

悪い例
public void CopyFile()
{
    try
    {
        File.Copy(".\a.txt", ".\b.txt");
    }
    catch (Exception ex)
    {
        // 例外時の処理
    }
}

 catch (Exception ex)はすべての例外をキャッチします。そのため本来はシステムを止めなければいけない例外が発生した場合でも、処理が続行されることとなり、リカバリが難しい状況におちいることがあります。

良い例1
public void CopyFile()
{
    try
    {
        File.Copy(".\a.txt", ".\b.txt");
    }
    catch (Exception ex)
    {
        // 例外時の処理
        thorw;
    }
}

 改善方法の一つとして、例外を再スローする方法があります。より上位のCatch句に例外処理をゆだねることで、エラーが出たまま処理が続行されることを防ぎます。

良い例2
public void CopyFile()
{
    try
    {
        File.Copy(".\a.txt", ".\b.txt");
    }
    catch (NotSupportedException ex)
    {
        // 例外時の処理
    }
}

 他の方法として、特定の例外しかキャッチしないという手があります。こうすることで、続行不能な例外は上位のCatch句で処理されます。

4. catch句およびfinally句で例外を上書きしている

悪い例
public void OpenNotepad()
{
    Process process;

    try
    {
        process = Process.Start("notepad.exe");
    }
    catch (Exception ex)
    {
        process.Kill(); // 例外が発生する可能性のあるメソッド
        throw;
    }
}

 例では例外が発生するメソッドがcatch句に書かれています。ここでprocess.Kill()において例外が発生した場合、try句で発生した例外が上書きされ、本来の例外発生原因が上位の例外処理部分にうまく伝わりません。
 基本的に例外が発生する可能性のあるメソッドはcatch句、finally句に記述してはいけません。

おわりに

 catch句は本当に必要な箇所以外には書かないようにしましょう。
 運用時に負担にならないよう気をつけましょう。

186
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
tokishirazu
客先常駐マン

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
186
Help us understand the problem. What is going on with this article?