#例外を無視する
例外の取り扱いについて考えてみる。
通常、共通処理で想定外例外をキャッチするような仕組みを作るので、プログラマが意識する場面は少ない。一方、意識しなければならない場面での取り扱いに問題が発生することが多い。
これまで見てきた例を挙げてみる。
- 例外をキャッチした時点で握りつぶす。
- 本来チェックしなければならない部分を例外キャッチで省略する。
- 例外インスタンスをスローする。
##サンプルコード
// パターン1,2
public Dto GetDto()
{
try
{
// ファイルから読み込んだ情報をDtoに変換する。
// (コードは省略)
return new Dto();
}
catch (Exception e)
{
// ファイルフォーマットエラーしか発生しないので、
// エラーログを呼び出し元で出力する。
return null;
}
}
// パターン3
public void Update()
{
try
{
// テーブルのレコード更新
// (コードは省略)
}
catch (Exception e)
{
// ロールバック。
// (コードは省略)
throw e;
}
}
public class Dto
{
}
##問題点
例外発生時に何よりも知りたいのは例外のスタックトレースである。その上で、各パターンについて考える。
パターン1では、スタックトレースが取得できないので、論外。例外キャッチをして何もしないのであれば、キャッチするべきではない。
パターン2の場合、スタックトレースが取得できないだけでなく、戻り値がnullの場合はフォーマットエラーであるという暗黙の了解が発生する。その上、本当にフォーマットエラーかは不明である。フォーマットエラーが想定できるのであれば、フォーマットチェック処理を作成するべきである。
パターン3はキャッチすることの是非はともかく、スタックトレースの情報が書き換わってしまうのが問題である。throw;とすれば元の情報をそのままスロー可能。
##リファクタリング後
// パターン1
public Dto GetDto1()
{
// ファイルから読み込んだ情報をDtoに変換する。
// (コードは省略)
// 想定外例外は外側の共通例外処理でキャッチされる。
return new Dto();
}
// パターン2
public ReadResult GetDto2()
{
// ファイルのフォーマットチェック
if (HasFormatError())
{
return new ReadResult()
{
HasFormatError = true,
Dto = null
};
}
else
{
// ファイルから読み込んだ情報をDtoに変換する。
// (コードは省略)
// 想定外例外は外側の共通例外処理でキャッチされる。
return new ReadResult()
{
HasFormatError = false,
Dto = new Dto()
};
}
}
// パターン3
public void Update()
{
try
{
// テーブルのレコード更新
// (コードは省略)
}
catch (Exception e)
{
// ロールバック。
// (コードは省略)
// 再スローする場合、eを書いてはいけない。
throw;
}
}
private bool HasFormatError()
{
// ファイルフォーマットチェック
// コードは省略
return true;
}
// ファイル読み込み結果
public class ReadResult
{
public bool HasFormatError;
public Dto Dto { get; set; }
}
public class Dto
{
}
##まとめ
例外を握りつぶしてはいけないし、手を抜くための手段にしてもいけない。あえてキャッチする場合、再スローの方法はよく考えること。
尚、例外キャッチは、パフォーマンス低下につながることも心にとどめておきたい。
参考