21

これらの2つの例を見てみましょう。

初め:

try {
    execute(testObj);
} catch(Exception e) {
    //do somethingwith that
}

public void execute(TestObj testObj) throws Exception {
    if (testObj == null){
        throw new Exception("No such object");
    }
    //do something with object
}

2番:

if (testObj != null){
    execute(testObj);
} else {
    //handle this differently
}

public void execute(TestObj testObj) {
    //do something with object
}

「isnull」などをチェックする必要があるかどうかは、現時点では重要ではありません。全体的にどちらの方法が優れているか知りたい–「チェックしてから実行する」または「実行してから、発生した場合に例外を処理する」?

4

11 に答える 11

29

どちらも、システム間の境界のみをチェックする必要があります。

システム間の境界とは?

  1. ユーザーからの入力を受け取ると、
  2. ファイル、ネットワーク、またはソケットを読み取るとき、
  3. システムコールやプロセス間通信を行う場合、
  4. ライブラリ コード内、すべての公開 API 内。

ライブラリ コードでは、パブリック API が外部コードから呼び出される可能性があるため、外部コードから呼び出せるものはすべて常にチェックインする必要があります。内部専用メソッドをチェックする必要はありません (アクセス修飾子が public であっても)。引数エラーは、個人的な好みに応じて、例外またはリターン コードによって通知される場合がありますが、チェック例外は無視できないため、通常、エラーを外部コードに通知する方法として推奨されます。

アプリケーション コードでは (ライブラリとは対照的に)、ユーザーからの入力を受け取るとき、URL をロードするとき、ファイルを読み取るときなどにのみチェックを行う必要があります。パブリック メソッドでは入力チェックを行う必要はありません。独自のアプリケーション コードによって呼び出されることはありません。

Within your own code, you should avoid the situation where you need to check in the first place. For example, instead of checking for null, you can use "null object pattern". If you still need to do a check though, do it as early as possible, this usually means doing it outside but try to find even earlier points.

Even if not written down formally and even if it's not enforced, all methods whether its internal or public-facing should have a contract. The difference is that in external-facing code, you should enforce the contract as part of your error checking, while in internal-only code you can and should rely on the caller knowing what to and not to pass.

In short, since checking is only done on system boundaries, if you actually have a choice of whether to check inside or outside a method, then you probably should not be checking there since that is not a system boundary.

In a system boundary, both sides always have to check. The caller had to check that the external call succeeds and the callee had to check that the caller gives a sane input/argument. Ignoring an error in the caller is almost always a bug. Robustness principle applies, always check everything you receive from external system, but only send what you know that the external system can accept for sure.

In very big projects though, it's often necessary to compartmentalize the project into small modules. In this case, the other modules in the project can be treated as external systems and you should therefore do the checks in the APIs that are exposed to the other modules in the project.

TLDR; checking is hard.

于 2013-03-01T11:38:01.537 に答える
8

@LieRyanが言ったように、システム間の境界のみをチェックする必要があります。しかし、自分のコードの内部に入った後でも、予期しない問題を検出できる必要があります。主な理由は次のとおりです。

  • あなた(およびあなたの同僚)は完璧ではなく、それを処理できないメソッドに null を渡す可能性があります。
  • 1 行で 2 つのオブジェクトを使用し、1 つが null の場合、NPE はどちらが原因かを通知しません (System.out.println ("a.getName() " + "b.getName()") は NPE をスローします... a null または b null または両方?)

どのメソッドが null をパラメーターとして受け入れるか、または null を返す可能性があるかを明確に文書化します。
Eclipse Juno を使用している場合に役立つ便利なツール (IntelliJ-Idea にも同様の機能があると思います) は、null チェック分析を有効にすることです。いくつかの注釈を記述して、コンパイル時に null チェックを行うことができます。本当にすごいです。次のようなものを書くことができます

