型検査

次のような JavaC# の interface 宣言があるとします.

// Java
public interface IMyFunction
{
    int myFucntion( int value1 ) throws Bounds;
}
// C#
public interface IMyFunction
{
    int myFucntion( int value1 ); //【重要】10000 より大きいときは ArgumentException を発生させる!【例外】
}

興味深いのは,Java における検査例外リストはメソッドシグネチャの一部であることです.Java では戻り値が型検査を受けるのと同様に,検査例外についても型チェックが行われます.この意味では Java では「A, B, C,...,X のいずれかの型の要素を返す関数」と「A 型の要素を返す関数」の区別が存在します.すなわち,「戻り値の型を動的に変更することは可能ですか?」という質問がもし Java に対してなされたのであれば,「(検査例外を用いて) メソッドシグネチャを変更する」はある意味自然な回答です (「動的」という単語に善意からの拡大解釈を行うことになりますが).C# でこのような複数の戻り値型を用いる方法としては,文法的な択一の強制はできませんが,ref/out を用いるか,(id:ladybug:20050505:p2) で述べられているように明示的に新たな戻り値の型を作成*1することになります.
このように Java 屋さんが言うところの「例外を使えば良い」は,メソッドシグネチャ,ひいてはインターフェイス仕様の変更を意味する可能性があることには注意しておく必要があるでしょう.一方 C# では実装クラスで例外を発生させるるようにしてもインターフェイス仕様には何ら変化が無く,ref/out による参照渡し引数の追加または戻り値型の F1Result への変更こそが Java と同様“きちんと”インターフェイス仕様を変化させる方法です.
他方,厳密な型検査によるプログラムの強固さは『コンパイル時にエラーを検出する(id:ladybug:20050512:p1)』でも述べられているように多くのプログラマによって支持されていますし,私もその一人です.しかしこれはインターフェイス仕様の互換性が「Java で言うところの例外の追加」に対してひどく fragile であることも意味します.

不安定なメソッド・シグニチャーの問題は先の問題に関連しています。単純にメソッドを通して例外を渡していると、メソッドの実装を変える度にそのメソッド・シグニチャーを変える必要があるだけでなく、そのメソッドを呼ぶ全てのコードも変える必要があります。一旦クラスが実稼働状態に展開されてしまうと、繊細なメソッド・シグニチャーを管理するのは高くつくものになります。ところがこの問題は基本的に、Blochの43項のヒントに従わないことから起きる別の症状なのです。メソッドは失敗があった時には例外を投げるべきですが、その例外が反映すべきなのはそのメソッドが何をするかであって、どのようにするか、ではないのです。

実装変更によるメソッド・シグニチャーへの例外の追加削除にプログラマーが疲れて、対象のレイヤーが投げる例外タイプの定義に抽象化を使わず、単純に全てのメソッドがExceptionを投げるように宣言してしまうことが時々あります。別の言い方をすれば、例外はあまりにも面倒すぎると結論し、例外という電源スイッチを切ってしまうのです。当然ですがこの手法は一般的に、どうでも良いようなコード以外では良いエラー処理とは言えません。

*1:複数の型の合成という意味では akiramei さんが紹介されている Nemerle の variant (id:akiramei:20050323:p2) なども興味深いですね