オブジェクト指向超入門〜第10回〜
現在のプログラミング言語における2大メジャーであるJAVAとC#の2つを例に、例外に対するポリシーを考えてみようと思います。文法を表面的に見ただけではちょっとした違いにしか思えないかもしれませんが、そのちょっとした違いの中に、それぞれの言語の設計者の思想の違いが見て取れます。
まずはJAVAから。
JAVAでは例外クラス(ここではthrowで投げる事の出来るクラスという意味とします)の最上位にいるのはThrowableというクラスであり、これを継承したのがExceptionとErrorの2つ。Exceptionをさらに継承したのがRuntimeExceptionとその他のクラス、さらにさらにRuntimeExceptionの派生クラスとしていくつかの例外がJDKに定義されています。この継承ツリーを図示すると以下のようになります。
Trowable
|
+Error
| |
| +ホニャララError
| ・
| ・
|
+Exception
|
+RuntimeException
| |
| +ナンチャラException
| ・
| ・
+Runtime以外のその他大勢Exception
・
・
JAVAで例外をthrowする関数を書いた場合、その関数宣言でthrows 〜Exceptionと書かないといけないという文法になっています。これは「オレは〜Exceptionを投げるぜ」と宣言する必要があるという意味です。なぜそんな事を宣言しないといけないかというと、関数を呼び出す側に対して、その例外をキャッチする事を強制させる必要があり、その為にはどんな例外をキャッチしなきゃいけないかを呼び出し側に教えてあげなきゃならないからです。
一方呼び出す側ではその例外をキャッチするか、またはもう1つの選択肢としてキャッチせずに放ったらかすという事もできます。ただし放ったらかす場合は自身の関数宣言でも同じようにthrows 〜Exceptionを書かないとコンパイラに怒られます。これは当たり前の話で、A→B→Cという呼び出し階層があったとして、Cが例外を投げたときにBがそれを無視すればその例外はAにそのまま投げられる訳で、その場合にAから見れば「Bが〜Exceptionを投げてきた」ように見えるのだから、BはAに対して「オレは〜Exceptionを投げまっせ」とあらかじめ断っておく責任がありますよね。
これは非常に理にかなった文法で、これこそが例外の存在理由だと前回の記事でも書いているとおりなのですが、実は上記のツリー構造でErrorとRuntimeExceptionの下にぶら下がっている例外クラスは「キャッチしなくていいしthrows宣言もいらない」という文法になっています。平たく言うと「何の断りもなく突然想定外の例外が投げられるかもしれないよ」という事です。ErrorとRuntimeExceptionだけが何故そんな特別扱いなんでしょうか。
これは例外の発生原因によってクラス階層を分けているからです。
Error配下のクラスは主にハードウェアや実行環境の不具合が原因で起こる事態で発生します。例えばVMのヒープが枯渇してしまいクラスをこれ以上生成できなかったので、newしてる箇所でOutOfMemoryErrorが投げられる、といったケースです。
RuntimeExceptionは主にプログラムのバグが原因で想定外の(本来起こってはいけないはずの)不具合が発生した場合に投げられます。おそらく一番有名なのは、インスタンスがまだ未生成のクラス型変数を介してメンバをいじろうとした場合に起きるNullPointerですね。また値がゼロの変数で割り算をしようとした時に投げられるArithmeticException(いわゆるゼロ割り)などもプログラマなら一度は見た事があるんじゃないでしょうか。
これら2つに共通して言えるのは、コーディング時にあらかじめ想定しておけない、まさに「例外的な」事象が起こった場合に発生するものだという事です。前者のErrorはハードウェアや特定の実行環境の都合で起こるものであり、プログラムを書いている時点では、どんな環境でどんな不都合が起こるかは事前に予想が付きません。それ故にプログラム内のどこでどんな不具合の可能性を考慮してあらかじめエラー処理を実装しておけばよいのかという判断が出来ません。
もう1つのRuntimeExceptionの方はというと、本来起こってはいけないはずのエラーです。null経由でメンバを参照しようとしたりゼロで割り算しようとしたりするのは、そんなプログラムを書いたプログラマの責任です。要するにただのバグであり、そのプログラムを書いた本人はバグだと思わずに書いている訳ですからエラーの可能性など考えていないので、当然エラー処理をあらかじめ実装しておく事などありません。
このように考えるとErrorやRuntimeExceptionは事前に予想できないのだからthrows宣言なんて書けるはずもなく、従ってキャッチもできない、という話になるのです。じゃあ実際にそれが起こってしまった場合どうなるかというと、誰もキャッチしていないので、もちろんプログラムが落ちるんですが、これはむしろ落ちてくれた方がいいんです。落ちてくれればその事実に気付いて原因を探り、バグなのであればプログラムを修正するなり、環境が原因であれば設定を見直すなどの対処がすぐにできます。それなのにExceptionをまとめてキャッチしているためにこれら事象に気付かず、エラーが発生したという事実をもみ消して無かった事にしてしまっても、潜在的な不具合に目をつぶって無駄な延命措置をしているだけに過ぎません。病気と一緒で早期発見早期治療が大事なのであって、見て見ぬふりで問題を先送りしていい事など何も無いんです。