#blog2navi() *例外処理とアサート処理 [#u3b1f7e0] 先に言い訳を書いておきますが、今回の記事はエラー・例外処理に対する私なりの考え方です。~ 「世間一般でこう定まっている」というわけではありませんので、鵜呑みにだけはしないでください。 さて、C++やC#を始めとするオブジェクト指向言語には、大抵は「例外処理」が実装されています。~ 例外処理というのは、まぁ要するにtry-catch構文のことです(言語によってはtry-catch-finally)。 #code(c){{ // C++ #include <new> // …中略… try { // C++ではnewに失敗するとstd::bad_alloc例外が送出される data = new SomeData(); } catch (const std::bad_alloc& ex) { std::cout << ex.what() << std::endl; } // …後略… }} #code(csharp){{ // C# using System; // …中略… try { // C#ではnewに失敗するとOutOfMemoryException例外が送出される data = new SomeData(); } catch (OutOfMemoryException ex) { Console.WriteLine(ex.Message); } finally { // 例外の有無に関わらず絶対に行わせたい処理を書く // 例: 開いたファイルのクローズ } }} 一方、C++やC#などでは、アサート処理も提供されています。~ C++ではassertマクロ、C#ではSystem.Diagnostics.DebugクラスのAssert静的メソッドです。~ これらはデバッグ用にコンパイルされた場合のみ有効になります。 #code(c){{ // C++ #include <cassert> // …中略… // 引数がfalse値だとエラーメッセージを出力してアボートする assert(data != NULL); // NULLチェック }} #code(csharp){{ // C# using System.Diagnostics; // …中略… // 引数がfalse値だとエラーメッセージを出力する // デバッガで実行した場合はブレークポイントと同様に一時停止する Debug.Assert(data != null); // nullチェック }} じゃあどっちを使えばいいんだという話ですが、基本は次の通りです。 -アサート処理は開発者のデバッグ用であり、ユーザに配布するプログラムには含まれない。 -例外処理は開発者のデバッグ用でもあるが、ユーザに配布するプログラムにも含まれる。 じゃあ全て例外処理で書けばいい…というわけではありません。~ 例えば、次の二つの例を考えてみましょう。 +newでメモリを確保できなかった。 +プログラム内で閉じている自作関数の引数に不正な値(nullなど)が渡された。 一つ目の例は、どんなに頑張っても発生数をゼロにすることはできません。~ たとえ開発者の環境では起こらなくても、ユーザがロースペックなマシンを使っていたり大量のプログラムを同時に走らせたりしていれば起こる可能性があります。~ こういった類のエラーはまさに『例外』であり、例外処理でフォローすべきものといえます。~ ユーザにメモリが確保できなかった旨のメッセージを表示し、可能な限り状態を保存して終了する…といった処理が妥当でしょうか。 さて一方、二つ目の例はどうでしょうか。~ 自作関数を使うのは開発者自身ですから、その引数に不正値が渡されるのは開発者のコーディングミス…即ち『バグ』に他なりません。~ これに対して例外処理を適用し、ユーザに「○○関数に不正値が渡された」というメッセージを出したとしても、ユーザは困惑するしかないでしょう。 二つ目の例のような「デバッグで完全に除去できるもの」は単なる『バグ』であり、『例外』ではありません。~ これに対しては関数の先頭などにアサート処理を設け、デバッグを行うことでバグを除去すべきでしょう。~ 今時のデバッガならばアサート時にコールスタック(要するに関数呼び出し履歴)を参照できるので、デバッグには便利です。~ 更に、ユーザ向けのリリースビルド時にはアサート処理は「無いもの」として扱われるため、わざわざコメントアウトしたりする必要もありません。 例外処理は便利なので、ついついそこら中にtry-catchを付けたくなる衝動に駆られることもあります。~ ですが、「例外処理はユーザにも見える」ということを常に考え、状況に応じて適切に使うべきだと言えるでしょう。 …ただ、C#やJavaで使えるfinallyは別です。~ try-finallyは後処理の必要な場面では例外安全のためにどんどん使うべきだと思います。~ 特にC#やJavaでは、いつ例外が送出されるかわかりませんから…。 **補足 [#acb8c3d4] 上述の二つ目の例で「プログラム内で閉じている自作関数」とわざわざ書いたのは、ライブラリ等ではまた事情が異なるためです。 一般的に、ライブラリ化する必要のある関数を使うのは自分だけではありません。~ それが開発者向けに配布するようなライブラリであれば尚更です。~ こういった場合、引数に不正値が渡されるのは十分にありうることです。 そこで例外処理を使うべきかアサート処理を使うべきか…ですが、これはライブラリのポリシーに依ると思います。~ 例えばソースコードごと公開しているようなライブラリならアサート処理でもいいと思いますが、リリースビルド版しか配布しないようなライブラリではそもそもアサート処理が使えません。~ もっとも、引数に不正値が渡された程度のことであれば、関数の戻り値としてfalseなりエラーコードなりを返すという選択肢もありますが(むしろこれが最も一般的か…)。 C++の場合はもうちょっと複雑で、リリースビルド版しか配布しない場合、逆に例外を送出するのは避けた方が良いかもしれません。~ というのも、例えば「Visual StudioでビルドされたライブラリをBorland C++ Builderで使う」といったことが往々にしてあるわけですが、C++ではコンパイラによって例外処理の機構が異なっているため、ライブラリから送出された例外をキャッチできない可能性が高いのです((昔見聞きした記憶に頼って書いているため、現在は事情が異なるかもしれませんが。))。~ C++では例外処理はモジュール(ライブラリ、プログラム)内で閉じている方が無難でしょう。 RIGHT:Category: [[[プログラミング>ぼやきごと/カテゴリ/プログラミング]]][[[C++>ぼやきごと/カテゴリ/C++]]][[[C#>ぼやきごと/カテゴリ/C#]]] - 2009-03-08 09:07:35 ---- RIGHT:&blog2trackback(); #comment(above) #blog2navi()