以前は真剣なJavaコーディングはしていませんでしたが、自分の既存のスキル(DelphiとC#)に基づいて構文、ライブラリ、および概念を学びました。理解できないことの1つは、次のように、printStackTrace
の後に例外を静かに消費するコードがたくさんあります。
public void process() {
try {
System.out.println("test");
} catch(Exception e) {
e.printStackTrace();
}
}
私が遭遇したほぼすべてのJava記事およびプロジェクト)にこのような同様のコードがあります。私の知識に基づいて、これは非常に悪いです。例外は、ほとんど常に次のような外部コンテキストに転送されます:
public void process() {
try {
System.out.println("test");
} catch(Exception e) {
e.printStackTrace();
throw new AssertionError(e);
}
}
ほとんどの場合、例外は、基になるフレームワーク(Java Swingなど)に属する最も外側のループで処理されることになります。 Javaの世界でこのようにコーディングするのが標準のように見えるのはなぜですか?私は困惑しています。
私の経歴に基づいて、私はprintStackTrace 完全にを削除したいと思います。私は単純に未処理のRuntimeException
(またはさらに良いのはAssertionError
)として再スローし、最も適切な場所、つまりフレームワークの最も外側のループでそれをキャッチしてログに記録します。
public void process() {
try {
System.out.println("test");
} catch(Exception e) {
throw new AssertionError(e);
}
}
私はいつも思っていましたが、これは次のシナリオに似ています。
"男が撃たれる。
彼は息を止め、バスに乗るのに十分な力を持っています。
10マイル後、男はバスを降り、数ブロック歩いて死亡しました。 "
警察が死体に到着したとき、彼らは今何が起こったかの手掛かりを持っていません。彼らは最終的に持っているかもしれませんが、それははるかに困難です。
より良いです:
「男は撃たれ、彼は即座に死にます、そして死体はちょうど殺人事件が起こったところに正確にあります。」
警察が到着すると、すべての証拠が揃います。
システムに障害が発生した場合、 fail fast /
質問への対応:
編集:
もちろん、キャッチセクションは便利です。
例外を除いて何かを実行できる場合は、それを実行する必要があります。
おそらくそれは特定のコードの例外ではないでしょう、おそらくそれは予想されるものです(そして私の例えでは、防弾ジャケットのようであり、男はそもそもショットを待っていました)。
そして、はい、キャッチを使用して、 抽象化に適切な例外をスローすることができます
通常、これはIDEが原因で、例外処理を使用して問題のコードをtry-catchブロックにラップする便利な「クイックフィックス」を提供しているためです。開発者はしません。
これは間違いなく悪い形です。
これは古典的な straw man argument です。 printStackTrace()
はデバッグを支援します。ブログや雑誌でそれを見たのは、ライターが例外処理よりもotherの説明に興味を持っていたからです。本番用コードでそれを見た場合、そのコードの開発者は無知または怠惰でした。これは、「Javaの世界」での一般的な慣行の例として取り上げられるべきではありません。
これが行われる理由は2つあることがよくあります
これはJavaに限った現象ではないと思います。このようなコーディングはC#やVB.Netでも頻繁に見られます。
表面的にはかなり衝撃的で、ひどく見えます。しかし、実際には何も新しいことではありません。エラーコードの戻り値と例外を使用するC++アプリケーションでは常に発生します。ただし、致命的な可能性のある戻り値を無視しても、voidを返す関数を呼び出す場合と実際には何の違いもないという違いがあります。
Foo* pFoo = ...;
pFoo->SomeMethod(); // Void or swallowing errors, who knows?
このコードは見栄えがよくなりますが、SomeMethod()がHResultを返すと言っても、意味的には例外を飲み込むことと変わりません。
Checked Exceptionsは失敗した実験であるため
(おそらくprintStackTrace()は本当の問題?:)です
私はこの種の緩やかなエラー処理動作がJavaプログラマーにとって基本的なものであることを暗示する口調に少し憤慨しています。確かに、Java他のすべてのプログラマーと同じように怠惰で、Javaは人気のある言語なので、多くのコードが例外を飲み込んでいることに気付くでしょう。
また、他の場所で指摘されているように、チェックされた例外のJavaの強制宣言には理解できるフラストレーションがありますが、個人的には問題はありません。
私が問題を抱えているのは、おそらく、コンテキストを気にすることなく、Web上の一連の記事やコードスニペットを簡単に処理していることです。真実は、特定のAPIがどのように機能するか、または何かを開始する方法を説明しようとする技術記事を書いているとき、コードのいくつかの側面をスキップする可能性が高いです-直接ではないエラー処理特に、例のシナリオで例外が発生する可能性が低い場合は、あなたが実証しているものに関連するものが処分の可能性が高い候補です。
その性質の記事を書く人は、妥当な信号対雑音比を維持する必要があり、かなり公平に言えば、開発している言語に関する基本を知っていると想定する必要があるということです。エラーを適切に処理する方法、および他の多くのこと。記事に出くわし、適切なエラーチェックの欠如に気づいたら、それで問題ありません。これらのアイデア(もちろん、正確なコード自体はありませんよね?)を本番用コードに組み込むときは、作者が慎重に除外したすべてのビットとボブを最も適切な方法で処理するようにしてくださいあなたが開発しているものに適しています。
私はそれらに戻ることなくそのような問題を簡単に処理する非常に高レベルの紹介記事に問題がありますが、エラー処理に関してJavaプログラマー)の特定の「考え方」がないことに注意してください;私も、問題のすべてに対処しなくても、愛するC#プログラマーをたくさん知っています。
System.out印刷またはe.printStackTrace()-System.outの使用を意味する通常、赤信号であり、誰かが勤勉な仕事をしなかったことを意味します。デスクトップを除くJavaアプリケーション、ほとんどのJavaアプリはロギングを使用する方が良いでしょう。
メソッドの失敗モードが操作なしの場合、理由(および存在)を記録するかどうかに関係なく、例外を食べることはまったく問題ありません。ただし、より一般的には、catch句は何らかの例外的なアクションを実行する必要があります。
例外の再スローは、必要な情報がまだ利用可能なレベルでキャッチを使用して作業の一部をクリーンアップする場合、または例外を呼び出し元により適した例外タイプに変換する必要がある場合に最適です。
他の人が指摘したように、これが表示される理由は、次の3つの理由のいずれかです。
最後のポイントは発生する可能性が最も低いです。私は誰もが実際にこの方法でデバッグするとは思わないので、私はこれを言います。デバッガーでコードをステップ実行すると、デバッグがはるかに簡単になります。
キャッチブロックですべきことの最も適切な説明は、 の第9章にあります。有効なJavaJoshua Blochによる 。
これは、catchブロックが実際に空の場合にのみ静かに消費されます。
記事に関する限り、例外への対処方法以外のいくつかの点を証明する上で、おそらくもっと興味深いでしょう。彼らは単に要点を正確に理解し、可能な限り最短のコードを用意したいだけです。
明らかにあなたは正しいですが、例外が「無視」される場合は、少なくともログに記録する必要があります。
Javaプログラマーのほとんどは例外をどうするかわからないのではないかと思います。そして、常にそれを「名目」の場合のコーディングを遅くする煩わしさと見なします。もちろん彼らは完全に間違っていますが、IT IS例外を正しく処理するために重要です。このようなプログラマーに遭遇するたびに(頻繁に発生します)、彼に2つの読み取りエントリを与えます:
ちなみに、RuntimeExceptionに埋め込まれた型付き例外を再スローするために型付き例外をキャッチするのは愚かであることに強く同意します。
C#では、すべての例外はランタイム例外ですが、Javaには、実行時例外とチェック例外があり、メソッドでキャッチまたは宣言する必要があります。「最後に、そこに記載されている例外をキャッチするか、メソッドがそれらの例外も宣言する必要があります。
例外処理は記事の主題とは無関係であるため、Java記事は通常、スタックトレースを出力するか、コメントを付けます。ただし、プロジェクトでは、例外の種類に応じて、これに対して何かを行う必要があります。
チェックされた例外とインターフェースの組み合わせにより、コードは決してスローされない例外を処理しなければならない状況につながります。 (同じことが通常の継承にも当てはまりますが、インターフェースで説明する方が一般的で簡単です)
理由:インターフェースの実装は、インターフェース仕様で定義されたもの以外の(チェックされた)例外をスローしない場合があります。そのため、インターフェイスの作成者は、インターフェイスを実装するクラスのどのメソッドが実際に例外をスローする必要があるかを知らないため、すべてのメソッドが少なくとも1種類の例外をスローする可能性があると明確に規定する場合があります。例:JDBC。すべてとその祖母がSQLExceptionをスローするように宣言されています。
しかし実際には、実際の実装の多くのメソッドは単純に失敗することはないため、どのような状況でも例外がスローされることはありません。このメソッドを呼び出すコードは、なんとかして例外を「処理」する必要があります。最も簡単な方法は、例外を飲み込むことです。決して実行されることのない、一見役に立たないように見えるエラー処理でコードを乱雑にしたくはありません。
プログラマーが自分の仕事を正しく行うなら、これを頻繁に見るべきです。例外を無視することは悪い、悪い習慣です!しかし、これを実行する理由と、より適切な解決策にはいくつかの理由があります。
「これは起こらない!」もちろん、この例外が発生しないことを「知っている」こともありますが、発生した例外を単に無視するのではなく、「原因」として実行時例外を再スローするほうがより適切です。きっと将来いつか起こるでしょう。 ;-)
コードのプロトタイピング何かを入力してそれが機能するかどうかを確認するだけの場合は、発生する可能性のあるすべての例外を無視することができます。これは私が遅延キャッチをする唯一のケースです(Throwable)。しかし、コードが有用なものになる場合は、適切な例外処理を含めます。
「どうすればいいかわからない!」アプリケーションのこのレイヤーでは適切な処理を実行できないため、多くのコード、特にライブラリコードが発生し、例外が発生しました。これをしないでください!例外を再スローするだけです(メソッドシグネチャにthrows句を追加するか、例外をライブラリ固有のものにラップします)。
常に転送するか、実際の状況で適切に処理する必要があります。多くの記事やチュートリアルでは、コードを簡略化してより重要なポイントを得ていますが、最も簡単なことの1つはエラー処理です(エラー処理に関する記事を書いている場合を除きます)。 Javaコードは例外処理をチェックするため、単純なサイレント(またはログステートメント)キャッチブロックを配置するのが、実例を提供する最も簡単な方法です。
これがサンプルコード以外の何かで見つかった場合は、コードをTDWTFに転送してください。ただし、現時点ではコードの例が多すぎる可能性があります。
チェックされた例外を再スローすることの方が良いとは思いません。キャッチとは処理を意味します。再スローする必要がある場合は、キャッチしないでください。その場合は、メソッドシグネチャにthrows句を追加します。
私は、チェックされた例外をチェックされていないものにラップすること(たとえば、SpringがチェックされたSQLExceptionをそのチェックされていない階層のインスタンスにラップする方法)は許容できると言います。
ロギングは処理と見なすことができます。コンソールに書き込むのではなく、log4jを使用してスタックトレースをログに記録するように例を変更した場合、それで問題ありませんか?それほど大きな変化はありません、IMO。
実際の問題は、例外的で許容可能な回復手順と見なされるものです。例外から回復できない場合、最善の方法は失敗を報告することです。
チェックされた例外をチェックされていない例外にラップしないでください。
自分がすべきでないと思う例外に自分が対処していることに気付いた場合、私のアドバイスは、おそらく間違った抽象化レベルで作業しているということです。
詳しく説明します。チェックされた例外とチェックされていない例外は2つの非常に異なる獣です。チェック例外は、エラーコードを返す従来の方法と似ています...はい、エラーコードよりも処理が少し面倒ですが、利点もあります。未チェックの例外とは、プログラミングエラーと重大なシステム障害です...つまり、例外的な例外です。多くの人は、例外が何であるかを説明しようとすると、これら2つの非常に異なるケースの違いを認めないため、完全に混乱します。
例外の本当のポイントは、エラー処理を簡素化し、エラー検出から分離することです。これは、エラー処理コードがいたるところに散らばっており、失敗する可能性のあるすべての呼び出しについて、戻りコードをチェックするエラーコードによるエラーの表現とは対照的です。
例外がエラーを表す場合(これはほとんどの場合です)、通常、それを処理する最も合理的な方法は、救済し、処理を上位層に任せることです。意味のあるセマンティクスが追加されている場合、別の例外の再スローを検討する必要があります。つまり、このエラーは異常なシステム障害/一時的な(ネットワーク)問題/これはクライアントまたはサーバー側のエラーなどです。
すべてのエラー処理戦略の中で最も無知なのは、エラーメッセージを非表示にするか単に出力して、何も起こらなかった場合に進むことです。
Sunの人々は、コードをより明確にしたいと考え、プログラマーに、どのメソッドによってどの例外がスローされる可能性があるかを書かせるようにしました。それは正しい動きのように見えました-プロトタイプであることを考えると、メソッド呼び出しからの戻り値は誰にでもわかります(この型の値を返すか、指定されたクラス(またはサブクラス)のインスタンスをスローする可能性があります)。
しかし、多くの無知なJava=プログラマーで判明したため、今では例外処理を、それが言語のバグ/「機能」であるかのように扱い、回避策が必要で、最悪または可能な限り最悪の方法でコードを記述しています。 :
「正しい方法」の書き方は?
指摘したように、printStackTrace()の呼び出しは実際にはサイレント処理ではありません。
この種の例外の「飲み込み」の理由は、チェーンに例外を渡し続けると、例外を処理する必要があるどこかか、アプリケーションをクラッシュさせるためです。したがって、情報ダンプを使用して発生するレベルで処理することは、情報ダンプを使用して最上位レベルで処理することと同じです。
その怠惰な練習-それ以外に何も実際にはありません。
それは通常、あなたが本当に例外を気にしないときに行われます-指仕事を増やすのではなく。
彼らはまだこのトリックを学んでいないので:
class ExceptionUtils {
public static RuntimeException cloak(Throwable t) {
return ExceptionUtils.<RuntimeException>castAndRethrow(t);
}
@SuppressWarnings("unchecked")
private static <X extends Throwable> X castAndRethrow(Throwable t) throws X {
throw (X) t;
}
}
class Main {
public static void main(String[] args) { // Note no "throws" declaration
try {
// Do stuff that can throw IOException
} catch (IOException ex) {
// Pretend to throw RuntimeException, but really rethrowing the IOException
throw ExceptionUtils.cloak(ex);
}
}
}
チェックされた例外があり、メソッドでそれを処理したくない場合は、メソッドに例外をスローさせるだけです。例外をキャッチして処理するのは、それを使用して何か便利なことをする場合のみです。ユーザーが例外を探してログを読んだり、例外がスローされた場合の対処法を理解したりする時間はめったにないので、それをログに記録するだけではあまり役に立ちません。
例外のラップはオプションですが、以下の場合を除き、これを行うことはお勧めしません。既存のインターフェースと一致する別の例外をスローしている、またはそのような例外をスローする方法が実際にはない。
ところで:あなたがチェックされた例外を再びスローしたい場合は、これを行うことができます
try {
// do something
} catch (Throwable e) {
// do something with the exception
Thread.currentThread().stop(e); // doesn't actually stop the current thread, but throws the exception/error/throwable
}
注:これを行う場合、この状況ではコンパイラーがこれを行うことができないため、メソッドのスロー宣言が正しいことを確認する必要があります。
Catch Exceptionを使用する理由はたくさんあります。多くの場合、RuntimeExceptionsもキャッチするため、これは悪い考えです。また、これが発生した後に、基になるオブジェクトがどのような状態になるかわかりません。これは予期しない状況では常に難しいことです。残りのコードが後で失敗しないことを信頼できますか。
あなたの例はスタックトレースを出力するので、少なくともあなたは根本的な原因が何であったかを知るでしょう。大きなソフトウェアプロジェクトでは、これらをログに記録することをお勧めします。そして、ログコンポーネントが例外をスローしないことを期待しましょう。無限ループに陥る可能性があります(おそらくJVMが強制終了されます)。
例外を現在のメソッドのスコープ外で処理する場合は、実際にキャッチする必要はありません。代わりに、メソッドシグネチャに「throws」ステートメントを追加します。
あなたが見たtry/catchステートメントは、プログラマーがその場で例外を処理することを明示的に決定し、それ以上それをスローしないことをコードにのみ示しています。
開発者は、特定のコンテキストで「正しいことをする」ことの重要性も検討しようと思う。多くの場合、コードを破棄したり、例外を上方に伝播したりしても、例外は致命的であるため何も購入されませんが、「間違ったことをする」ことで時間と労力を節約することもできます。
経験から、例外を飲み込むことは、それが印刷されない場合にほとんど有害です。クラッシュした場合に注意を引くのに役立ちますが、私は意図的にそれを行いますが、例外を出力して続行するだけで問題を見つけて修正できますが、通常は同じコードベースで作業している他の人に悪影響を与えることはありません。
私は実際にはFail-Fast/Fail-HARDに夢中ですが、少なくともそれを印刷してください。あなたが実際に例外を「食べる」場合(それは本当に何もしないことです:{})誰かがそれを見つけるのに数日かかります。
問題は、Javaは、開発者がスローされないこと、またはスローされても気にしないことを知っている多くのことをキャッチすることを強制します。最も一般的なのはThread.sleep()です。 。ここにスレッドの問題を引き起こす可能性のある穴があることに気づきましたが、一般的にはそれを中断していないことを知っています。
例外から回復できない場合、通常は例外を飲み込みますが、それは重要ではありません。良い例の1つは、データベース接続を閉じるときにスローされるIOExcetionです。これが発生した場合、本当にクラッシュしますか?そのため、ジャカルタのDBUtilsにはcloseSilentlyメソッドがあります。
ここで、回復できないが(通常はプログラミングエラーが原因で)重要なチェック例外については、それらを飲み込まないでください。例外は問題の原因にできるだけ近い場所に記録する必要があると思うので、printStackTrace()呼び出しを削除することはお勧めしません。ビジネスメソッドでこれらの例外を宣言する必要がないという唯一の目的で、それらをRuntimeExceptionに変換する必要があります。 createClientAccount()などの高レベルのビジネスメソッドがProgrammingErrorException(SQLExceptionを読み取る)をスローすることは実際には意味がありません。SQLにタイプミスがある場合や、不正なインデックスにアクセスしている場合は、何もできません。