この記事では、Java例外の選択と使用におけるいくつかの誤解に焦点を当てています。読者が例外処理のポイントと原則のいくつかを習得し、要約と誘導に注意を払うことができることを願っています。例外を処理することによってのみ、開発者の基本的な品質を改善し、システムの堅牢性を向上させ、ユーザーエクスペリエンスを向上させ、製品の価値を向上させることができます。
誤解1。異常な選択
図1。異常分類
図1は、例外の構造を説明しています。実際、私たちは皆、異常が異常の検出と非検出異常の検出に分割されていることを知っていますが、実際には、これら2つの異常の適用は混乱しています。検出されない例外は使いやすいため、多くの開発者は例外の検出は役に立たないと考えています。実際、異常なアプリケーションシナリオは次のように要約できます。
1.コールコードは引き続き実行されず、すぐに終了する必要があります。サーバーが接続されていない、パラメーターが正しくないなど、この状況にはあまりにも多くの可能性があります。これらの時点では、非検出された例外は適用できず、コードの明示的なキャプチャーと処理を呼び出す必要はなく、コードは簡潔で明確です。
2。呼び出しコードには、さらに処理と回復が必要です。 Sqlexceptionが非検出された例外として定義されている場合、開発者は、Sqlexceptionがコードの明示的なキャプチャーと処理を呼び出す必要がないと自然に信じるでしょう。これは、コードが例外を生成した後、開発者が明示的にリソースをキャプチャしてクリーンアップするように駆動されるという例外を検出することとして定義されているためです。もちろん、リソースをクリーンアップした後、プログラムの実行を防ぐために、検出されない例外を引き続き投げ続けることができます。観察と理解によれば、例外の検出はツールクラスに主に適用できます。 Java Learning Group 669823128
誤解2:ページまたはクライアントに直接例外を表示します。
クライアント側に例外を直接印刷するのが一般的です。 JSPを例にとって、コードが実行されると、コンテナはデフォルトでページ上の例外スタック情報を直接印刷します。実際、顧客の観点からは、例外は実際的な意味を持たず、ほとんどの顧客は例外情報をまったく理解できません。また、ソフトウェア開発は、ユーザーに例外を直接提示することを避けるようにする必要があります。
リスト1
<strong>パッケージ</strong> com.ibm.dw.sample.exception;/*** custom runtimeexception*エラーコード属性を追加*/<strong> public </strong> <strong> class </strong> <strong> runtimeexception </strong> <strong>コード<strong> public </strong> <strong> static </strong> <strong> final </strong> integer generic = 1000000; //エラーコード<strong>プライベート</strong> integerエラーコード。 <strong> public </strong> <strong> runtimeexception </strong>(integer errorcode、throwable bause){<strong> this </strong>(errorcode、<strong> null </strong>、couse); } <strong> public </strong> <strong> runtimeexception </strong>(string message、throwable bause){//一般的なエラーコード<strong> this </strong>(generic、message、cause); } <strong> public </strong> <strong> runtimeexception </strong>(integer errorcode、string message、throwable bause){<strong> super </strong>(メッセージ、原因); <strong> this </strong> .errorcode = errorCode; } <strong> public </strong> integer <strong> getErrorCode </strong>(){<strong> return </strong> errorCode; }}例のコードが示すように、エラーコードを例外に導入します。例外が発生したら、例外のエラーコードをユーザーに提示するか、エラーコードをより理解しやすいプロンプトに変換します。実際、ここのエラーコードには別の機能も含まれており、開発者はエラーコードに基づいてどのタイプの例外が発生したかを正確に知ることもできます。
誤解3:コード階層の汚染
多くの場合、コードをサービス、ビジネスロジック、DAOなどのさまざまな階層に分割します。
リスト2
<strong> public </strong> customer <strong> retivecustomerbyid </strong>(long id)<strong> shrow </strong> sqlexception {// idに基づいてデータベースをクエリ}一見すると、上記のコードに問題はありませんが、設計結合の観点から慎重に考えると、ここでのsqlexceptionは上部の呼び出しコードを汚染します。呼び出しレイヤーは、トライキャッチを明示的に使用してキャプチャするか、さらに高いレベルに投げ込む必要があります。設計の分離原則によれば、適切に変更できます。
リスト3
<strong> public </strong> customer <strong> retivecustomerbyid </strong>(long id){<strong> try </strong> {// idに基づいてデータベースをクエリ} <strong> </strong>(sqlexception>(sqlexception){//非検出されていない例外カプセル化を使用して例外を検出します。 runtimeexception(sqlerrorcode、e); } <strong>最後に</strong> {//接続を閉じてリソースをクリーンアップ}}誤解4:例外を無視します
以下の例外処理は、コンソールの例外を出力するだけであり、意味がありません。さらに、ここに例外が表示され、プログラムは中断されず、コールコードは引き続き実行され、より例外が発生します。
リスト4
<strong> public </strong> <strong> void </strong> <strong> retiveObjectbyid </strong>(long id){<strong> try </strong> {// .. sqlexception} <strong> <strong>(sqlexception escy>(sqlexception exce){/***ここにあることを知っている人がいます。 *生産環境では、エラースタックをログに出力する必要があります。 *そして、プログラムはキャッチプロセスの後も実行され続けます。これにより、さらなる問題が発生します*/ ex.printstacktrace(); }}再構築することができます:
リスト5
<strong> public </strong> <strong> void </strong> <strong> retiveObjectbyid </strong>(long id){<strong> try </strong> {// .. sqlexception} <strong> <strong>(sqlexception exce){squlesception exper){<strong> show </strong> new </strong> new </strong> retive objectbyid "、ex); } <strong>最後に</strong> {//結果の結果、ステートメント、接続など}}}この誤解は比較的基本的であり、通常の状況では、この低レベルの間違いはありません。
誤解5:ループステートメントブロックに例外を含めます
次のコードに示されているように、例外はforループステートメントブロックに含まれています。
リスト6
<strong> for </strong>(<strong> int </strong> i = 0; i <100; i ++){<strong> try </strong> {} <strong> catch </strong>(xxxexception e){//…。 }}私たちは皆、例外処理がシステムリソースを占めることを知っています。一見すると、誰もがそのような間違いを犯さないと思っていました。別の観点から見ると、ループはクラスAで実行され、クラスBの方法はループで呼び出されますが、クラスBで呼ばれる方法にはトリキャッチなどのステートメントブロックが含まれています。クラスの階層が薄れ、コードは上記とまったく同じです。
誤解6:例外を使用して、すべての潜在的な例外をキャプチャします
メソッドの実行中に、いくつかの異なるタイプの例外がスローされます。コードを簡単にするために、ベースクラスの例外は、次の例に示すように、すべての潜在的な例外をキャッチするために使用されます。
リスト7
<strong> public </strong> <strong> void </strong> <strong> retiveObjectbyid </strong>(long id){<strong> try </strong> {//…コードコールIoException //…sqlexceptionをスローするコードコール複数のレベルがこれをキャッチした場合、元の例外の有効な情報が失われます}}再構築できます
リスト8
<strong> public </strong> <strong> void </strong> <strong> retiveObjectbyid </strong>(long id){<strong> try </strong> {// .. runtimeexception、ioexception、sqlexception} <strong> catch </strong>(ioexception>(ioexception>(ioexception>) runtimeexception(/*ここでioExceptionに対応するエラーコードを指定*/コード、 "Exception <strong> in </strong> retiveObjectbyid"、e); } <strong> catch </strong>(sqlexception e){// sqlexception <strong> new </strong> <strong> new </strong> runtimeexception(/*ここでsqlexceptionに対応するエラーコードを指定*/"/strong> in </strong> retraine objectbyid"、e); }}誤解7:マルチレベルのカプセル化は、検出されない例外をスローします
さまざまな種類の例外が異なるキャプチャステートメントを使用する必要があると常に主張する場合、ほとんどの例はこのセクションをバイパスできます。ただし、1つのコードコールのみが複数の例外をスローする場合、異なるタイプの例外ごとにキャッチステートメントを記述する必要はないことがよくあります。開発のために、プログラムの特定の問題を説明するのに十分な例外が十分です。
リスト9
<strong> try </strong> {// runtimeexception、ioexeptionまたはその他が投げられる場合があります。 //ここと6つの誤解の違いに注意してください。複数の例外をスローするコードを次に示します。上記はコードの複数のセグメントであり、それぞれが異なる例外をスローします} <strong> catch </strong>(<strong>例外</strong> e){//常にruntimeexceptionに変換しますが、ここでのeは実際にはruntimeexceptionのインスタンスであり、前のコードにカプセル化されています。 runtimeexception(/**/code、/**/、e);}上記の例に示すように、すべての例外をruntimeexceptionに変換する場合、例外タイプがすでにruntimeexceptionである場合、別のカプセル化を行います。 RuntimeExceptionは再び再エンコールされ、元のRuntimeExceptionによって運ばれる有効な情報が失われました。
解決策は、runtimeexceptionクラスに関連するチェックを追加して、パラメーターがruntimeexceptionのインスタンスではないことを確認できることです。その場合、対応する属性を新しく作成したインスタンスにコピーします。または、さまざまなCatchステートメントブロックを使用して、runtimeexceptionおよびその他の例外をキャッチします。個人の好みの方法1、利点は自明です。
誤解8:マルチレベル印刷の例外
まず、2つのクラスAとBを定義する次の例を見てみましょう。クラスBコードはクラスAで呼び出され、クラスAとクラスBの両方のキャプチャと印刷の例外があります。
リスト10
<strong> public </strong> <strong> class </strong> <strong> a </strong> {<strong> private </strong> <strong> static </strong> logger logger = loggerfactory.getlogger(a.class); <strong> public </strong> <strong> void </strong> <strong> Process </strong>(){<strong> try </strong> {//クラスBをインスタンス化するには、B b = <strong> new </strong> b()などの他の注入方法に変更できます。 B.Process(); //他のコードは例外を引き起こす可能性があります} <strong> catch </strong>(xxxexception e){//クラスBプロセスメソッドが例外をスローする場合、例外はクラスが印刷され、ここでも印刷されます。 <strong>スロー</strong> <strong> new </strong> runtimeexception(/*エラーコード*/エラーコード、/*例外情報*/msg、e); }}} <strong> public </strong> <strong>クラス</strong> <strong> b </strong> {<strong> private </strong> <strong> static </strong> logger logger = loggerFactory.getLogger(b.class); <strong> public </strong> <strong> void </strong> <strong> process </strong>(){<strong> try </strong> {//例外をスローする可能性のあるコード} <strong> catch </strong>(xxxexception e){logger.error(e); <strong>スロー</strong> <strong> new </strong> runtimeexception(/*エラーコード*/エラーコード、/*例外情報*/msg、e); }}}同じ例外が2回印刷されます。レベルがもう少し複雑である場合、ログの印刷のシステムパフォーマンスを考慮しないで、例外ログに特定の問題を見つけることは頭痛の種です。
実際、ログを印刷するには、コードの最も外側の層でキャプチャと印刷のみが必要です。例外印刷は、AOPとして記述され、フレームの最も外側の層に織り込むこともできます。
誤解9:例外に含まれる情報が完全に見つけることができないという問題
例外により、開発者は何が間違っているのかを知ることができるだけでなく、開発者が問題の原因を知る必要があることもよくあります。 Java .lang.Exceptionには文字列型パラメーターのコンストラクターがあり、この文字列はわかりやすい迅速な情報にカスタマイズできることがわかっています。
シンプルなカスタム情報開発者は、例外がどこに表示されるかを知ることができますが、多くの場合、開発者はこのような例外を引き起こしているパラメーターについてもっと知る必要があります。この時点で、メソッド呼び出しのパラメーター情報をカスタム情報に追加する必要があります。次の例には、1つのパラメーターのケースのみがリストされています。複数のパラメーターの場合、そのような文字列を整理するためのツールクラスを記述できます。
リスト11
public <strong> void </strong> retive objectbyid(long id){<strong> try </strong> {// .. sqlexception} <strong> catch </strong>(sqlexception ex){//例外情報にパラメーター情報を追加する<strong>スロー</strong> </strong> new> new> ringby <strong> with </strong> object id: "+ id、ex); }}誤解10:潜在的な異常を予測することはできません
コードを書くプロセス中、呼び出しコードの深い理解がないため、呼び出されたコードが例外を生成するかどうかを正確に判断することは不可能であるため、処理は無視されます。生産バグが生成された後、特定のコードに例外検索を追加する必要があることを覚えていましたが、例外の原因を正確に指摘することさえできませんでした。これには、開発者が自分が何をしているかを知るだけでなく、他の人が何をしたか、どのような結果が生じるかを可能な限り知り、グローバルな観点からアプリケーション全体の処理プロセスを検討する必要があります。これらのアイデアは、コードの執筆と処理に影響します。
誤解11:複数のサードパーティログライブラリの混合使用
現在、ますます多くのJavaサードパーティのログライブラリがあります。大規模なプロジェクトでさまざまなフレームワークが導入され、これらのフレームワークはさまざまなログライブラリの実装に依存します。最も厄介な問題は、必要なすべてのログライブラリを導入することではありません。問題は、導入されたログライブラリが互換性がないことです。プロジェクトの初期段階で簡単に解決できる場合は、必要に応じてコード内のすべてのログライブラリを再導入したり、フレームワークを変更したりできます。しかし、この種のコストはすべてのプロジェクトで手頃な価格ではなく、プロジェクトが進むにつれてリスクが大きくなるほど。
同様の問題を効果的に回避するにはどうすればよいですか?現在、ほとんどのフレームワークは同様の問題を考慮しています。 LIBライブラリでログ実装クラスを実行している場合にのみ、プロパティまたはXMLファイル、パラメーター、またはランタイムスキャンログ実装クラスを構成できます。アプリケーションが実行されている場合のみ、適用する特定のログライブラリを決定できます。
実際、複数のレベルのログ印刷を必要としないという原則に基づいて、元々ログ印刷コードと呼ばれる多くのクラスを簡素化できます。多くの場合、インターセプターまたはフィルターを使用してログを印刷して、コードのメンテナンスと移行のコストを削減できます。
結論
上記は純粋に個人的な経験と要約です。物事は弁証法的であり、絶対的な原則はありません。最も効果的な原則はあなたに適しています。上記の説明と分析が役立つことを願っています。