#はじめに
運用者はシステムでエラーが発生した際に、プログラムから出力されたログを見て原因の特定作業を行います。このとき重要となるのが例外処理です。適切な例外処理が行われていれば、原因の特定が容易になります。
ここでは、こんなことをされたら運用者が困るという例を見ていきます。使用している言語は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)はすべての例外をキャッチします。そのため本来はシステムを止めなければいけない例外が発生した場合でも、処理が続行されることとなり、リカバリが難しい状況におちいることがあります。
public void CopyFile()
{
try
{
File.Copy(".\a.txt", ".\b.txt");
}
catch (Exception ex)
{
// 例外時の処理
thorw;
}
}
改善方法の一つとして、例外を再スローする方法があります。より上位のCatch句に例外処理をゆだねることで、エラーが出たまま処理が続行されることを防ぎます。
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句は本当に必要な箇所以外には書かないようにしましょう。
運用時に負担にならないよう気をつけましょう。