エラー処理


※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。

サイト名:
URL:


エラー処理 ログ出力の考え方

  • どこでエラーを出力するべきか?
1つは、ある関数でエラーが発生した時、エラーの出力もその関数の中で行ってしまう、という方針が考えられます。ログの出力は、たいていはこの方針で行われます。

もう1つは、発生したエラーの情報を呼び出し側に渡して、エラーの出力は呼び出し側で行う、という方針も考えられます。エラーメッセージの出力は、たいていはこの方針で行われます。

  • エラーが発生した箇所で、エラーの出力も行う場合の問題点
汎用性が無くなる
ログを出力する仕組みは、アプリケーションごとに異なります。
あるアプリケーションの中から、汎用的なソフトウェア部品を取り出し、他のアプリケーションに流用したいこともあります。しかし、2つのアプリケーションで、異なるログ出力の仕組みを使っている場合は、簡単には流用できなくなることがあります。

文脈が分からない
同じ状況が発生しても、呼び出された時の文脈によって、正常であったり、エラーであったりすることがあります。また、警告レベルのエラーに過ぎないこともあれば、致命的なエラーであることもあります。
呼び出される関数の側では、上位側からどのような文脈で呼び出されているのかを、知ることができません。そのため、エラーが発生しても、その重要性の判断ができません。
これは、汎用的なモジュールやクラスを作っている時に、特に問題となります。
実際のソフトウェア開発では、汎用的なモジュールであっても、呼び出される文脈が特定されていることがあります。その場合は、その文脈でしか呼び出されない、という前提で、エラー処理を実装してしまうことがあります。
例えば、XMLファイルを読み込み、引数で指定されたタグの値を読み取るモジュールを作ったとしましょう。指定されたタグが存在しない場合、これをエラーとするかどうかは、文脈に依存します。
しかし、このモジュールを使うアプリケーションでは、必ず存在するタグしか指定しない、と分かっていたとしましょう。この時、モジュールの中で、指定されたタグが存在しない場合はエラーを出力する、という処理を記述してしまうことがあります。
しかし、このように呼び出される文脈を意識した作りは、正しい設計ではありません。また、汎用性も失われてしまいます。

状況に応じた制御ができない
ある関数が繰り返し呼び出される時は、エラーの出力を抑制したくなります。数百個もの同じようなエラーメッセージを出力するよりは、「数百個のエラーがあります」というエラーメッセージを1回だけ出力する方が良いでしょう。
しかし、呼び出される関数では、数百回も繰り返し呼び出されている、ということは分かりません。

出力の仕方は一通りではない
エラーの出力を行う方法は、必ずしも一通りではありません。
エラーの書式は、アプリケーションごとに異なるかもしれません。また、アプリケーションによっては、複数の出力先にそれぞれエラーを出力することもあるかもしれません。
エラーが発生した箇所でエラーの出力も行う方針では、このようなバリエーションには対応できません。

  • エラーの情報を返し、上位側でエラーの出力を行う場合の問題点
下位が上位を意識してしまう
上位側でどのような出力を行うのかは、エラーが起きた下位のモジュールやクラスでは、本来は意識すべきではありません。
しかし、実際には、下位側のエラー情報の返し方を見ると、上位側でのエラー出力の仕方を強く意識していることが良くあります。
例えば、C言語では、エラー情報を返す際に、エラーコードという番号を返すことが多くあります。これは、何故でしょうか。
たいていの場合、呼び出し側で出力するエラーメッセージでは、エラーの概要だけを示し、詳細な値などは出力しません。そのため、エラーの内容を表すエラーコードだけで充分なのです。言い換えると、上位側で出力するエラーメッセージには、値などは出力しない、と分かっているからこそ、下位側の関数は、エラーコードだけを返すように決められる訳です。

情報が隠蔽されない
上位側でどのような出力も行えるようにするには、エラーに関するあらゆる情報を渡すことになります。
しかし、設計の良し悪しで言えば、下位側の詳細な情報は、上位側には隠されているべきです。
もし、ログの出力を上位側に委譲するとなると、クラスのprivate変数や、関数内で宣言された自動変数などまで、上位側に見せなくてはいけないかもしれません。
気が付いたら、すべての変数がpublicになっていた、ということにもなりかねません。

ポリモーフィズムに対応できない
オブジェクト指向言語では、さらに難しい問題が発生します。
クラスの継承やインターフェースの実装を行い、親クラスで定義されているメソッドを子クラスでオーバーライドします。この時、このメソッドから呼び出し側に渡せる情報は、親クラスで宣言された例外に限定されます。
親クラスでは、どのような子クラスが存在するかを知りません。メソッドが投げる例外には、親クラスのフィールドの情報をすべて含めることはできます。しかし、子クラスで独自に定義されるフィールドの情報は、含めることはできません。
子クラスでは、親クラスで宣言された例外を継承し、子クラス独自のフィールドの情報も、例外に詰め込むことはできます。
しかし、上位側、即ち、このメソッドを呼び出す側では、親クラスしか意識していません。投げられる例外も、親クラスで宣言された型で扱います。そのため、上位側では、子クラス独自のフィールドの情報は、得ることができません。

  • 正しいエラー処理のやり方とは?
実際のアプリケーションの開発では、エラーの判定やエラー処理だけを、独立したクラスに分離する、ということも良くあります。エラー処理やログ出力を行う専門のクラス群を作る訳です。この場合、これらのクラス群と、ソフトウェア本来の処理を行うクラスとの結合をできるだけ疎にすることが理想的です。
これがうまくいけば、ソフトウェア本来の処理と、エラー処理やログ出力を、見事に切り離すことができたように見えることでしょう。アスペクト指向プログラミングが目指した理想の形は、エラー処理やログ出力まで含めたソフトウェア全体の設計をうまくデザインすることでこそ、実現できるのではないか、と思います。

例外処理との正しい付き合い方


JavaDoc を見ると、メソッドでスローされる可能性のある例外が分かります。

  • 例外の種類
  1. チェックすべき例外
try ... catch を使用して例外処理を行うことが義務付けられています。try ... catch を行っていないプログラムを javac でコンパイルをすると、「例外 XXX は報告されません」とコンパイルエラーになります。

  1. 実行時例外
細心の注意を払ってコーディングされていれば、通常は発生することはありません。

  1. エラー
何らかの理由で、Java VM が回復不能状態になったときに発生します。このため、try ... catch で例外処理を行うことはできません

  • 例外処理の方法 種類
  1. 例外をキャッチして、その場で例外処理を行い、呼び出されたメソッドには例外を伝えない。
  2. 例外をキャッチして、新たな例外を生成して呼び出したメソッドに再スロー。
  3. 例外をキャッチせずに、そのまま呼び出したメソッドに伝える。もしくはキャッチした例外を、そのまま呼び出したメソッドに再スローする。

スタックトレース

[[]]