例外推論 前編

そんなことじゃいけないので、なんか書こう。
型推論と同じ要領で例外も推論すればいいんじゃないかという話。


僕は、まともに使える例外の機構はJavaしかないだろうと思っているのがある。
Dのアレとか、Eiffelのアレとか、その他スクリプト言語のアレとか、全て、実際にはあんまり例外処理の助けにはならんだろう、と。
Adaはなんとかなってればよいと思ったけど(Adaを勉強してる理由の10%くらいは、それを確かめたかったからだ)、Adaのは普通であった(つまり、あまり助けにはならない)
でも、Javaのはめんどい。そこで、Javaの例外のようなものに、例外推論を付ければいいだろう、とそういう話。


さて、では、まず最初に言いたいことは、例外処理において一番重要なことは、「例外が起こった場合の動作が仕様として決まっているか?」という部分だという話だ。


ちょっと前に、failmallocというものが流行った(流行ったっていうのがアレだなー)
http://enbug.tdiary.net/20060714.html
mallocのチェック抜けてるかどうか調べられるというのだ。


けど、今、目の前にあるコードにfailmallocを導入したとして、何かの役に立つだろうか?

char *p = malloc( sizeof(struct xxx) );
if ( p == NULL ) { /* お、ここのチェック抜けとった。教えてくれてありがとう > failmalloc!! */
	/* で…ここには何を書けば…? */
}

(まず、日本語のコメントを書くのはCの規格外だというのは覚えておかないといけない…のはいいとして)


そう、mallocをチェックしたとして、それが失敗だった場合にどうすればいいか、が、決まらないのである!
このことは、もはや言語の機能がどうとか、そういうのはまっったく関係無い。アセンブリで書いた場合も、Haskellで書いた場合も同じ問題にぶち当たるのである。goto、throw、catch、fail、signal、error、raiseとかの違いは、仕様が決まってる/決まってないの違いに比べたら、ごくごくごくごく些細な問題である。

まず、一番、重要なことは、エラーが起こった場合に、どうするか、という仕様を決めることだ。例外処理は、unwindが起こるから難しいのではない。仕様が決まってないから、難しいのである。


でー、ここまでは、プログラミング言語とは、関係無いはなし。
続いて、プログラミング言語にどういう機能があれば、この問題を解決するときの助けになるかについて考える。

まず、プログラミング言語は、仕様を決定する場合の助けにはなってくれない。けど、仕様が矛盾してないかどうかをチェックする助けをすることは可能だ…って、なんか大げさだな。


そういう大きな話がしたいんじゃないんだ…例外に話を絞ると、仕様を作ったときには気付かなかった、予期せぬところで起こる例外が無いかどうかチェックしたい、というのはあると思う。


まず、例として、以下の仕様があったとする。

  1. ファイルを開いて
  2. 設定値を読んで設定値を反映
  3. ファイルを閉じる

これに、起こるだろう例外を付け加えると、

  1. ファイルを開いて → 開けなかった場合は、何もしない
  2. 設定値を読んで設定値を反映 → ファイルフォーマットが違ってた場合、何もしない
  3. ファイルを閉じる → 閉じるときのエラーは無視

こんな感じ。だけど、この場合、「設定値が範囲外だった場合どうするの?」という仕様が抜けている。
実際にプログラムしていった場合、以下のような感じになるはずだ。

try {
	fp = open();
	if ( fp == failed ) throw open_failed;

	read_config( fp );

        if ( close(fp) == failed ) throw close_failed;
} catch ( open_failed ) {
	/* なにもしない */
} catch( close_failed ) {
	/* なにもしない */
} catch( format_error ) {
        /* read_config はファイルフォーマットが違うと、何もしない */
	close( fp );
}

ここまでは、問題無く、で、read_configを書いてると、

split {
	... /* なんか分割処理 */

	if ( failed ) {
		throw format_error;
	}

	return fields;
}
read_config( fp )
{
	String fields[] = split( /:/ );

	if ( fields[0].to_int > MAX_VALUE ) {
		/* えーと…? */
	}
}

ここで、仕様が決まってなかったことが明かになる。(参考)
そんで、

	if ( fields[0].to_int > MAX_VALUE ) {
		/* TODO: あとで考える */
		throw bouds_error;
	}

こーやって、そのまま明日になれば、はい、キャッチされない例外のできあがり!である。


C++その他諸々の例外システムは、この問題について一切助けを出してくれない。Javaは、Javaの例外だけは、この問題を救ってくれる。

read_config ( fp ) throws format_error {
	if ( fields[0].to_int > MAX_VALUE ) {
		throw bounds_error;
	}
}

これだと、bounds_errorがキャッチされないとのコンパイルエラーが出る

read_config ( fp ) throws format_error, bounds_error {

こうすると、もういっこ上のレベルで同じエラーが出る。
まあ、もちろん、これだって、中身が空のcatchを書いてしまえば終わりなんだけど…それは、「仕様が決まってない例外をcatchするコードを書いてはいけない」とかいうコーディング規約を作れば、対処できるだろう。(C++のほうは、コーディング規約では対応できない)コーディング規約を守ろうという良心さえあれば、なんとかなるはずだ…(と、思ったけど、それだったら、C++のほうだって、/* TODO: の箇所はコンパイルエラーになるようにする。とかいうコーディングルール作れば対応できてしまうような…)


と、いうわけで、Javaの例外システムは正しいと思うという話にしたかったんだけど、なんか無理だな…まあいいか。無理矢理続く。