例外処理の使い方

仕事上ではC/C++を使っていて、エラー処理等は全て戻り値でやっているのですが、.NETだとクラスライブラリが例外を返してくるので、例外処理付き合わざるを得ません。私の例外処理に対する知識は10年以上前にC++の勉強で身につけたものでして「例外はあくまでも例外。通常は起こるはずが無いもの」となっています。しかし、「OSの異常」みたいなのだと話は簡単なのですが「業務上あり得ない」とかになってくると境界が曖昧になって悩みます。

それはさておき、メソッドが例外を返すとされている場合、

  1. 呼び出し側のミス(nullを渡すな等)
  2. メソッド内部で起こった異常

と、大ざっぱに分けられます。1.はバグなので出荷時には潰されているべきものでしょう。さて、2.については、呼び出し元の責務によって対応が変わってきます。

例外処理をどう扱えば良いか、イマイチよく分かっていないので、現在、自分が考えていることをつらつら書いてみます。念のため繰り返しますが、以下は、例外処理を使うためのアドバイスではなく、こんな感じで使って大丈夫ですか?って相談みたいなものです。(^^;
例としてFile.OpenReadを見てみます。このメソッドは、

  • ArgumentException
  • ArgumentNullException
  • PathTooLongException
  • DirectoryNotFoundException
  • FileNotFoundException
  • NotSupportedException

このような例外を返すとドキュメントには書いてあります。呼び出し元はこれら例外をキャッチして対応すれば良いのでしょうか?

static void Method1 (string path) {
  Stream s = null;
  try {
    s = File.OpenRead (path);
  }
  catch (ArgumentNullException ex) {
    throw new MyFileException ("パスがnullなんて、もってのほかだ!", ex);
  }
  catch (ArgumentException ex) {
    throw new MyFileException ("空のパスや使えない文字を含めるんじゃねー", ex);
  }
  catch (PathTooLongException ex) {
    throw new MyFileException ("パスがなげーよ", ex);
  }
  catch (NotSupportedException ex) {
    throw new NotSupportedException ("おいおい、どこのOSのパス形式よ?", ex);
  }
  catch (DirectoryNotFoundException ex) {
    throw new DirectoryNotFoundException ("そんなディレクトリはない", ex);
  }
  catch (FileNotFoundException ex) {
    throw new MyFileException ("ファイルがないなんてありえねー", ex);
  }
  finally {
    if (s != null) 
      s.Close ();
  }
}

一応、書いてあった例外全てに対応しました。しかし、ArgumentNullExceptionなんかは呼び出し元の不注意です。他にもパスが長すぎる云々など、呼び出すまでもなく失敗すると分かっているパターンがあります。その場合、全然"例外"ではなく、起こるべくして起きたと言えます。
では、FileNotFoundExceptionはどうでしょうか。呼び出し側でこの例外をキャッチした場合、その扱いは、

FILE* fp = fopen (path, "r");
if (fp == NULL) {
}

C言語で言うところのfopenがNULLを返したに当たるのでしょうか? 例外という意味を考えれば、fopenがNULLを返すことなんかは日常茶飯事。全然"例外"ではないです。どちらかというと、File.Existsでチェックすることが、fopen戻り値チェックに当たると思います。では、ファイル存在チェックしたにも関わらず、FileNotFoundExceptionが発生したとすると、どのように対応すれば良いのでしょうか?

static void Method2 (string path) {
  // ファイルがなければ、エラー処理など(今回は何もしない)
  if (!File.Exists (path))
    return;

  Stream s = null;  
  /*  存在チェックずみなので、以下の例外は発生しないはず(多分・・・)
    ArgumentException
    ArgumentNullException
    PathTooLongException
    DirectoryNotFoundException
    FileNotFoundException
    NotSupportedException   
  */

  // UnauthorizedAccessException はどうだっけ?

  // 出来る限り、例外が発生しないようにチェックはした。それでも発生したら対処できん
  try {
    s = File.OpenRead (path);
  }
  finally {
    if (s != null)
      s.Close ();
  }
}

何もしない。

UnauthorizedAccessExceptionの扱いは微妙ですが、その他の例外は発生しないようにチェックして呼び出しているので、もし、それでも例外が発生したらどうにもならないのでキャッチしなくて良いんじゃないかと思います。