English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Javaで例外を効果的に処理する3つの原則

例外が強力なデバッグツールであるのは、以下の3つの質問に答えるためです:

     1何が間違ったのか?63;

     2どこでエラーが発生したのか?63;

     3なぜエラーが発生したのか?63;

例外が効果的に使用されている場合、例外のタイプは「何が投げられたのか」を回答し、例外のスタックトレースは「どこで投げられたのか」を回答し、例外のメッセージは「なぜ投げられたのか」を回答します。もし例外がこれらのすべての質問に答えていない場合、例外を使用する方法がよくない可能性があります。

デバッグプロセスで例外を最大限に活用するための3つの原則があります:

     1、具体的かつ明確に

     2、早期投げ出し

     3、遅延捕捉

効果的な例外処理のこれらの3つの原則を説明するために、この記事では架空の個人情報管理器クラスJCheckbookを使用して議論しています。JCheckbookは、預金、引出し、支払い書の発行などの銀行口座活動を記録および追跡するために使用されます。

具体的かつ明確に

JavaはThrowableから始まる例外クラスの階層構造を定義しており、ErrorとExceptionが拡張され、ExceptionはRuntimeExceptionに拡張されます。図に示されています。1図に示されています。

これらのクラスは一般的であり、多くのエラーメッセージを提供しませんが、これらのクラスをインスタンス化することは文法的に合法です(例:new Throwable())、しかし、より特化したサブクラスを使用する方が良いとされています。Javaは多くの例外サブクラスを提供しており、さらに具体的に必要な場合は、自分自身の例外クラスを定義することもできます。

例えば、java.ioパッケージではExceptionクラスのサブクラスであるIOExceptionが定義されており、さらに特化したものとしてFileNotFoundException、EOFException、ObjectStreamExceptionなどのIOExceptionのサブクラスがあります。それぞれは特定のIを表現しています。/Oエラー:ファイルの消失、異常なファイルの終わり、そして誤ったシリアライズオブジェクトストリームです。例外がより具体的であればあるほど、私たちのプログラムは「何が間違ったのか」をよりよく回答できます。

例外を捕捉する際には、できるだけ明確にすることが非常に重要です。例えば:JCheckbookはFileNotFoundExceptionをユーザーにファイル名を再入力させることで処理し、EOFExceptionの場合は、例外が投げられる前に読み取った情報に基づいてプログラムを続けることができます。ObjectStreamExceptionが投げられた場合は、プログラムはユーザーにファイルが破損していることを示し、バックアップファイルや他のファイルを使用するように指示する必要があります。

Javaは、同じtryブロックに対して複数のcatchブロックを定義できるため、各種例外に対して適切な処理を行うことができ、明確な例外のキャッチを簡単にします。

File prefsFile = new File(prefsFilename);
try{
  readPreferences(prefsFile);
}
catch (FileNotFoundException e){
  // ユーザーに指定されたファイルが
  // 存在しません
}
catch (EOFException e){
  // ユーザーにファイルの終わりに達したことを警告する
  // に達しました
}
catch (ObjectStreamException e){
   // ユーザーにファイルが破損していることを警告する
}
catch (IOException e){
  // ユーザーに他のIを警告する/O
  // エラーが発生しました
}

JCheckbookは、ユーザーに例外をキャッチした明確な情報を提供するために、複数のcatchブロックを使用しています。例えば、FileNotFoundExceptionがキャッチされた場合、ユーザーに別のファイルを指定するように指示できます。複数のcatchブロックがもたらす追加のコーディング作業は、場合によっては不必要な負担になることがありますが、この例では追加のコードがプログラムがユーザーに対してよりフレンドリーな反応を提供するのに役立ちました。

前三个catchブロックで処理された例外以外に、最後のcatchブロックはIOExceptionがスローされた場合にユーザーにより一般的なエラーメッセージを提供します。このようにして、プログラムは可能な限り具体的な情報を提供しつつ、予期せぬ他の例外を処理する能力を持つことができます。

時には開発者が一般化された例外をキャッチし、例外クラス名を表示したりスタックトレースをプリントアウトして「具体的な」ことをしようとします。絶対にそうすることはしないでください!ユーザーがjava.io.EOFExceptionやスタックトレースを見ると、頭が痛くなるだけで、助けを得ることはありません。具体的な例外をキャッチし、ユーザーに「人間の言葉」で正確な情報を提示することが重要です。ただし、例外のスタックトレースはログファイルにプリントアウトすることができます。例外とスタックトレースは、開発者に助けを提供するためにあるものです。ユーザーには提供しないでください。

最後に、JCheckbookはreadPreferences()内で例外をキャッチするのではなく、ユーザーインターフェースレイヤーでキャッチおよび処理を残して、ダイアログや他の方法でユーザーに通知することを選択しています。これを「遅延キャッチ」と言います。以下で詳しく説明します。

