blog

例外使用の手引き

1.例外の適切な使用 上記の例では、配列のout-of-bounds例外を使用してデッドループから飛び出そうとしました。配列の探索シナリオの場合、その目的はJavaの例外メカニズムを使用し、パフォーマ...

Aug 1, 2020 · 8 min. read
シェア

例外を賢く使う

  • 課題

    まず反例を見てみましょう:

    try{
     int i = 0;
     while(true){
     range[i++].climb();
     } 
    } catch (ArrayIndexOutOfBoundsException){
    }
    

    }

  • ソリューション

    1. 例外は例外処理の場面でのみ使うべきで、制御フローに適用してはいけません
    2. 例外メカニズムはもともと異常な状況で使用されるように設計されているため、JVMの実装では最適化されていません。そのため、例外を使用してパフォーマンスの最適化を達成しようとしても、実現不可能です;
    3. 代わりにtry-catchブロックにコードを置くことで、最新のJVMが実行するかもしれない最適化のいくつかを防ぐことができます;
    4. 上記の例では、配列の標準的なトラバーサル・パターンは、JVMによって最適化される冗長性チェックにはつながりません;
  • はんけつをくだす

    例外を使用する場合、例外は例外としてのみ扱うべきであり、パフォーマンスの最適化のために使用しようとすべきではないと主張すべきです。

2.精査中の不必要な異常の使用を避けること

  • 課題

    チェックされた例外は、呼び出し元がcatchで例外を処理することを強制することで、プログラムの信頼性を確保するのに役立ちます。APIが複数のチェック例外を投げるように設計されている場合、呼び出し元は複数のcatchを使って適宜例外を処理するか、throwsで例外を投げる必要があります。では、メソッドが例外を投げるかどうかを設計する際の原則はどうあるべきでしょうか?

  • 課題

    1. 例外を投げるメソッドを設計する場合、別の考え方をすべきです。もしあなたが設計したAPIを使う呼び出し側だとしたら、スローした例外をどのように処理すべきでしょうか?もし明確な考えがあるのなら、それはこの例外を投げるのが適切だということです。つまり、その例外はAPI上避けられないものであり、いったん発生すれば呼び出し元が合理的に対処できるものであれば、検査された例外を投げることが適切であることを示しており、これら2つの前提条件がすべて成立していることを要求しなければなりません;

    2. チェックされた例外を持つメソッドへの呼び出しをリファクタリングする」方法の1つは、例外をスローするメソッドを2つのメソッドに分割し、最初のメソッドは、次のコード例のように、例外をスローすべきかどうかのステータステストを示すブール値を返します:

      //Invocation with checked exception
      try {
       obj.action(args);
      } catch(TheCheckedException e) {
       // Handle exceptional condition
      }
      のためのリファクタリングである:
      // Invocation with state-testing method and unchecked exception
      if (obj.actionPermitted(args)) { 
       obj.action(args);.
      } else {
       // Handle exceptional condition
      }
      

      上記のリファクタリング・アプローチにより、コード呼び出しは複雑になりますが、プログラムはより柔軟になります。しかし、状態の更新や状態テストが含まれる場合は常に、スレッドセーフを考慮する必要があることに注意することが重要です。

  • はんけつをくだす

    チェックされた例外は、呼び出し側が対処しなければならないので、APIを設計する際には、投げられるチェックされた例外に注目し、それが本当に妥当なものなのか、呼び出し側の視点から考える必要があります。

