元ネタは、C#チームのEric Lippert氏のブログの数年前の投稿です。
例外処理というと、「とりあえずキャッチしとけ」とか、逆に「とりあえずキャッチするな(集約例外ハンドラーで対処しろ)」とか言われたりします。かと思えば、「プロなんだから自分の頭で考えろ」とか、「業務フロー次第でしょ」などと千尋の谷に突き落とされたりもします。
極論を言えばそうなのですが、もう少しガイドラインとなるものは無いのか、ということでEricのブログの内容をかいつまんで紹介します。
Ericは、例外は4種類に分類できると言っています[1]。
種類 | 説明 | 特徴 | 対処方法 | 例 |
---|---|---|---|---|
致命的な例外(fatal exception) | プロセスに深刻な問題が発生し、今にも死にそうな状態にある場合に発生する例外。 | プログラマーの過失ではない[2]、 復旧は無理(finallyを実行することで悪化する場合も) | 何もしない、場合によっては Environment.FailFast | OutOfMemoryException、StackOverflowException、ExecutionEngineExceptionなど |
うっかり例外(boneheaded exception) | プログラマーのうっかりミスにより発生する例外。 | プログラマーの過失、 本来、テスト・デバッグの段階ですべて洗い出すべきもの | 何もしない(正確に言えばバグとして対処する) | ArgumentException系(後述の頭の痛い例外の場合を除く)、NullReferenceException、IndexOutOfRangeException、InvalidCastException、DevideZeroException など。 |
頭の痛い例外(vexing exception) | 失敗する可能性を前提とすべきAPIが、失敗時にスローしてくる例外。 | APIの設計ミス |
| (厄介なメソッドの例)Parse、XmlConvert.VerifyXXX、TypeConverter.ConvertXXX [4] など。 |
外因性の例外(exogenous exception) | プログラムの外部の環境に原因のある例外。 | 避けようがないが、対処しようがある場合もある。 | 適宜対処する[5]。 | IOException、SocketException、WebException、ExternalException(コードによって例外の種別が異なるので注意、ADO.NET の例外を含む)、UnauthorizedAccessException、SecurityException など。 |
つまり、処理しなければいけない例外というのは、本来は外因性の例外なわけです。
Windowsなど最近の環境はマルチタスク環境なので、アプリケーションとは別のプロセスがなんらかの処理をしています。ユーザーがうっかりファイルを操作すること だってあり得ます。なので、直前のif文で存在をチェックしたファイルが、その直後にFileStreamを開くまでの間に削除された、なんてことは十分あり得ます。また、ファイルがロックされているかどうかは実際に開いてみるまでわかりませんし(そもそも チェックするAPIがないのですが、あったとしても、if文でチェックした後に他のプロセスがロックする可能性は残ります)、アクセス権も別のプロセス(Explorerとか)が変更するかもしれません。別のマシンにあるプログラムと通信している場合は、障害を起こす可能性のあるものが途中に多数介在しますので、基本的にエラーが起こるものとして考えないといけません。
何かお役にたてば。
[1] InterruptionException、CancellationExceptionなど、この4つに入らないものもあります。
[2] CLRやJITの実装の隙間を探るようなコードを書くような方については、もちろんその人の過失。
[3] ただし、IDictionary<TKey,TValue>のインデクサが例外をスローするのは、値の型が値型の場合(たとえばInt32)に、値として0が格納されているのか、要素がなかったからdefault(Int32)(= 0)を返したのかを区別できないので、設計ミスではないと思います。
参照型とNullable<T>をいっしょくたにしたジェネリック型制約がかけられないジェネリックの設計ミスとも言えなくもありませんが、IDictionary<string, int?>としなければならないといったルールが追加され、APIの敷居が上がってしまうように思います。
[4] CanConvertXXXメソッドもあるのですが、おそらくは同じであろうロジックを2回呼び出すというのは設計として微妙だと思っています。WPFのIValueConverterは値を変換できない場合にDependencyProperty.UnsetValueを返すというコントラクトでいい感じですね。
[5] 結局千尋の谷に突き落としていますが、ここだけを考えれば良いということでどうかご容赦を。