早期に投げる

例外のスタックトレースは、例外が発生した場所を特定するための正確なメソッド呼び出しの順序を提供します。これには、各メソッド呼び出しのクラス名、メソッド名、コードファイル名、行数が含まれます。

java.lang.NullPointerException
at java.io.FileInputStream.open(Native Method)
at java.io.FileInputStream.<init>(FileInputStream.java:103)
at jcheckbook.JCheckbook.readPreferences(JCheckbook.java:225)
at jcheckbook.JCheckbook.startup(JCheckbook.java:116)
at jcheckbook.JCheckbook.<init>(JCheckbook.java:27)
at jcheckbook.JCheckbook.main(JCheckbook.java:318)

上記はFileInputStreamクラスのopen()メソッドがNullPointerExceptionを投げる場合を示しています。ただし、FileInputStream.close()は標準Javaクラスライブラリの一部であり、この例外を引き起こす可能性のある問題の原因は私たちのコードではなくJava APIにある可能性があります。したがって、問題は前の方法のいずれかで発生している可能性があり、幸いにもスタックトレースに表示されています。

残念ながら、NullPointerExceptionはJavaで情報量が最も少ない(しかし最も頻繁に遭遇し、壊滅的な)例外です。それが最も気になることを全く言及しません:どこがnullか。したがって、どこで間違っているかを見つけるために数ステップ後退する必要があります。

スタックトレースを逐次逆追跡し、コードをチェックすることで、readPreferences()に空のファイル名パラメータが渡されたことが原因であることを確認できます。readPreferences()は空のファイル名を処理できないことを知っているので、すぐにこの条件をチェックします:

public void readPreferences(String filename)
throws IllegalArgumentException{
  if (filename == null){
     throw new IllegalArgumentException("filename is null");
  } //if
  //...他の操作を実行する...
  InputStream in = new FileInputStream(filename);
  //...プレファレンスファイルを読み取る...
}

「迅速失敗」と呼ばれる早期に例外を投げることで、例外が明確かつ正確になります。スタックトレースは何が間違っているか(無効なパラメータ値が提供されました)、なぜ間違っているか(ファイル名は空値ではありません)、そしてどこで間違っているか(readPreferences()の一部)をすぐに示します。そのため、私たちのスタックトレースは事実を提供できます:

java.lang.IllegalArgumentException: ファイル名がnullです
at jcheckbook.JCheckbook.readPreferences(JCheckbook.java:207)
at jcheckbook.JCheckbook.startup(JCheckbook.java:116)
at jcheckbook.JCheckbook.<init>(JCheckbook.java:27)
at jcheckbook.JCheckbook.main(JCheckbook.java:318)

また、含まれる例外情報(「ファイル名が空」)は、何が空であるかという具体的な質問に答え、例外が提供する情報を豊かにします。これは、私たちの前のコードでスローしたNullPointerExceptionが提供できなかったものです。

エラーを検出した際に即座に例外をスローすることで、不必要なオブジェクトの構築やリソースの占有(例えばファイルやネットワーク接続)を避けることができます。これらのリソースを開く際のクリーンアップ操作も省略できます。

遅延捕獲

初心者や上級者がよく犯す間違いは、プログラムが例外を処理できる前にそれを捕獲することです。Javaコンパイラは、検出された例外がキャッチまたはスローされることを要求することで、この行動を間接的に助長しています。自然な方法は、コードをtryブロックで包装し、catchで例外をキャッチしてコンパイラエラーを避けることです。

問題は、捕獲した後の例外に対してどうするかです。最も避けたいのは何もしないことです。空のcatchブロックは、すべての例外をブラックホールに投げ込み、エラーがどのタイミングでどの場所でどの理由で発生したかを説明するすべての情報が永遠に失われることになります。例外をログに書き出すのは少し良いですが、少なくとも記録が残ります。ただし、ユーザーがログファイルや例外情報を読んだり理解したりするわけにはいきません。readPreferences()でエラーメッセージダイアログを表示することも適切ではありません。なぜなら、JCheckbookは現在デスクトップアプリケーションですが、Webアプリケーションとしての構想もあります。その場合、エラーメッセージダイアログを表示することは選択肢ではありません。また、HTMLかC/Sバージョンでは、設定情報はサーバー上で読み取られ、エラーメッセージはWebブラウザやクライアントプログラムに表示される必要があります。readPreferences()は、設計段階でこれらの将来の要件も考慮すべきです。ユーザーインターフェースコードとプログラムロジックを適切に分離することで、コードの再利用性を高めることができます。

