コーディング スタイルの話題の一つに、関数はエラーを返すべきか、例外を返すべきか、というテーマがあります。普段は Perl、PHP ばかり、最近は C++ も疎遠なので、例外自体ほとんど使っていないんですが。帰りの電車の中でちょこっと考えてみたので、そのメモ。
情報の流れる方向、境界線
関数呼び出しを行った際に、その関数内部で何か問題が発生したときに、それはエラー値を返すのか、例外を投げるのか、といった議論です。これでは少し曖昧なので、考えるポイントを明確にします。私が注目したのは、情報の流れる方向と、関数との入出力を行う境界線、という視点です。
- 入出力の境界(大抵は関数呼び出し)では、入出力に関する約束があります。どんな入力を受け取って、どんな出力を返すのか、という取り決めです。
- 情報が境界を越える前に、情報が約束を守っているかを検査し、約束が守られていない場合に発生するのが「エラー」。例えば、ユーザが入力した値の平方根を求めるような処理の場合、平方根には負数は認められません。そこで、関数に負数が渡らないように事前に検査を行うのは、呼び出し側(境界を越える前)の責任であって、不正な値を渡そうとしてしまうのは、関数呼び出し側のミスだからです。
- 情報が境界を越えた後で、約束通りの情報が受け渡されたのかを検査し、約束が守られていない場合に発生するのが「例外」。平方根を求める関数に負数を渡してはいけないという約束があった時、それを破って負数が渡されてしまった時には、関数側ではどうしようもありませんし、それは呼ばれた関数側のミスでもありません。ですので、平方根を求める関数は、エラーを返すのではなく、例外を投げるべき。
- 出力の段においても、同様に考えることができます。情報が境界を越える前(関数の呼び出し元に処理結果を返す前)に、処理結果が約束を守っているかを検査し、約束が守られていない場合に発生するのが「エラー」。例えば、整数値を返すという約束の関数の場合、処理結果が正しく整数であるかを検査します。もし、整数以外の値を返そうとしているのであれば、それは関数側のミスです。
- 情報が境界を越えた後で、約束通りの結果が帰ってきたのかを検査し、結果が約束を守っていない場合に発生するのが「例外」。信じて仕事をお願いしたのに、約束通りの仕事をしてくれなかったわけですから、呼び出し元としてはもうどうしようもありません。
- 事前に約束を破らないようにチェックするのが「エラー処理」
- 本当に約束が破られていないのかチェックするのが「例外処理」
- エラーも例外もリカバリする処理を書く必要があるけれど、例外の方が割りと深刻な感じ
- 契約プログラミングでも似たような考え方がある
こんな感じか?
int main () {
:
if (! (0 <= x))
error (); // 負数を渡さないようにするのは呼び出し元の責任
// regard as a function block
{
if (! (0 <= x))
exception(); // 負数は約束違反
// Requesting route of x into y
if (! (y*y == x))
error(); // 正しい結果を求められていないのは関数側の責任
}
if (! (y*y == x))
exception(); // 関数がちゃんと仕事をしてくれなかった
:
}
ご意見コメントやトラックバックは大歓迎です。