先に言い訳を書いておきますが、今回の記事はエラー・例外処理に対する私なりの考え方です。
「世間一般でこう定まっている」というわけではありませんので、鵜呑みにだけはしないでください。
さて、C++やC#を始めとするオブジェクト指向言語には、大抵は「例外処理」が実装されています。
例外処理というのは、まぁ要するにtry-catch構文のことです(言語によってはtry-catch-finally)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | - ! - ! - - ! ! - | ! - - | ! |
|
一方、C++やC#などでは、アサート処理も提供されています。
C++ではassertマクロ、C#ではSystem.Diagnostics.DebugクラスのAssert静的メソッドです。
これらはデバッグ用にコンパイルされた場合のみ有効になります。
じゃあどっちを使えばいいんだという話ですが、基本は次の通りです。
じゃあ全て例外処理で書けばいい…というわけではありません。
例えば、次の二つの例を考えてみましょう。
一つ目の例は、どんなに頑張っても発生数をゼロにすることはできません。
たとえ開発者の環境では起こらなくても、ユーザがロースペックなマシンを使っていたり大量のプログラムを同時に走らせたりしていれば起こる可能性があります。
こういった類のエラーはまさに『例外』であり、例外処理でフォローすべきものといえます。
ユーザにメモリが確保できなかった旨のメッセージを表示し、可能な限り状態を保存して終了する…といった処理が妥当でしょうか。
さて一方、二つ目の例はどうでしょうか。
自作関数を使うのは開発者自身ですから、その引数に不正値が渡されるのは開発者のコーディングミス…即ち『バグ』に他なりません。
これに対して例外処理を適用し、ユーザに「○○関数に不正値が渡された」というメッセージを出したとしても、ユーザは困惑するしかないでしょう。
二つ目の例のような「デバッグで完全に除去できるもの」は単なる『バグ』であり、『例外』ではありません。
これに対しては関数の先頭などにアサート処理を設け、デバッグを行うことでバグを除去すべきでしょう。
今時のデバッガならばアサート時にコールスタック(要するに関数呼び出し履歴)を参照できるので、デバッグには便利です。
更に、ユーザ向けのリリースビルド時にはアサート処理は「無いもの」として扱われるため、わざわざコメントアウトしたりする必要もありません。
例外処理は便利なので、ついついそこら中にtry-catchを付けたくなる衝動に駆られることもあります。
ですが、「例外処理はユーザにも見える」ということを常に考え、状況に応じて適切に使うべきだと言えるでしょう。
…ただ、C#やJavaで使えるfinallyは別です。
try-finallyは後処理の必要な場面では例外安全のためにどんどん使うべきだと思います。
特にC#やJavaでは、いつ例外が送出されるかわかりませんから…。
上述の二つ目の例で「プログラム内で閉じている自作関数」とわざわざ書いたのは、ライブラリ等ではまた事情が異なるためです。
一般的に、ライブラリ化する必要のある関数を使うのは自分だけではありません。
それが開発者向けに配布するようなライブラリであれば尚更です。
こういった場合、引数に不正値が渡されるのは十分にありうることです。
そこで例外処理を使うべきかアサート処理を使うべきか…ですが、これはライブラリのポリシーに依ると思います。
例えばソースコードごと公開しているようなライブラリならアサート処理でもいいと思いますが、リリースビルド版しか配布しないようなライブラリではそもそもアサート処理が使えません。
もっとも、引数に不正値が渡された程度のことであれば、関数の戻り値としてfalseなりエラーコードなりを返すという選択肢もありますが(むしろこれが最も一般的か…)。
C++の場合はもうちょっと複雑で、リリースビルド版しか配布しない場合、逆に例外を送出するのは避けた方が良いかもしれません。
というのも、例えば「Visual StudioでビルドされたライブラリをBorland C++ Builderで使う」といったことが往々にしてあるわけですが、C++ではコンパイラによって例外処理の機構が異なっているため、ライブラリから送出された例外をキャッチできない可能性が高いのです*1。
C++では例外処理はモジュール(ライブラリ、プログラム)内で閉じている方が無難でしょう。