例外設計における大罪
t_wada さんの素晴らしいスライドに基づいて、手元にある実際の書籍も読みつつ自分なりに情報をまとめなおしてみる。
例外設計における大罪には以下がある。
無視
隠蔽
乱用
過剰防御
大罪
無視
エラーを放置した場合の問題
不安定なコードができる : 大きな問題を抱えていて、さらにはそれが修正しにくい
セキュリティ上問題のあるコードができる : クラッカーにエラー処理の不備を利用される
貧弱な構造とインターフェース : 利用側のエラーハンドリングが複雑なコードはインタフェースが悪い可能性がある。発生し得るエラーを明示し、重大なものでない場合は楽に復帰できるようにしたい
エラーを放置する言い訳
コードの見通しが悪くなるから...
納期が迫っているから...
エラーを絶対に返さない関数もあるから (自分もそのような関数を作っても良いだろう) ...
遊びで書いたプログラムだから...
93 エラーを無視するな Pete Goodliffe
Kevlin Henney (2010-12). プログラマが知るべき97のこと. 243p.
隠蔽
try-catch ブロックをコードベースに大量に入れれば、「例外が発生しても絶対に止まらない」というアプリケーションを作ることが可能なはずです。ただ、これは、もう死んでいる人の体を釘か何かで固定し、無理やり立った状態にしているようなものですが...。
27 死ぬはずのプログラムを無理に生かしておいてはいけない Verity Stob
Kevlin Henney (2010-12). プログラマが知るべき97のこと. 243p.
乱用
例えば、配列を回す時に以下のようなコードが書かれている場合がある。配列に対してループを回し続け、インデックスの範囲外にアクセスしようとすると例外が発生するので、発生したらそれを握りつぶして、配列のループの完了としている。
code:例外の乱用.java
try {
int i = 0;
while(true)
} catch(ArrayIndexOutOfBoundsException e) {
}
これは本来、以下のようにかける。
code:java
for (Mountain m : range)
m.climb()
前者のようなコードを書く人の主張には、以下のようなものがある。
JVM は配列へのアクセスを全て検索s流ので、ループ終了検査は冗長であり避けるべき
これには以下のような誤りがある。
例外は例外的状況で使用されるように設計されているのであり、明示的な検査と同等に速くするという動機はない
try-catch ブロック内にコードを書くと、書かれていない場合に行われるある種の最適化が排除される
配列のループが冗長な検査をするとは限らず、最新の JVM 実装ではそのような検査は最適化され取り除かれる
実際、例外に基づくイデオムは最新の JVM 実装ではかなり遅くなるとのこと。
教訓:
例外は、例外的状況を表現するのに利用するものであり、通常の制御フローで使用すべきでない
パフォーマンスについては、常にそれの改善を目指しているプラットフォーム実装により、いつか利点がなくなるかもしれない
にも関わらず、過度に凝ったイデオムによるバグや保守の難しさの問題は残る
うまく設計された API は通常の制御フローに例外を使用することをクライアントに矯正しない
事前条件を満たしていなければ呼び出せない API は、「状態検査」メソッドを持つべき
例えば、Iterator クラスは次の状態を返す next と、状態検査メソッドである hasNext を持っている。これは、for-each ループでも内部的に利用されている
状態検査メソッドの代わりに、null 等の区別できる値を返すようにする設計も考えられる。オブジェクトが外部要因で状態遷移する場合は、この方法の方が望ましい。なぜなら、状態検査メソッドを利用してしまうと、状態検査から API の利用までの間に状態が変化してしまう恐れがあるためである。
第9章 例外
ジョシュアブロック (2014-02-26). Effective Java. 325p.
過剰防御
code:java
package javaja;
public class UserRepository {
public User create(String name, int age) {
if (name == null) {
throw new NullPointerException("name is null");
}
if (age < 0) {
throw new IllegalArgumentException("age is negative");
}
if (name.isEmpty()) {
throw new IllegalArgumentException("name is empty");
}
return new User(name, age);
}
}
契約による設計 (Design by Contract)
過剰防御への対策として、契約による設計 が1つの答えだと言われている。 あるルーチンそれ自体と、その呼び出し側があったときに、どちらにどのような責務があるか考えて設計を行う。
契約による設計 では、少なくとも、ルーチンそれ自体に、事前条件が満たされているかどうかをチェックする責務は存在せず、事前条件のチェックをルーチン内でまで行ってしまうのは過剰防御である。 過剰防御の悪い点は、システムが冗長になることであり、冗長性のある検証はシステムに損傷を与える。システム全体が シンプルに保たれていることが重要 であり、複雑さは品質の敵 である。