TL; DR
前提
- エラーが回復不可能な場合、つまり、エラーがコード内にあり、外部状態に依存しない場合(つまり、回復によりコードが修正される場合)、ランタイム例外をスローする必要があります。
- チェックされた例外は、コードが正しいが、外部状態が期待どおりではない場合にスローされる必要があります。ネットワーク接続がない、ファイルが見つからない、または破損しているなどです。
結論
伝播コードまたはインターフェイスコードで、基礎となる実装が外部状態に依存していると想定している場合、明らかに依存していない場合、チェック例外をランタイム例外として再スローすることがあります。
このセクションでは、いずれかの例外をスローする必要がある場合のトピックについて説明します。結論のより詳細な説明を読みたい場合は、次の水平バーにスキップできます。
ランタイム例外をスローするのはいつが適切ですか?コードが正しくないことが明らかで、コードを変更することで回復が適切である場合、ランタイム例外をスローします。
たとえば、次の場合はランタイム例外をスローするのが適切です。
float nan = 1/0;
これにより、ゼロ除算ランタイム例外がスローされます。コードに欠陥があるため、これは適切です。
または、たとえば、HashMap
のコンストラクタの一部を次に示します。
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// more irrelevant code...
}
初期容量または負荷係数を修正するには、コードを編集して正しい値が渡されるようにするのが適切です。これは、遠く離れたサーバーが稼働していること、ディスクの現在の状態、ファイル、または別のプログラム。無効な引数で呼び出されたコンストラクターは、無効なパラメーターまたはエラーを見逃した不適切なフローを引き起こした間違った計算であっても、呼び出し元のコードの正確さに依存します。
いつチェック例外をスローするのが適切ですか?コードを変更せずに問題を回復できる場合、チェック済み例外をスローします。別の言い方をすれば、コードが正しい状態でエラーが状態に関連している場合は、チェック済み例外をスローします。
ここで、「回復」という言葉は扱いにくいかもしれません。これは、目標を達成する別の方法を見つけることを意味する場合があります。たとえば、サーバーが応答しない場合は、次のサーバーを試す必要があります。あなたのケースでそのような回復が可能な場合、それは素晴らしいですが、回復が意味するのはそれだけではありません-回復は単に何が起こったのかを説明するエラーダイアログをユーザーに表示するか、それがサーバーアプリケーションである場合管理者にメールを送信するか、エラーを適切かつ簡潔に記録するだけです。
mrmugglesの回答で言及された例を見てみましょう。
public void dataAccessCode(){
try{
..some code that throws SQLException
}catch(SQLException ex){
throw new RuntimeException(ex);
}
}
これは、チェック済み例外を処理する正しい方法ではありません。このメソッドのスコープで例外を処理するだけの能力がないからといって、アプリがクラッシュする必要があるという意味ではありません。代わりに、次のように上位のスコープに伝達するのが適切です。
public Data dataAccessCode() throws SQLException {
// some code that communicates with the database
}
これにより、呼び出し元による回復の可能性が考慮されます。
public void loadDataAndShowUi() {
try {
Data data = dataAccessCode();
showUiForData(data);
} catch(SQLException e) {
// Recover by showing an error alert dialog
showCantLoadDataErrorDialog();
}
}
チェック例外は静的分析ツールであり、実装を学習したり試行錯誤プロセスを実行したりすることなく、特定の呼び出しで何がうまくいかないかをプログラマーに明らかにします。これにより、エラーフローのどの部分も無視されないことを簡単に確認できます。実行時例外としてチェック済み例外を再スローすると、この省力化された静的分析機能に対して機能します。
また、呼び出し層は、上で示したように、より大きなスキームのより良いコンテキストを持っていることに言及する価値があります。dataAccessCode
呼び出される原因は多くありますが、呼び出しの特定の理由は呼び出し元にのみ表示されます。したがって、障害が発生した場合の正しい回復時に、より適切な判断を下すことができます。
この区別が明確になったので、チェック例外をランタイム例外として再スローしてもよいかどうかを推測することに進むことができます。
上記を考えると、いつチェック例外をRuntimeExceptionとして再スローするのが適切でしょうか?使用しているコードが外部状態に依存していると仮定した場合、外部状態に依存していないことを明確に主張できます。
以下を考慮してください。
StringReader sr = new StringReader("{\"test\":\"test\"}");
try {
doesSomethingWithReader(sr); // calls #read, so propagates IOException
} catch (IOException e) {
throw new IllegalStateException(e);
}
この例ではIOException
、APIはReader
外部状態にアクセスするように設計されているため、コードは伝播していますが、StringReader
実装は外部状態にアクセスしないことがわかっています。このスコープでは、呼び出しに関係する部分がIOや他の外部状態にアクセスしないことを確かに主張できますが、実装を知らない同僚を驚かせることなく、例外をランタイム例外として安全に再スローできます(おそらくIOアクセスコードがIOException
)をスローすると仮定します。
外部状態依存の例外を厳密にチェックし続ける理由は、それらが非決定的であるためです(コードのバージョンごとに予測可能に再現されるロジック依存の例外とは異なります)。たとえば、0で除算しようとすると、常に例外が発生します。0で除算しないと、例外が発生することはありません。また、例外が発生することはないため、例外を処理する必要はありません。ただし、ファイルにアクセスする場合、一度成功しても次に成功するとは限りません。ユーザーがアクセス許可を変更したり、別のプロセスがそのファイルを削除または変更した可能性があります。そのため、例外的なケースを常に処理する必要があります。そうしないと、バグが発生する可能性があります。