平々毎々(アーカイブ)

はてなダイアリーのアーカイブです。

例外クラスの指針

ちゃんと論じようとすると長くなりそう。Javaの理論と実践: 例外をめぐる議論とか、とあるコンサルタントのつぶやき : .NETの例外処理 Part.1から始まる一連のポストとかを見てもわかることだけど。

「エラーなどの種類によって処理を分岐させる場合、種類に対応した例外クラスを作成するべき」

うーん。わかるけど難易度が高いから非現実的かも。

// フィールドに持たせた場合
try
{
  ... ;
}
catch (FooException ex)
{
  switch (ex.Reason)
  {
    case Hoge:
      ... ;
      break;

    case Fuga:
      ... ;
      break;

    default:
      ... ;
      // break or rethrow
  }
}

実際これで済む場合もあるわけで、これで済むなら何の問題もない。
エラー理由とその対処の粒度(or 関心事 or 責務)が同じならね。
ビジネスロジックのエラーだったらこんな感じでもいいんじゃ?と思う。

で、これですまない場合なんだけれども。

try
{
  ... ;
}
catch (HogeException ex)
{
  ... ;
}
// FugaExceptionは上位層に渡す。

こんなのとか*1

あとは

try
{
  ... ;
}
catch (HogeException ex)
{
  // ビジネスロジック的なエラーの対処
}
catch (FugaException ex)
{
  // バグとかシステムエラーとかの対処
}

とかね。

で、難しいのは、HogeExceptionとFugaExceptionがどっちもFooBarExceptionを継承しているとか、そんなis-a関係が本当に必要なのか?ってとこだと考える。
つまり、上の例みたいな区別を必要とする場面もあるが、どっちもまとめてcatch(FooBarException ex)で扱いたい場面もある、そんな例外を新たに設計する必要ってどこまである?

もちろん、必要があると信じて設計するんだけど、設計の正しさってよくわからない。

たとえばJavaではSocketException is a IOExceptionだけども、.NETではそうじゃない。これは.NETの例外設計が間違ってるのか?というとそんなこともない。
たとえばJavaではDriverManager.getConnection throws SQLExceptionだけど、DBへの接続文字列が間違ってた場合なんかはIOException派生の別例外でもいいんじゃないのかと思うこともある*2

……というわけで、俺は

「他の例外クラスを継承しただけの例外クラスを作らない」

をこのように解釈した。やっぱり長くなった。

*1:もしJavaの検査例外だったらメソッドのシグネチャに書くことになるが、.NETには検査例外が存在しない

*2:異論は認める