例外処理の使い方

Since: May./04th/2005

ここでは、例外処理の注意すべき事項についてまとめておきます。

例外処理の注意点

必ず実行しなければならない処理は finally に記述する

例外処理で最も重要なのが try-catch 構文finally 節です。finallyは、例外が発生してもしなくても、JVMが生きている限り、必ず実行されます。リソースの解放のような、必ず実行されなければならない処理は、finally内部に記述します。さもないと、例外発生時にリソースが解放されないなどの状況(リソースリーク)が発生し、システム全体のハングアップ/異常終了に至りかねません。

具体的な例外をスロー/キャッチする

例えば、全ての例外をException型でキャッチすると、実装できる例外からの回復処理が限られたものに制限されてしまいます。また、実際にスローされた例外的状況を把握することを難しくします。

例外処理を、正常な処理の分岐のために使ってはならない

例外処理も iffor と同様に制御構造の一つです。メソッドから抜ける場合に、明示的な戻り値をセットしないで済む裏口として使えますが、コードの可読性が落ちる、スタックトレースの生成処理は一般に重い、スタックトレース自体が巨大などのデメリットがあります。正常な処理の分岐の一つとして例外処理を使うことはパフォーマンス上の誤りです。

コーディングエラーには例外処理は使わない

開発中は、コーディングエラーを発見するためのプリント文や例外処理を書くことが多いのですが、それらが本番に持ち込まれることは好ましくありません。テストコードは、テストモジュールに含める、ロガーのデバッグモードで制御する、アサーションで実装するなどの方法で、本番リリースするコードからは排除します。本番リリースしたプログラムでの例外処理の本来の役割は、例外的状態からの回復処理を実装することです。

チェック例外と非チェック例外を明示的に選択する

メソッドの throws リストに挙げられたチェック例外は、メソッドの入出力情報の一部です。プロジェクトの初期の設計段階において、チェック例外と非チェック例外の何れを使うのか決めておくべきです。明確な指針はありませんが、チェック例外であっても、新規に生成した例外オブジェクトにラップすることで任意の例外型に変更できます。

元々の例外情報は、例外チェインを用いて新規生成した例外オブジェクトに含めます。

低レベルな例外オブジェクトをそのままスローしない

コア・パッケージでは、色々なメソッドが色々な例外オブジェクトをスローします。例えば、チェック例外である SQLException, IOException, FileNotFoundExeption, SocketException などをそのまま投げると、実装の変更によってスローされる例外が変わってしまいます。実装の変更による修正範囲を該当クラス内に押さえ込むためには、捕捉して処理内容に関する例外でラップして仮想化します。

元々の例外情報は、例外チェインを用いて新規生成した例外オブジェクトに含めます。

チェック例外と非チェック例外の選択方針

例外処理しない
全てのメソッドに throws Exception を指定することで、もう例外処理に悩むことはなくなります。全ての例外は、チェック例外/非チェック例外の区別無く、コールスタックを伝播します。適当なおまとめクラス(ファサードクラスなど)でログに吐けば終わりです。ただし、これは例外処理の仕組みを放棄することになるので、システム全体で採用するには勇気が要ります。
全てチェック例外 (checked exception) とする
チェック例外は、呼び出し元で捕捉するか、さらに上位に投げるかしなければならないので、コード中に明文化することを強制できます。但し、例外処理の不要なネストや、メソッドの内容からは不適切な実装レベルの例外がスローされて、呼び出し元が混乱することがあります。また、補足しても異常終了するほかない場合は、捕捉のコードは全くの無駄です。
全て非チェック例外 (unchecked exception) とする
非チェック例外は、例外処理が任意なので、コンパイルエラーが減り、呼び出し元のコードが状況に応じて判断できます。但し、JavaDocなどの仕様書類に明文化されず、発生しても誰にも気付かれない恐れがあります。結果的に関係するクラスの責任者の属人的能力に頼ることになります。
チェック例外にするものと非チェック例外にするものについて取り決める


Copyright © 2005 SUGAI, Manabu. All Rights Reserved.
SEO [PR] !uO z[y[WJ Cu