オブジェクト指向と型システムの狭間で例外を考える

 「検査例外はアジャイルやオブジェクト指向の考えに反するという事実」について一部誤解あり - じゅんいち☆かとうの技術日誌のあんまりな釣りタイトルにやれやれだぜ、と思いつつも非チェック例外多用作戦のトレードオフ認識 - 都元ダイスケ IT-PRESSでツッコミたかったことが突っ込まれてしまってるので、しょうがないのでオブジェクト指向と型システムの話でもしよう。

Javaの静的型システム ≠ オブジェクト指向

 僕が10年ほど前、Javaを使い始めてからしばらくたってやっとオブジェクト指向プログラミングが掴めて楽しくなってきた頃合、これこそがオブジェクト指向なのだと誤解をしていたころ、オブジェクト指向は型がチェックできてなんぼだと思ってた。

 javascriptのプロトタイプ型のオブジェクト指向に憤り、「あんなものはオブジェクト指向ではない」などと思うのがJavaプログラマ中二病というやつだが、Smalltalkのメッセージングによるオブジェクト指向を知って、「あれ?じゃぁこのJavaオブジェクト指向って奴はいったい…?」とオブジェクト指向ゲシュタルト崩壊するわけだね。

 実は崇め奉っていたものはオブジェクト指向ではなく、Javaの型システムだということを知り非常な衝撃を受けるのだ。間違っていたのは自分だったのか orz

 Javaのチェック例外、あるいは非チェック例外の是非を考えるに当たって、それは主にJavaの型システムの問題であって、オブジェクト指向の問題ではないという衝撃もまたなかなかの破壊力かもしれない。

原始的な例外処理機構を作る

 通常、オブジェクト指向ではオブジェクトに対してメッセージを送り、応答を得るという構造になる。メッセージ、というのはJavaの場合はつまるところメソッド呼び出しのこと。例えばtoString()を呼び出せばオブジェクトの文字列表現が戻ってくるわけ。

 ところが、処理中になんらかの異常事態が発生して、文字列表現じゃない何かを返さざるをえない状況ってのに直面したとする。するとString型の戻り値じゃそんなの表現出来ないから困る。

 さてここで、原始的に解決する場合のコードを考えてみよう。

public static class Result {
    /** 戻り値 */
    public Object value;
    /** 成否フラグ */
    public boolean success;
}

といった型をつくり、すべてのメソッド呼び出しの戻り値をこの型にしてしまう。文字列型を戻り値にするから、失敗時の返却値に困るのだ。そうした状態をもった型で戻り値をラップしてやればいい。

 ここでは成否フラグだけを持たせたけども、必要に応じて失敗時の情報をより詳細にわかるように情報を追加することもできる。

面倒くさい

 で、こんなことを毎回書いてたら面倒くさくてかなわないので、言語仕様でそのへんはうまくやってくれって話になる。

 僕らはラップされたResultオブジェクトなんて普段は意識しないでString型を返す、とプログラム中で宣言すればいい。ランタイムなりコンパイラなりが内部でごにょごにょと成否フラグを持たせた呼び出しに変換してくれればハッピーだよね。

例外を静的型付けで自動精査してみる

 ともかく例外を表現することはそれでできるけど、静的型付けに慣れ親しんでる人なら、例外の種類も型システムでチェックできるようにしたら機械的にどんな例外が出るのかチェックできて嬉しいんじゃないのって考えが浮かんでくる。

 つまり、Javaのチェック例外だ。

 メソッドにthrows宣言でどんな例外を出すかマークしておく。そうしたら例外の処理漏れを機械的にチェックできて嬉しい。きっとこれでバラ色のプログラミングライフがおくれるぜ!って考えたんだろう。

面倒くさい

 オブジェクトの型を設計するのが面倒くさい、あるいは上手く設計出来ないから静的型付けが嫌いって人がいるけど、同じように例外の型を設計するのが面倒くさい、あるいは上手く設計出来ないから例外の静的型付けが嫌いって人も出てくる。

 確かにJavaのチェック例外は例外を型システムで管理する機構を提供したよ。でも、それを活かすために随分といろいろな行動規範に縛られなくちゃいけない。やってられるか!俺は全部、非チェック例外にしちまうぜ!そういう一派が生まれてくるのも仕方ないと思うんだ。

 だからといってC#みたいにみんな規範を守らないからチェック例外イラネってのはどうかと思うのだけどね。*1こうなると選択の余地はなくて、派閥も生まれようがない。それがいいかどうかは別の問題。

で、オブジェクト指向ってどこに行ったんだっけ?

 例外処理ってのは別にオブジェクト指向パラダイムというわけではない。構造化プログラミングが可能な言語であれば自然に織り込めるパラダイムだ。あんまり構造化プログラミングできてなかったMSX-BASICにだって一応の例外処理機構はあったしね。*2

 Javaの例外処理では、例外をオブジェクト指向的なクラスで表現した。そしてクラスの継承階層から代入互換性を保証するそのJavaの型システムを利用した。catch節で型システムによる代入階層が絡んでくるのはJavaの型システム的である。

 チェック例外の問題というのは、例外をJavaの型システムに対応付けて自動検出を試みたところ、どうもプログラマからしたばあいのユーザビリティが悪いケースというのがある、という話なんだ。

  • 検査例外という概念そのものが良くない
  • Javaの検査例外の仕様、つまり検査例外の特定の実装がマズい
Javaの検査例外の欠点について - kmizuの日記

はよく留意しておくべきで、Javaのチェック例外のユーザビリティが悪い、という事例を持って「検査例外という概念そのものが良くない」を語ることはできまい。

 もちろん、Javaのチェック例外の実装だとこのようなケースで困る、という指摘は改善を考える上で重要なのでどんどんすればいいと思うし、このような言語設計ならば問題が解決できるんじゃないか、という提案もどんどんすればいいと思う。

*1:The Trouble with Checked Exceptions - プログラミング言語を作る日記

*2:Javaのそれとは似ても似つかないから同列に語るべきではないと思うけど