3.例外は適宜文書化する必要があります。

  • 課題

    メソッドのAPIを設計する際、例外は重視されるべきものです。同様に、例外の条件はメソッドのドキュメントのアノテーションに明示する必要があります。では、例外のドキュメントを書く際に留意すべき点は何でしょうか?

  • ソリューション

    1. **そして、javadocの@throwsタグを使用して、どのような条件で各例外がスローされたかを正確に文書化してください。メソッドが複数の例外クラスをスローする場合、スローする例外クラスの親クラスを使用せず、メソッドを "throwsException" と宣言したり、最悪 "throw Throwable" と宣言してはいけません。このような宣言は、開発者に「このメソッドがどのような例外を投げるのか」という有益な情報を与えず、実際、同じ実行状況下でメソッドが投げる可能性のある他の例外を不明瞭にしてしまうため、そのメソッドが使われるのを妨げてしまいます;
    2. メソッドが投げる可能性のある実行時例外を表示するには javacdoc の @throws タグを使用しますが、メソッドの宣言に実行時例外を含めるために throws キーワードを使用しないでください。APIを使用するプログラマは、どの例外がチェックされ、どの例外がチェックされないかを知っていなければなりません。
  • はんけつをくだす

    抽象メソッドや具象メソッドと同じように、チェックされた例外とチェックされな い例外について、それぞれのメソッドでスローされる例外のドキュメントを作成しましょう。チェックされた例外にはそれぞれ別の throws 文を用意し、チェックされていない例外には throws 文を用意しないでください。スローされる例外についてのドキュメントがないと、 他の開発者があなたのクラスやインターフェイスを効果的に使うことが難しくなったり、 できなくなったりします。

4.適切な例外メッセージの記述

  • 課題

    捕捉されない例外によってプログラムが失敗すると、システムは自動的にその例外のスタック・トレースを出力します。スタック・トレースに含まれるのは、例外の文字列表現、つまり toString メソッドの呼び出しの結果です。スタック・トレースには通常、例外のクラス名が含まれ、その後に詳細メッセージが続きます。プログラマは、出力された例外メッセージに基づいてトラブルシューティングを行います。つまり、例外メッセージが十分でないと、トラブルシューティングが遅くなります。

  • ソリューション

    1. 重要な原則の 1 つは、例外詳細メッセージは、後の分析のために例外を明確に記述する必要があるということです。例外を明確に記述するために、例外の詳細メッセージには、例外に "寄与する" すべてのパラメータとフィールドの値を含める必要があります。例えば、IndexOutOfBoundsException の詳細メッセージには、下界、上界、および境界内にない添え字の値を含める必要があります。詳細メッセージは、失敗に関する多くの情報を提供します。これらの 3 つの値のいずれか、またはすべてが間違っている可能性があります。実ラベルの添え字の値は、下界より小さいか上限と等しいか、または小さすぎたり大きすぎたりする無効な値である可能性があります。下限が上限より大きいこともあり得ます。これらのシナリオはそれぞれ異なる問題を表しており、プログラマがどのエラーを探すべきかを知っていれば、トラブルシューティングのプロセスを大幅に短縮することができます。例外の詳細メッセージに失敗を検出するのに十分な情報が含まれていることを確認する1つの方法は、例外のコンストラクタにこの情報を導入することです。
    2. スタックトレースは、例外がスローされた正確なファイルと行、スタック内の他のすべてのメソッドコールが配置されているファイルと行を含んでいるのが普通です。その結果、ソースコードを読むことで得られる情報もあるため、例外がオーバーロードされている場合、例外を記述することは実際には冗長になります;
    3. 例外の詳細メッセージは、エンドユーザが理解できなければならない「ユーザレベルのエラーメッセージ」と混同してはいけません。ユーザーレベルのエラーメッセージとは異なり、例外の文字列表現は主にプログラマーやドメインサービス担当者が障害の原因を分析するために使用することを目的としています。
  • はんけつをくだす

    例外が捕捉されたとき、重要な例外情報があればトラブルシューティングが容易になりますが、重要な例外情報を追加する必要があります。