異常を処理する前に早すぎる捕獲は、通常より深刻なエラーや他の異常を引き起こします。例えば、先ほどのreadPreferences()メソッドがFileInputStreamのコンストラクタを呼び出す際に即座にFileNotFoundExceptionを捕獲して記録すると、以下のようにコードが変わります:

public void readPreferences(String filename){
  //...
  InputStream in = null;
  // この行動を行わないでください!!!
try{
  in = new FileInputStream(filename);
}
catch (FileNotFoundException e){
  logger.log(e);
}
in.read(...);
//...
}

上のコードはFileNotFoundExceptionから復旧できない場合にその例外をキャッチしています。ファイルが見つからない場合、以下のメソッドはファイルを読み取ることができません。readPreferences()が存在しないファイルを読み取ることを要求された場合に何が起こりますか?もちろんFileNotFoundExceptionは記録されますが、ログファイルを見ればわかります。しかし、プログラムがファイルからデータを読み取ろうとする際に何が起こりますか?ファイルが存在しないため、変数inは空であり、NullPointerExceptionが投げられます。

プログラムのデバッグ中に、本能的にログの最後の情報を見ることになります。それはNullPointerExceptionで、非常に厄介な例外です。この例外は非常に具体的ではありません。エラーメッセージは、何が間違っているかを誤解させ(実際のエラーはFileNotFoundExceptionではなくNullPointerExceptionです)、エラーの起源を誤解させます。実際の問題は、NullPointerExceptionを投げる数行外にあります。その間には、方法の呼び出しやクラスの破壊が何度も発生する可能性があります。私たちの注意はこの小さな問題から本質的なエラーから遠ざかり、ログを遡って問題の根源を見つけるまで引き延ばされます。

readPreferences()が本当にするべきことはこれらの例外をキャッチすることではないので、どのようなことができるのでしょうか?常識に反するかもしれませんが、最も適切な行動は何もしないこと、例外をすぐにキャッチしないことです。責任をreadPreferences()の呼び出し元に任せ、配置ファイルの欠損を適切に処理する方法を検討させ、ユーザーに他のファイルを指定するように指示したり、デフォルト値を使用したり、最悪の場合はユーザーに警告を表示してプログラムを終了させることです。

例外処理の責任を呼び出しチェーンの上位に移行する方法は、メソッドのthrows子句で例外を宣言することです。例外が発生する可能性がある場合、具体的であるほど良いです。これは、あなたのメソッドを呼び出すプログラムが認識し、処理する必要がある例外の種類を示すために使用されます。例えば、「遅延キャッチ」バージョンのreadPreferences()は以下のようになります:

public void readPreferences(String filename)
throws IllegalArgumentException ,
FileNotFoundException, IOException{
  if (filename == null){
      throw new IllegalArgumentException("filename is null");
   } //if
   //...
   InputStream in = new FileInputStream(filename);
//...
}

技術的には、私たちが宣言する必要がある唯一の例外はIOExceptionですが、FileNotFoundExceptionがメソッドからスローされる可能性があることを明確に宣言しています。IllegalArgumentExceptionは宣言する必要はありませんが、非検査例外(RuntimeExceptionのサブクラス)です。しかし、コードの文書化のために宣言しています(これらの例外もJavaDocsに記載されるべきです)。

もちろん、最終的にあなたのプログラムは例外をキャッチする必要があります。そうしないと、予期せぬ終了が発生します。ここでの技術は、適切なレベルで例外をキャッチすることです。これにより、プログラムは例外から意味のあるリカバリを行い、さらに深刻なエラーを引き起こさないようにすることができます。または、ユーザーに明確な情報を提供し、彼らがエラーから復旧する手助けをすることができます。あなたのメソッドがこれを達成できない場合は、例外を処理せず、後でキャッチし、適切なレベルで処理することを選択してください。

まとめ

経験豊富な開発者は知っていますが、プログラムのデバッグにおける最大の難点は、欠陥の修復よりも、大量のコードの中から欠陥の隠れ場所を見つけることです。この記事の3つの原則に従うことで、あなたの例外が欠陥を追跡し、除去する助けとなり、あなたのプログラムがより堅牢でユーザーフレンドリーになります。これがこの記事のすべてです。皆さんの学習や仕事に少しでも役立つことを願っています。

声明:このコンテンツはインターネットから取得しており、著作権者はすべての権利を保有しています。コンテンツはインターネットユーザーによって自発的に提供され、アップロードされています。このウェブサイトは所有権を持ちません。また、人工編集は行われていません。著作権侵害が疑われる場合は、以下のメールアドレスまでご連絡ください:notice#oldtoolbag.com(メールを送信する際、#を@に置き換えてください。報告を行い、関連する証拠を提供してください。一旦確認が取れた場合、このサイトは即座に侵害する可能性のあるコンテンツを削除します。)