public @NonNull String method(@Nullable String a){
//since a may be null, you need to make the check or code will not compile
    if(a != null){
        return a.toUppercase();        
    }
    return ""; //if you write return null it won't compile, 
     //because method is marked NonNull 
}

また、素敵な @NonNullByDefault もあり、基本的に「@Nullable とマークされていない限り、メソッドは null を受け入れたり返したりしません」と述べています。これは素敵なデフォルトであり、コードをクリーンで安全に保ちます。

詳細については、Eclipse のヘルプを確認してください。

于 2013-03-01T14:55:00.147 に答える
4

例外の種類によって異なります。例外が無効な引数にある場合は、メソッドでそれらを確認してください。それ以外の場合は、呼び出し元に処理を任せます。

例:

public void doSomething(MyObject arg1, MyObject arg2) throw SomeException {
   if (arg1==null || arg2==null) {
       throw new IllegalArgumentException(); //unchecked
   }
   // do something which could throw SomeException
   return;
}

呼び出し:

try {
    doSomething(arg1, arg2);
} catch (SomeException e) {
    //handle
}
于 2013-03-01T09:57:36.123 に答える
3

場合によります。どちらのアプローチでも十分です。最初は、関数が他のクライアントによって使用される場合に適しています。この場合、チェックは不正な引数による失敗を防ぎます。作成者は、予期しない不正な入力引数が発生した場合でも、コードが正常に機能することを保証します。2つ目は、関数の作成者でない場合、またはメソッドによってスローされた例外を処理したくない場合に使用できます。

于 2013-03-01T10:04:49.087 に答える
3

項目38によるとJoshua Bloch Effective Java Second Edition、メソッドの妥当性についてパラメーターをチェックするためのより良い方法です。したがって、最初のアプローチは、この状況では基本的にスローする必要があるという事実を除いて、はるかに優れています。 publicNullPointerExceptionRuntimeException

// do not need try...catch due to RuntimeException
execute(testObj);

public void execute(TestObj testObj) {
    if (testObj == null){
        throw new NullPointerException("No such object");
    }
    ;//do smth with object
}
于 2013-03-01T10:12:11.250 に答える
2

目的別:

ライブラリを作成している場合は、実行可能なコードではなく、API を提供しているだけです。ユーザーが呼び出し元からチェックを行うかどうかわからないため、堅牢性を確保するためにメソッドから例外をスローすることができます (必ずしも if ステートメントが機能するわけではありません)。

アプリケーションを作成している場合は、2 番目の方法の方がはるかに優れています。単純な条件のチェックだけに try-catch 句を使用するのは悪い習慣です。

実際、例外スロー ステートメントと if-checks は共存できるので、それをお勧めします。ただし、コードでは、可能な限り例外を発生させないようにする必要があります。


用途別:

一部の状況が予期されていないが発生する可能性がある場合、例外がスローされます。Exceptionインスタンスを評価することでスタックをトレースできるため、通常はデバッグ目的で役立ち

If 節はあらゆる場合に役立ちます。実際、throwステートメントはif節で囲まれていることがよくあります。これは、その状態に対処したくない (または対処できない) ことを意味し、デバッガー (例外マネージャー) に任せるだけです。あるいは、好きなようにコードを書くこともできます。


ほとんどの場合、すべての条件に対処する必要がありますが、発生する可能性が低いものもあります。先ほど述べたように、パフォーマンスの問題だけでなく、使いやすさのためにも、スローする例外をできるだけ少なくするようにしてください。

例外が無駄になる場合があることを示す (悪い) 例:

public void method1 () throws Exception1, Exception2 {
    if (condition) {
        throw new Exception1("condition match");
    } else {
        throw new Exception2("condition mismatch");
    }
}

public void method2 () {
    try {
        method1();
    } catch (Exception1 e1) {
        // do something
    } catch (Exception2 e2) {
        // do something
    }
}

通常、このようなコードを書くことはありませんが、ご覧のとおり、1 つのメソッドが 2 つに分割され、method1 はほとんど何もしません。最初の例と同じです。1 つのメソッドに属するコードを別の場所に分割しないでください。

