7

現在、次の複合 if ステートメントがあります...

if ((billingRemoteService == null)
    || billingRemoteService.getServiceHeader() == null
    || !"00".equals(billingRemoteService.getServiceHeader().getStatusCode())
    || (billingRemoteService.getServiceBody() == null) 
    || (billingRemoteService.getServiceBody().getServiceResponse() == null) 
    || (billingRemoteService.getServiceBody().getServiceResponse().getCustomersList() == null) 
    || (billingRemoteService.getServiceBody().getServiceResponse().getCustomersList().getCustomersList() == null) 
    || (billingRemoteService.getServiceBody().getServiceResponse().getCustomersList().getCustomersList().get(0) == null) 
    || (billingRemoteService.getServiceBody().getServiceResponse().getCustomersList().getCustomersList().get(0).getBillAccountInfo() == null)
    || (billingRemoteService.getServiceBody().getServiceResponse().getCustomersList().getCustomersList().get(0).getBillAccountInfo().getEcpdId() == null)) {
        throw new WebservicesException("Failed to get information for Account Number " + accountNo);
}

return billingRemoteService.getServiceBody().getServiceResponse().getCustomersList().getCustomersList().get(0);

これは次のように単純化できませんでした...

try {
    //Check to be sure there is an EpcdId.
    (billingRemoteService.getServiceBody().getServiceResponse().getCustomersList().getCustomersList().get(0).getBillAccountInfo().getEcpdId();
    return billingRemoteService.getServiceBody().getServiceResponse().getCustomersList().getCustomersList().get(0);
} catch (NullPointerException npe) {
    throw new WebservicesException("Failed to get information for Account Number " + accountNo);
}

もしそうなら、Java 6 での 2 つのアプローチの「コスト」の違いは何ですか? 介在するすべての呼び出しが null でないことを確認するためだけに、かなり複雑な if ステートメントのように思えます。この操作は、異なるアカウントに対して何度も呼び出されます。

4

7 に答える 7

7

私は Edwin Buck の主張に反対しなければなりません。

彼は言い​​ます:

他の人が述べているように、例外は if ステートメントよりもコストがかかります。ただし、あなたのケースではそれらを使用しない優れた理由があります。「例外は特別なイベントのためのものです」

メッセージをアンパックするとき、メッセージに含まれていないものはエラー チェックであり、例外的なイベントではありません。

これは基本的に、エラー チェックを行う場合、(探していたために) エラーが予想されるため、例外ではないことを示しています。

しかし、それは「例外的な出来事」の意味ではありません。例外的なイベントとは、異常な/異常な/ありそうもないイベントを意味します。例外的とは、イベントが発生する可能性に関するものであり、あなたがそれを期待している (またはそうすべきである) かどうか、および/または探しているかどうかではありません。

したがって、最初の原則に戻ると、例外を回避するための根本的な理由は、コストのトレードオフです。つまり、イベントを明示的にテストするコストと、例外をスロー、キャッチ、および処理するコストです。正確には。

発生確率を P とすると

  • 例外を使用する平均コストは次のとおりです。

    P * 作成/スロー/キャッチ/処理される例外のコスト + (1 - P) * 明示的なテストがない場合のコスト

  • 例外を使用しない場合の平均コストは次のとおりです。

    P * 条件が発生した場合のテストとエラー処理のコスト + (1 - P) * 条件が発生しない場合のテストのコスト。

そしてもちろん、これが「例外的」==「可能性が低い」の出番です。なぜなら、P が 0 に近づくにつれて、例外を使用するオーバーヘッドがますます重要ではなくなるからです。また、P が (問題によっては) 十分に小さい場合、例外はより効率的になります。


したがって、元の質問への回答として、単純に if/else 対例外のコストではありません。また、テスト対象のイベント (エラー)の可能性も考慮する必要があります。

もう 1 つの注意点は、JIT コンパイラーが両方のバージョンを最適化する余地がたくさんあることです。

  • 最初のバージョンでは、部分式の計算が何度も繰り返され、裏で null チェックが繰り返される可能性があります。副作用があるかどうかによって異なりますが、JIT コンパイラーはこれの一部を最適化できる場合があります。それができない場合、一連のテストはかなり高価になる可能性があります。

  • 2 番目のバージョンでは、JIT コンパイラが、例外オブジェクトを使用せずに同じメソッドで例外がスローされ、キャッチされていることを認識できる範囲があります。例外オブジェクトは「エスケープ」しないため、(理論的には) 最適化して取り除くことができます。その場合、例外を使用するオーバーヘッドはほとんどなくなります。


(これは、私の非公式な方程式が何を意味するかを明確にするための実際の例です:

  // Version 1
  if (someTest()) {
      doIt();
  } else {
      recover();
  }

  // Version 2
  try {
      doIt();
  } catch (SomeException ex) {
      recover();
  }

前述のように、Pを例外の原因となるイベントが発生する確率とします。

バージョン #1 - テストが成功しても失敗してもコストが同じであると仮定し、someTest()「doIt-success」を使用して例外がスローされない場合の doIt のコストを表す場合、バージョン # の 1 回の実行の平均コスト1 は:

  V1 = cost("someTest") + P * cost("recover") + (1 - P) * cost("doIt-success")

doIt()バージョン #2 -例外がスローされるかどうかにかかわらずコストが同じであると仮定すると、バージョン #2 の 1 回の実行の平均コストは次のようになります。

  v2 = P * ( cost("doit-fail") + cost("throw/catch") + cost("recover") ) +
       (1 - P) * cost("doIt-success")

一方を他方から差し引いて、平均コストの差を出します。

  V1 - V2 = cost("someTest") + P * cost("recover") + 
            (1 - P) * cost("doIt-success") -
            P * cost("doit-fail") - P * cost("throw/catch") -
            P * cost("recover") - (1 - P) * cost("doIt-success")

          = cost("someTest") - P * ( cost("doit-fail") + cost("throw/catch") )

recover()のコストと成功した場合のコストがdoIt()相殺されることに注意してください。プラスの要素 (例外を回避するためにテストを実行するコスト) と、失敗の確率に比例するマイナスの要素が残ります。この式は、スロー/キャッチのオーバーヘッドがどれほど高くても、確率Pがゼロに十分近い場合、差はマイナスになることを示しています。


このコメントに応えて:

フロー制御のためにチェックされていない例外をキャッチしてはならない本当の理由は次のとおりです。呼び出すメソッドの 1 つが NPE をスローするとどうなるでしょうか。NPE をキャッチすると、それが getter の 1 つから来た可能性がある場合に、コードから来たものと見なされます。コードの下にバグを隠している可能性があり、これが大規模なデバッグの頭痛の種になる可能性があります (個人的な経験)。フロー制御用の NPE や IOOBE などの未チェックの例外をキャッチして、自分 (または他のユーザー) のコードにバグを隠している可能性がある場合、パフォーマンスの引数は役に立ちません。

これは本当にエドウィン・バックスの主張と同じです。

問題は、「フロー制御」が何を意味するかです。

  • 一方では、例外のスローとキャッチはフロー制御の一形態です。つまり、チェックされていない例外をスローしてキャッチしてはいけません。それは明らかに意味がありません。

  • そのため、さまざまな種類のフロー制御についての議論に戻ります。これは、何が「例外的」であり、何が「非例外的」であるかについて議論するのと同じです。

NPE や類似のものをキャッチするときは、予期しないソース (つまり、別のバグ) から発生したものをキャッチしないように注意する必要があることを認識しています。しかし、OPの例では、そのリスクは最小限です。また、単純な getter のように見えるものが実際には単純な getter であることを確認できますし、確認する必要があります。

ifまた、NPE (この場合) をキャッチするとコードが単純になり、ステートメント内の条件の長いシーケンスよりも信頼性が高くなることも認識しておく必要があります。この「パターン」は多くの場所で複製される可能性があることに注意してください。

肝心なのは、例外とテストの選択は複雑になる可能性があるということです。常にテストを使用するように指示する単純なマントラは、状況によっては間違ったソリューションを提供することになります。そして、「間違い」は、信頼性が低く、読みにくく、コードが遅くなる可能性があります。

于 2012-09-11T03:13:32.623 に答える
2

他の人が述べているように、例外は if ステートメントよりもコストがかかります。ただし、あなたのケースではそれらを使用しない優れた理由があります。

例外は例外的なイベントです

メッセージをアンパックするとき、メッセージに含まれていないものはエラー チェックであり、例外的なイベントではありません。

このコード ブロックは、他のインスタンスのデータに関心がありすぎます。それらの他のインスタンスにいくつかの動作を追加します。現在、すべての動作はクラスにないコードにあり、これは不適切なオブジェクト指向です。

-- for billingRemoteService --
public boolean hasResponse();
public BillingRemoteResponse getResponse();

-- for BillingRemoteResponse --
public List<Customer> getCustomerList();

-- for Customer --
public Customer(Long ecpdId, ...) {
  if (ecpdId == null) throw new IllegalArgumentException(...);
}
于 2012-09-10T20:45:37.503 に答える
1

Groovy を JVM ベースのアプリケーションに混在させることができます。その場合、行はかなり単純になります。

def result = billingRemoteService?.
  serviceBody?.
  serviceResponse?.
  customersList?.
  customersList[0];
if ('00' != billingRemoteService?.serviceHeader?.statusCode ||
   result?.
   billAccountInfo?.
   getEcpdId == null)
  throw new WebServicesException
...
于 2012-09-10T20:50:59.140 に答える
0

基本的に、if/elseとtrycatchは同じものではありません。これらのもののパフォーマンス/コストは、コードには関係ありません。ビジネスルールを正しく設計および実装すると、パフォーマンスについて心配することができます。

try / catchは、プログラムの例外状態の例外処理用です。elseの場合は、プログラムの条件付き状態用です。

それらには独自の用途があります。必要なものを確認し、それに応じてコーディングしてください。

さらに、あなたはデメテルの法則を破っています。読みやすくするための設計とコードを検討し、保守性と拡張性のためのより良い設計にする方がよいでしょう。

于 2012-09-10T20:31:39.140 に答える
0

経験則として、例外処理はifsよりも高価ですが、予想される負荷の下で両方のバージョンをベンチマーク/プロファイリングすることが最善のアプローチであるというTheZに同意します。IO とネットワークのコストを考慮すると、その差は無視できるほど大きくなる可能性があります。一般に、CPU のコストは桁違いに推定されます。また、!00.equals条件はおそらく 2 番目のバージョンで確認する必要があることに注意してください。

于 2012-09-10T20:25:47.740 に答える
0

OK、実際に質問に答えた回答はありませんでしたが、私の現在の状況を考えると、theZの提案がこれを確認する最速の方法でした. このコードはどれも私が設計または作成したものではなく、その一部であるアプリケーションは大規模です。つまり、このようなあらゆる状況を処理するには何年ものリファクタリングが必要になることを意味します。

だから、みんなの啓発のために:

両方のメソッドに必要なクラスを模擬する簡単なテストを作成しました。私の質問とは無関係であるため、クラスの個々のメソッドが実行される時間は気にしません。また、JDK 1.6 および 1.7 でビルド/実行しました。2 つの JDK の間に実質的な違いはありませんでした。

動作する場合 --- IE どこにもヌルがない場合、平均時間は次のとおりです。

Method A (compound IF):  4ms
Method B (exceptions):   2ms

したがって、オブジェクトが null でない場合に例外を使用すると、複合 IF の 2 倍の速さになります。

get(0) ステートメントで意図的に null ポインター例外を強制すると、事態はさらに興味深いものになります。

ここでの平均は次のとおりです。

Method A: 36ms
Method B:  6ms

したがって、文書化された元のケースでは、例外がコスト面で進むべき道であることは明らかです。

于 2012-09-11T02:35:17.490 に答える