Java異常処理についていくつかの提案があります。動力ノードJava学院の整理
第1条:異常な場合にのみ使用する
提案:異常は正常でない条件にのみ使用されるべきで、それらは永遠に正常な制御フローに使用されるべきではない。
下の二つのコードを比較して説明します。
コード1 異常機構の設計は正常ではない場合のために意図されているので、JVMの性能の最適化を試みることはめったにない。したがって、異常なオーバーヘッドを作成、投げ出し、捕獲することは非常に高価である。 コードをtry-catchに戻してJVMが本来実行する可能性のある特定の最適化を実現することを阻止しました。 配列を巡回する標準モードは冗長性の検査につながりません。いくつかの近代的なJVM実装はそれらを最適化します。 実際には,異常に基づくモードは標準モードよりもはるかに遅い。テストコードは以下の通りです。
第2条:回復可能条件の使用について検査された異常は、プログラムのエラーに対して運転時に異常となります。 運転時異常 -- RuntimeException類およびそのサブクラスを運転時異常と呼びます。 検査された異常 -- Exception類自体、およびExceptionのサブクラスでは、「運転時異常」以外のサブクラスは、検査された異常です。 その違いは、Javaコンパイラが「検査された異常」を検査しますが、「実行時異常」は検査しません。つまり、検査された異常については、throwsで声明を出したり、try-catchで捕獲処理を行ったりします。運行時の異常については、「throws声明によって投げられていない」としても、「try-catch文で捕獲されていない」としても、コンパイルされます。もちろん、Javaコンパイラは運行時の異常を検査しませんが、throwsを通じてこの異常を説明したり、try-catchを通して捕獲したりすることもできます。
ArthmeticException(例えば、除数が0)、IndexOutOfBounds Exception(例えば、配列オフライン)などはいずれも運転時異常です。この異常に対しては,コードを修正することによって発生を避けるべきである。検査された異常に対しては、処理によりプログラムを運転再開することができます。例えば、1人のユーザが十分な数の前を記憶していないので、彼は1つの有料電話で通話をしようとしたが失敗すると仮定する。そこで検査された一つの異常を持ち出した。
第3条:不必要な使用を避けるために検査された異常
「検査された異常」はJava言語の優れた特性です。リターンコードと違って、「検査された異常」は例外の条件をプログラマに強制的に処理させ、プログラムの信頼性を大幅に向上させます。
しかし、あまりにも使用しすぎると、異常が確認されてAPIが使用されにくくなります。1つの方法が検査された異常を投げ出した場合、この方法を起動するコードは、1つ以上のcatch文ブロックでこれらの異常を処理しなければならないか、またはthrows声明によってこれらの異常を投げ出さなければならない。catchで処理しても、throws声明で投げ出しても、プログラマに無視できない負担をかけました。
「検査された異常」に適用するには、二つの条件を同時に満たす必要があります。第一に、APIを正しく使用しても、異常条件の発生を阻止することはできません。第二に、一旦異常が発生したら、APIを使用するプログラマは、有用な動作をとってプログラムを処理することができる。
第4条:できるだけ標準を使用する異常
コードの再利用は提唱に値するものであり、これは共通の規則であり、異常も例外ではない。既存の異常を再利用するといくつかの利点があります。
第一に、APIはあなたの学習と使用を容易にします。これはプログラマがもともと熟知していた習慣の使い方と一致しています。
第二に、これらのAPIを使用するプログラムにとって、それらの可読性は、プログラマが知らない異常に満ちていないため、より良い。
第三に、異常クラスが少ないほど、メモリの占有率が小さいことを意味し、これらの種類を転載する時間の出費も小さい。
Javaプラットフォームライブラリはこれまでで最も多く使われてきた異常ですが、許可のもとで他の異常も再利用できます。例えば、複数または行列のような演算対象を実現するには、ArthmeticExceptionとNumberFormatExceptionを再利用することが適切である。異常があなたの要求を満たしているなら、迷わずに使ってもいいですが、異常な条件が文書に記載されている条件と一致することを確認してください。このような重用は名前のもとではなく意味のもとに作らなければなりません。
最後に、どの異常を重用するかを明確にしなければなりません。従わなければならない規則がありません。例えば、カードの対象の状況を考慮して、配札操作のための方法があると仮定して、そのパラメータは手札のカードの枚数である。このパラメータにおいて使用者が伝達する値は、サブプレート全体の残りの枚数よりも大きいと仮定する。このような状況は、IL Argement Exception(handSizeの値が大きすぎる)と解釈されても良いし、IIlegalStation Exceptionとして解釈されても良い(お客様の要求に対して、カードオブジェクトのカードが少なすぎる)。
第5条:投げる異常は、相応の抽象に適合すること
一つの方法で投げられた異常と、それが実行しているタスクとの明確な関係がないと、この状況はどうしたらいいのか分かりません。一つの方法が低い抽象から投げられた異常を伝えると、このようなことが起こります。このような情況が発生する時、人を困惑させるだけではなくて、その上も“汚染”の高層のAPI。
この問題を避けるために、高層の実現は低い層の異常を捕らえるべきで、同時に高い層の抽象によって紹介することができる異常を投げ出します。このやり方は「異常変換(exception translation)」と呼ばれています。
例えば、Javaにおける集合フレームワークAbstractSequentialListのget()方法は以下の通りである(JDK 1.7.0_に基づく)。40)
第6条:各方法の投げられた異常については、文書が必要である。
別個の声明が検査された異常は、Javadocの@throwsマークを利用して正確に記録されます。
クラスの多くの方法が同じ原因で同じ異常を投げた場合、このクラスの文書注釈にはこの異常を文書として作成し、各方法のために単独で文書を作成するのではなく、これは許容できる。
第7条:詳細メッセージに失敗--キャプチャメッセージを含める。
簡単に言えば、私たちが異常をカスタマイズしたり、異常を投げたりする時は、失敗に関する情報を含めるべきです。
一つのプログラムが捕獲されていない異常によって失敗した場合、システムは自動的にこの異常なスタック軌跡を印刷します。スタック軌跡にはこの異常な文字列表現が含まれています。典型的には、例外クラスの名前と、その後に続く詳細メッセージが含まれている。
第8条:失敗を原子性の維持に努める
オブジェクトが異常を投げた後、常にこのオブジェクトは定義された利用可能な状態に保たれることを期待しています。検査された異常に対しては、これは特に重要であり、調合者は通常検査された異常から回復することを期待するからである。
一般的には、失敗した方法の呼び出しは、オブジェクトを「呼び出し前の状態」に保つべきである。このような属性を持つ方法は「失敗原子性」と呼ばれる。失敗しても原子性を維持していると理解できる。オブジェクトを「失敗原子性」に保つ方法はいくつかあります。
(01)可変でないオブジェクトを設計する。
(02)可変オブジェクト上で動作する方法について、「失敗原子性」を得る最も一般的な方法は、動作を実行する前にパラメータの有効性をチェックすることである。以下のように(Stock.java中のpop方法):
(04)動作中に発生した失敗を説明するために、回復コードを作成し、動作開始前の状態にオブジェクトをロールバックさせる。
(05)オブジェクトの一部を一時的にコピーして操作し、操作が完了したら仮コピー中の結果を元のオブジェクトにコピーする。
「対象の失敗原子性を維持する」ことは望ましい目標であるが、常に可能ではない。例えば、複数のスレッドが適切な同期機構なしにオブジェクトに同時にアクセスしようとすると、オブジェクトは不一致の状態に留まる可能性がある。
「失敗原子性」が実現できる場合でも、それは常に期待されていません。いくつかの動作に対しては、オーバーヘッドまたは複雑性が著しく増加する。
全体的なルールは、方法規範の一部として、いずれの異常もオブジェクトを変えてこの方法を呼び出す前の状態を変えるべきではない。この規則が違反されている場合、APIドキュメントでは、オブジェクトがどのような状態にあるかを明確に示すべきである。
第9条:異常を無視しない
APIの設計者が一つの方法がある異常を投げ出すと宣言した時、彼らはいくつかのことを説明しようとしています。だから、無視しないでください。異常コードを無視すると以下のようになります。
提案:異常は正常でない条件にのみ使用されるべきで、それらは永遠に正常な制御フローに使用されるべきではない。
下の二つのコードを比較して説明します。
コード1
try {
int i=0;
while (true) {
arr[i]=0;
i++;
}
} catch (IndexOutOfBoundsException e) {
}
コード2
for (int i=0; i<arr.length; i++) {
arr[i]=0;
}
二つのコードの役割はアール配列を巡回し、配列の各要素の値を0に設定します。コード1は異常によって終了し、非常に分かりにくいように見えます。コード2は配列境界によって終了します。コード1の使用を避けるべきです。主な理由は3つあります。
public class Advice1 {
private static int[] arr = new int[]{1,2,3,4,5};
private static int SIZE = 10000;
public static void main(String[] args) {
long s1 = System.currentTimeMillis();
for (int i=0; i<SIZE; i++)
endByRange(arr);
long e1 = System.currentTimeMillis();
System.out.println("endByRange time:"+(e1-s1)+"ms" );
long s2 = System.currentTimeMillis();
for (int i=0; i<SIZE; i++)
endByException(arr);
long e2 = System.currentTimeMillis();
System.out.println("endByException time:"+(e2-s2)+"ms" );
}
// arr :
private static void endByException(int[] arr) {
try {
int i=0;
while (true) {
arr[i]=0;
i++;
//System.out.println("endByRange: arr["+i+"]="+arr[i]);
}
} catch (IndexOutOfBoundsException e) {
}
}
// arr :
private static void endByRange(int[] arr) {
for (int i=0; i<arr.length; i++) {
arr[i]=0;
//System.out.println("endByException: arr["+i+"]="+arr[i]);
}
}
}
実行結果:
endByRange time:8ms
endByException time:16ms
異常エルゴードによる速度は通常の方法よりかなり遅くなりました。第2条:回復可能条件の使用について検査された異常は、プログラムのエラーに対して運転時に異常となります。
ArthmeticException(例えば、除数が0)、IndexOutOfBounds Exception(例えば、配列オフライン)などはいずれも運転時異常です。この異常に対しては,コードを修正することによって発生を避けるべきである。検査された異常に対しては、処理によりプログラムを運転再開することができます。例えば、1人のユーザが十分な数の前を記憶していないので、彼は1つの有料電話で通話をしようとしたが失敗すると仮定する。そこで検査された一つの異常を持ち出した。
第3条:不必要な使用を避けるために検査された異常
「検査された異常」はJava言語の優れた特性です。リターンコードと違って、「検査された異常」は例外の条件をプログラマに強制的に処理させ、プログラムの信頼性を大幅に向上させます。
しかし、あまりにも使用しすぎると、異常が確認されてAPIが使用されにくくなります。1つの方法が検査された異常を投げ出した場合、この方法を起動するコードは、1つ以上のcatch文ブロックでこれらの異常を処理しなければならないか、またはthrows声明によってこれらの異常を投げ出さなければならない。catchで処理しても、throws声明で投げ出しても、プログラマに無視できない負担をかけました。
「検査された異常」に適用するには、二つの条件を同時に満たす必要があります。第一に、APIを正しく使用しても、異常条件の発生を阻止することはできません。第二に、一旦異常が発生したら、APIを使用するプログラマは、有用な動作をとってプログラムを処理することができる。
第4条:できるだけ標準を使用する異常
コードの再利用は提唱に値するものであり、これは共通の規則であり、異常も例外ではない。既存の異常を再利用するといくつかの利点があります。
第一に、APIはあなたの学習と使用を容易にします。これはプログラマがもともと熟知していた習慣の使い方と一致しています。
第二に、これらのAPIを使用するプログラムにとって、それらの可読性は、プログラマが知らない異常に満ちていないため、より良い。
第三に、異常クラスが少ないほど、メモリの占有率が小さいことを意味し、これらの種類を転載する時間の出費も小さい。
Javaプラットフォームライブラリはこれまでで最も多く使われてきた異常ですが、許可のもとで他の異常も再利用できます。例えば、複数または行列のような演算対象を実現するには、ArthmeticExceptionとNumberFormatExceptionを再利用することが適切である。異常があなたの要求を満たしているなら、迷わずに使ってもいいですが、異常な条件が文書に記載されている条件と一致することを確認してください。このような重用は名前のもとではなく意味のもとに作らなければなりません。
最後に、どの異常を重用するかを明確にしなければなりません。従わなければならない規則がありません。例えば、カードの対象の状況を考慮して、配札操作のための方法があると仮定して、そのパラメータは手札のカードの枚数である。このパラメータにおいて使用者が伝達する値は、サブプレート全体の残りの枚数よりも大きいと仮定する。このような状況は、IL Argement Exception(handSizeの値が大きすぎる)と解釈されても良いし、IIlegalStation Exceptionとして解釈されても良い(お客様の要求に対して、カードオブジェクトのカードが少なすぎる)。
第5条:投げる異常は、相応の抽象に適合すること
一つの方法で投げられた異常と、それが実行しているタスクとの明確な関係がないと、この状況はどうしたらいいのか分かりません。一つの方法が低い抽象から投げられた異常を伝えると、このようなことが起こります。このような情況が発生する時、人を困惑させるだけではなくて、その上も“汚染”の高層のAPI。
この問題を避けるために、高層の実現は低い層の異常を捕らえるべきで、同時に高い層の抽象によって紹介することができる異常を投げ出します。このやり方は「異常変換(exception translation)」と呼ばれています。
例えば、Javaにおける集合フレームワークAbstractSequentialListのget()方法は以下の通りである(JDK 1.7.0_に基づく)。40)
public E get(int index) {
try {
return listIterator(index).next();
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
listIterator(index)はListIteratorのオブジェクトに戻ります。このオブジェクトのnext()を呼び出す方法はNoSuchElemenntExceptionの異常を投げるかもしれません。get()の方法では、NoSuchElemenntExceptionを投げると異常に困惑します。したがって、get()はNoSuchElemenntExceptionを捕獲し、IndexOutOfBounds Exception異常を投げました。すなわち、NoSuchElement ExceptionをIndexOutOfBounds Exception異常に変換することに相当する。第6条:各方法の投げられた異常については、文書が必要である。
別個の声明が検査された異常は、Javadocの@throwsマークを利用して正確に記録されます。
クラスの多くの方法が同じ原因で同じ異常を投げた場合、このクラスの文書注釈にはこの異常を文書として作成し、各方法のために単独で文書を作成するのではなく、これは許容できる。
第7条:詳細メッセージに失敗--キャプチャメッセージを含める。
簡単に言えば、私たちが異常をカスタマイズしたり、異常を投げたりする時は、失敗に関する情報を含めるべきです。
一つのプログラムが捕獲されていない異常によって失敗した場合、システムは自動的にこの異常なスタック軌跡を印刷します。スタック軌跡にはこの異常な文字列表現が含まれています。典型的には、例外クラスの名前と、その後に続く詳細メッセージが含まれている。
第8条:失敗を原子性の維持に努める
オブジェクトが異常を投げた後、常にこのオブジェクトは定義された利用可能な状態に保たれることを期待しています。検査された異常に対しては、これは特に重要であり、調合者は通常検査された異常から回復することを期待するからである。
一般的には、失敗した方法の呼び出しは、オブジェクトを「呼び出し前の状態」に保つべきである。このような属性を持つ方法は「失敗原子性」と呼ばれる。失敗しても原子性を維持していると理解できる。オブジェクトを「失敗原子性」に保つ方法はいくつかあります。
(01)可変でないオブジェクトを設計する。
(02)可変オブジェクト上で動作する方法について、「失敗原子性」を得る最も一般的な方法は、動作を実行する前にパラメータの有効性をチェックすることである。以下のように(Stock.java中のpop方法):
public Object pop() {
if (size==0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null;
return result;
}
(03)前の方法と同様に、計算処理手順は、失敗する可能性のある任意の計算部分がオブジェクト状態が修正される前に発生するように順序を調整することができる。 (04)動作中に発生した失敗を説明するために、回復コードを作成し、動作開始前の状態にオブジェクトをロールバックさせる。
(05)オブジェクトの一部を一時的にコピーして操作し、操作が完了したら仮コピー中の結果を元のオブジェクトにコピーする。
「対象の失敗原子性を維持する」ことは望ましい目標であるが、常に可能ではない。例えば、複数のスレッドが適切な同期機構なしにオブジェクトに同時にアクセスしようとすると、オブジェクトは不一致の状態に留まる可能性がある。
「失敗原子性」が実現できる場合でも、それは常に期待されていません。いくつかの動作に対しては、オーバーヘッドまたは複雑性が著しく増加する。
全体的なルールは、方法規範の一部として、いずれの異常もオブジェクトを変えてこの方法を呼び出す前の状態を変えるべきではない。この規則が違反されている場合、APIドキュメントでは、オブジェクトがどのような状態にあるかを明確に示すべきである。
第9条:異常を無視しない
APIの設計者が一つの方法がある異常を投げ出すと宣言した時、彼らはいくつかのことを説明しようとしています。だから、無視しないでください。異常コードを無視すると以下のようになります。
try {
...
} catch (SomeException e) {
}
空のcatchブロックは、あるべき目的に異常をきたすことができません。異常な目的は、異常な条件を強制的に処理することです。異常を無視すると、火災信号を無視するようになります。もし火災信号をOFFにしたら、本当に火災が発生した時、火災信号を見る人がいなくなります。したがって、少なくともcatchブロックは、なぜこの異常を無視したのかを説明するための説明を含むべきである。