于 2013-03-01T10:13:01.810 に答える
2

2 番目の方法は、最初の方法と同様に優れています。例外が発生する可能性があり、これは予期されていることだと思われます。

また、例外を使用してプログラム フローを制御すると、例外を作成するとパフォーマンスが低下するため、計算コストが高くなることに注意してください。たとえば、休止状態ではSELECT COUNT(*)、recordNotFoundException が発生するかどうかを確認し、その例外の発生に基づいてアプリケーション ロジックを作成するよりも、行が存在するかどうかを確認する方が適切です。

于 2013-03-01T09:54:28.430 に答える
2

2 番目の方法は、読みやすく、維持しやすいため、優れています。

また、例外をスローしても問題が A から B に移動するだけで、問題が解決しない場合があることにも注意してください。

于 2013-03-01T09:57:26.210 に答える
2

たとえば、データがユーザーから渡された場合、それらが適切でない場合は新しいデータを要求したり、データが適切でない場合は関数を呼び出さなかったりできるため、呼び出し元から実行することをお勧めします。したがって、メソッドを「クリーン」に保つことができ、特定のメソッドはその単純な目的の機能のみを実行します。そのため、他の領域でより簡単に転送および再利用できます。メソッドに提供されたデータが例外をスローする場合、可能です。

EGデータをファイルに書き込み、ファイル パスを入力として取得するメソッドの場合。FileNotFound例外をスローできます。

そうすれば、メソッドを使用するプログラマーは、適切なファイル パスを提供し、必要なすべてのチェックを事前に行う必要があることを知ることができます。

于 2013-03-01T09:58:24.520 に答える
1

には例外を使用する必要がありexceptional conditionsます。あなたが起こるとは思わないこと。入力の検証は、それほど例外的ではありません。

于 2013-03-01T10:00:38.050 に答える
1

可能な限り、メソッドから例外がスローされた場合に、失敗した操作に副作用がないことを確認するように努める必要があります。悲しいかな、Java や .net には、例外が「副作用のない完全な障害」であることを示すことができる標準的な手段はありませんが、予測が難しい多くのシナリオ (データ構造を逆シリアル化しようとするなど) があります。すべてのタイプの障害が発生する可能性があります (たとえば、破損したファイルをロードしようとした結果として) が、ほぼすべてのタイプの副作用のない障害が同じ方法で処理される場合があります (たとえば、ドキュメントをロードできなかったことをユーザーに通知します)。 、破損しているか、形式が間違っている可能性があります)。

メソッドが独自のコード内でのみ使用される場合でも、他のコードに悪影響を与える可能性のある操作を行う前に、パラメーターを検証することをお勧めします。たとえば、コードが遅延ソートされたリストを使用しているとします (つまり、ソートされているかどうかを示すフラグとともに、ソートされているかどうかを示すリストが保持されます)。リストにアイテムを追加するときは、単にそれを最後に追加し、「ソートが必要」フラグを設定します。「項目の追加」ルーチンは、null 参照またはリスト内の項目と比較できないオブジェクトが与えられた場合、「正常に」完了する可能性がありますが、そのような項目を追加すると、後の並べ替え操作が失敗します。「アイテムの追加」メソッドがアイテムが有効かどうかを気にする必要がない場合でも、「アイテムの追加」メソッドの方がはるかに優れています。

次に、コードのトラブルシューティング、メソッドfooがすぐに渡されてスローされるパラメーター値を受け取った場合、例外をスローするのではなく、例外をスローするbarことは多少役立つかもしれませんが、例外が本質的にすぐにスローされずにスローされるシナリオネストされたメソッドによるものであっても、副作用は、例外をスローする前にいくつかの副作用を引き起こすか、さらに悪いことに、「正常に」完了するが将来の操作が失敗するシナリオよりもはるかに重要ではありません.barfoobarfoo

于 2013-03-01T16:10:15.780 に答える