例外のカプセル化を試みます

  • 課題

    メソッドが実行中のタスクとは明らかに関係のない例外を投げる場合、** この状況は圧倒的なものになる可能性があります。**これはメソッドが高レベルで低レベルの例外を投げ続ける場合に起こりがちです。これは混乱を招くだけでなく、実装の詳細が上位のAPIを汚染することを許します。上位の実装がその後のリリースで変更された場合、それが投げる例外も一緒に変更される可能性があり、潜在的に既存のクライアント・プログラムを破損する可能性があります。では、高レベルAPIと低レベルAPIで例外を処理する場合、どうすべきでしょうか?

  • ソリューション

    1. 上位レベルの実装は、下位レベルの例外をキャッチし、上位レベルの抽象に従って解釈できる例外をスローする必要があります。これを例外変換と呼びます:

      try{ //Use lower-level abstraction to do our bidding ... }cache(LowerLevelException e){ throw new HigherLevelException(...); }

      このアプローチでは、HighLevel のビジネス上の意味に一致する HighLevelException を、基礎となる例外がキャッチされたときに HighLevel API でスローする必要があります。

    2. 例外チェーニングと呼ばれる例外変換の特別な形式が上位の例外に渡され、**上位の例外は下位の例外を取得するためのアクセスメソッド(Thmwable.getCause)を提供します。例

      try{ //Use lower-level abstraction to do our bidding }cache(LowerLevelException cause){ throw new HigherLevelException(cause); }

      HighLevelException のコンストラクタは次のとおりです:

      class HigherLevelException extends Exception {
       HigherLevelExceptionCThrowable cause) {
       super(cause);
       }
      }
      
    3. 異常に直面したら、具体的にどうすればいいのでしょうか?

      • **例外の変換は、上位のレベルで基礎となる例外を直接スローし続けるよりは改善されますが、乱用すべきではありません。** 可能であれば、下位レベルからの例外を処理するためのベストプラクティスは、基礎となる 例外を何としても回避することです。下位レベルのメソッドにパラメータを渡し、上位レベルのメソッドのパラメータの有効性をチェックすることで、下位レベルのメソッドが例外をスローしないようにできることがあります;
      • 低レベルの例外を回避することが不可能な場合、次善の選択肢は、高レベルAPIの判断によってこれらの例外を回避させ、高レベルメソッドの呼び出し元を低レベルの問題から隔離することです。この場合、この目的のためにロギングツールを使用できます。
  • はんけつをくだす

    低レベルのメソッドが投げる例外がすべて高レベルでも適切であることを保証している場合を除き、低レベルからの例外をブロックしたり処理したりすることができない場合は、例外変換を使用するのが一般的です。例外の連鎖は、高レベルの例外と低レベルの例外の両方に対して最高の機能を提供します。

例外を無視しない

  • 課題

    そんな反例もあります:

    try {
     ...
    }catch (SomeException e) {
    }
    

    }

    上記の例では、例外がキャッチされますが、それは空のキャッチブロックですが、あなたはうっかり隠された危険をまく例外を処理することを忘れてしまう、例外に直面する必要があります基本原則は何ですか?

  • ソリューション

    1. APIの設計者が、あるメソッドが例外を投げると宣言した場合、彼らは何らかの危険な状況を想定しようとしているのです;
    2. この原則は、チェックされた例外にもチェックされていない例外にも等しく適用されます。例外が予見可能な例外条件であろうとプログラミングエラーであろうと、空のキャッチブロックで無視すると、エラーが発生したにもかかわらずプログラムが静かに実行されることになります。将来、明らかにエラーの発生源に関係する問題をプログラムが許容できなくなった時点で、システムに障害が発生する可能性があります。
  • はんけつをくだす

    例外に直面した場合の最も基本的で重要な原則は、例外を無視してはならず、対処しなければならないということです。

Read next

Spring5トランザクション

トランザクションは、データベース操作は、操作の論理的なセットは、1つの操作が失敗した場合、それらはすべて失敗します。 その結果、Zhang Sanのお金は増えますが、Li Siのお金は減りません。 トランザクションの伝播動作: メソッドがメソッド内にネストされたときに、トランザクションがどのように処理されるかを指します。よく使われるのは、REQUERDHE、.NET、.NET、.NET、.NET、.NET、.NET などです。 トランザクションの分離レベル: MySQL のトランザクション分離レベルと同じです。 タイムアウト: トランザクションの ...

Aug 1, 2020 · 3 min read