D 1.0   D 2.0
About Japanese Translation

Last update Mon Nov 27 18:15:54 2006

D でのエラー処理

I came, I coded, I crashed. -- Julius C'ster

どんなプログラムでも、エラーを扱う必要があります。エラーとは、 プログラムの通常動作の一環ではない、期待されない状態のことです。 よくあるエラー状態の例としては:

エラー処理にまつわる問題

伝統的なCのエラー検知/報告の方法は、伝統でも何でもなくて、 関数ごとに異なる場当たり的なものです。以下のようなパターンがあります: 可能性のあるエラーに対処するためには、関数呼び出しそれぞれに対して 面倒なエラー処理コードを追加しなければなりません。エラーが発生した場合は、 エラーから復帰し、何らかのユーザーフレンドリーな方法でユーザにそれを 通知しなければなりません。エラーがその場で処理できなければ、 もっと上位のルーチンへそのことを伝搬しなければなりません。 長い長いerrno値のリストは、適切なテキストに変換して表示する必要があります。 このような作業をするコード全てを書くことは、プロジェクトのなかで多くの時間を 費やしてしまいます。さらに、もし仮に新しいerrnoの値がランタイムシステムに 追加されたら、 古いコードは意味のあるエラーメッセージを表示できなくなります。

きちんとエラー処理をしたコードは、それでなければ綺麗でまとまった実装を、 雑然と散らかったものに変えてしまう傾向があります。

もっと悪いことに、よくエラー処理をしたコードというのはそれ自体がエラーを生みやすく、 プロジェクト中で一番テストされていない(それゆえバグの多い)部分となるため、 単純に省略されてしまいがちなのです。こうなると結局、何か予想されないエラーを プログラムが扱いきれず、"死の青画面" に突入してしまうはめになります。

ささっと適当なコードで済ましてしまうような小プログラムには、 ややこしいエラー処理を書くコストを払うだけ価値がなく、そのようなユーティリティは、 刃ガードのついていないノコギリのような存在になっていまいます。

エラー処理に必要な哲学と方法論は、次の通りです:

D でのエラー処理の解決策

まず、エラーについてよく考えて、幾つかの仮定を置きましょう: 解決策は、エラーの報告には例外処理機構を用いることです。 全てのエラーは 仮想クラスErrorから派生したオブジェクトになります。Errorクラスは純粋仮想関数 toString を持ち、エラーを人間に読める形で説明した char[] を返します。

コードが "メモリ不足" のようなエラーを検知したとしましょう。すると、 "メモリ不足" というメッセージを持った Errorオブジェクトが投げられます。 関数呼び出しスタックは、Errorのハンドラが見つかるまで巻き戻されます。 スタック巻き戻しの途中に finally ブロックが実行されます。Errorのハンドラが見つかると、 そこから実行が再開されます。最後まで見つからなければ、 デフォルトのエラーハンドラがよびだされ、メッセージを表示してプログラムを終了します。

これは、先ほどあげた基準にあっているでしょうか?

標準化されている - 一貫して使用することで、より使いやすくなる
ここに上げた方法がD流で、 Dのランタイムライブラリや例で一貫して使われています。
プログラマがエラーチェックをさぼったとしても、 ある程度意味のある結果を出力する
エラーに対するcatchハンドラが存在しなければ、 プログラムはデフォルトのエラーハンドラによってキチンと終了し、 適切なエラーメッセージを出力します。
古いコードを新しいコードを組み合わせるときに、新しいエラーを、 古いコードを書き換えずとも扱えるようにする
古いコードは全てのエラーをcatchするよう書くことも、一部をcatchして 残りは上位関数へ伝えることもできます。どの場合でも、 エラー番号と文字列を相関させる必要はなく、 常に正しいメッセージが与えられます。
不注意でエラーが無視されることをなくす
Error例外は、何とかして処理されます。 エラーを示すためにNULLポインタを返して、その次の行で NULL ポインタを使ってしまう、というようなことはなくなります。
'ささっと適当な' ユーティリティでも正しくエラー処理がなされるようにする
Quick and dirty コードでは、 一切エラー処理を書いたりエラーチェックをしたり する必要はありません。全てデフォルトで、エラーが起きれば 適切なメッセージを表示してからプログラムはきちんと終了します。
エラー処理のコードを見やすく書ける
try/catch/finally 文は、延々と続く if (error) goto errorhandler; 文より ずっと見やすいです。
エラーについて設けた仮定とつきあわせてみるとどうでしょう?
エラーはプログラムの通常の実行フローにはない。エラーは 例外的で、期待されないものである。
D の例外処理はぴったりこれに当てはまります。
エラーは通常状態ではないので、エラー処理のコードは さほどパフォーマンスを重視しなくてよい。
例外処理のスタック巻き戻しは、比較的遅いプロセスです。
逆に通常の実行ロジック部分のパフォーマンスが重要である。
通常の実行フローでは全ての関数のエラーチェックをする必要はなくなるので、 エラー処理に対して例外を使うことで、 現実に高速化します。
全てのエラーは何らかの方法で処理される。 明示的に書かれたエラー処理コードか、 あるいはシステムのデフォルト処理か。
もし特定のエラーに対してハンドラが存在しなければ、ランタイムライブラリの デフォルトのハンドラで処理されます。仮にエラーが無視されるとすれば、 それはプログラマが明示的にエラーを無視するコードを書いたからで、 それは意図的な処理であると考えられます。
エラー発生部分のコードの方が、エラーから復帰する部分のコードよりも エラーについてよく知っている。
もはや、エラーコードを人間に読める形式に変換する必要はありません。 正しい文字列は、エラー対処コードではなく、エラー検知コードから生成されます。 これはまた、同じエラーに対しては異なるアプリケーション間で一貫性のある メッセージが表示されることにつながります。
例外をエラー処理に用いることで、別の問題 -- いかに例外安全な プログラムを書くか -- が持ち上がってきます。この問題についてはこちらで。