web-dev-qa-db-ja.com

到達不可能なコードで新しいRuntimeExceptionをスローするのは悪いスタイルですか?

私は、より熟練した開発者によって少し前に書かれたアプリケーションを保守するように割り当てられました。私はこのコードの断片に出くわしました:

_public Configuration retrieveUserMailConfiguration(Long id) throws MailException {
        try {
            return translate(mailManagementService.retrieveUserMailConfiguration(id));
        } catch (Exception e) {
            rethrow(e);
        }
        throw new RuntimeException("cannot reach here");
    }
_

RuntimeException("cannot reach here")のスローが正当化されるかどうか、私は興味があります。このコードがより熟練した同僚からのものであることを知っているので、おそらく何か明らかなことを見逃しているでしょう。
編集:いくつかの回答が参照した本文を再スローします。この質問ではそれは重要ではないと思いました。

_private void rethrow(Exception e) throws MailException {
        if (e instanceof InvalidDataException) {
            InvalidDataException ex = (InvalidDataException) e;
            rethrow(ex);
        }
        if (e instanceof EntityAlreadyExistsException) {
            EntityAlreadyExistsException ex = (EntityAlreadyExistsException) e;
            rethrow(ex);
        }
        if (e instanceof EntityNotFoundException) {
            EntityNotFoundException ex = (EntityNotFoundException) e;
            rethrow(ex);
        }
        if (e instanceof NoPermissionException) {
            NoPermissionException ex = (NoPermissionException) e;
            rethrow(ex);
        }
        if (e instanceof ServiceUnavailableException) {
            ServiceUnavailableException ex = (ServiceUnavailableException) e;
            rethrow(ex);
        }
        LOG.error("internal error, original exception", e);
        throw new MailUnexpectedException();
    }


private void rethrow(ServiceUnavailableException e) throws
            MailServiceUnavailableException {
        throw new MailServiceUnavailableException();
    }

private void rethrow(NoPermissionException e) throws PersonNotAuthorizedException {
    throw new PersonNotAuthorizedException();
}

private void rethrow(InvalidDataException e) throws
        MailInvalidIdException, MailLoginNotAvailableException,
        MailInvalidLoginException, MailInvalidPasswordException,
        MailInvalidEmailException {
    switch (e.getDetail()) {
        case ID_INVALID:
            throw new MailInvalidIdException();
        case LOGIN_INVALID:
            throw new MailInvalidLoginException();
        case LOGIN_NOT_ALLOWED:
            throw new MailLoginNotAvailableException();
        case PASSWORD_INVALID:
            throw new MailInvalidPasswordException();
        case EMAIL_INVALID:
            throw new MailInvalidEmailException();
    }
}

private void rethrow(EntityAlreadyExistsException e)
        throws MailLoginNotAvailableException, MailEmailAddressAlreadyForwardedToException {
    switch (e.getDetail()) {
        case LOGIN_ALREADY_TAKEN:
            throw new MailLoginNotAvailableException();
        case EMAIL_ADDRESS_ALREADY_FORWARDED_TO:
            throw new MailEmailAddressAlreadyForwardedToException();
    }
}

private void rethrow(EntityNotFoundException e) throws
        MailAccountNotCreatedException,
        MailAliasNotCreatedException {
    switch (e.getDetail()) {
        case ACCOUNT_NOT_FOUND:
            throw new MailAccountNotCreatedException();
        case ALIAS_NOT_FOUND:
            throw new MailAliasNotCreatedException();
    }
}
_
31

まず、あなたの質問をudpatし、rethrowが何をするかを示してくれてありがとう。つまり、実際には、プロパティを持つ例外をより詳細な例外クラスの例外に変換します。これについては後で詳しく説明します。

私はもともとメインの質問に実際には答えていなかったので、ここに行きます:はい、到達できないコードでランタイム例外をスローすることは一般的に悪いスタイルです。アサーションを使用するか、問題を回避する方がよいでしょう。すでに指摘したように、ここのコンパイラーは、コードが_try/catch_ブロックの外に出ないことを確認できません。あなたはそれを利用することでコードをリファクタリングすることができます...

エラーは値です

(当然のことながら、 それはgo でよく知られています)

編集前に使用した簡単な例を使用してみましょう。何かをログに記録し、 Konradの答え のようなラッパー例外を作成していると想像してください。それをlogAndWrapと呼びましょう。

logAndWrapの副作用として例外をスローする代わりに、副作用として機能させ、例外(少なくとも、入力で指定されたもの)を返すようにすることができます。ジェネリックを使用する必要はなく、基本的な関数だけを使用します。

_private Exception logAndWrap(Exception exception) {
    // or whatever it actually does
    Log.e("Ouch! " + exception.getMessage());
    return new CustomWrapperException(exception);
}
_

次に、明示的にthrowを実行すると、コンパイラは満足します。

_try {
     return translate(mailManagementService.retrieveUserMailConfiguration(id));
} catch (Exception e) {
     throw logAndWrap(e);
}
_

throwを忘れた場合はどうなりますか?

Joe23のコメント で説明されているように、例外が常にスローされることを保証する 防御的プログラミング 方法は、明示的にthrow new CustomWrapperException(exception)Guava.Throwables によって実行されるため、logAndWrapの末尾にあります。このようにして、例外がスローされることがわかり、型アナライザーは満足します。ただし、カスタム例外は未チェックの例外である必要がありますが、これは常に可能とは限りません。また、開発者がthrowを書き込めないというリスクは非常に低いと評価します。開発者はそれを忘れなければならず周囲のメソッドは返すべきではありませんそれ以外の場合、コンパイラーは欠落した戻りを検出します。これは型システムと戦う興味深い方法ですが、機能します。

再スロー

実際のrethrowも関数として記述できますが、現在の実装には問題があります。

  • のような無駄なキャストがたくさんあります 実際にはキャストが必要です(コメントを参照):

    _if (e instanceof ServiceUnavailableException) {
        ServiceUnavailableException ex = (ServiceUnavailableException) e;
        rethrow(ex);
    }
    _
  • 新しい例外をスローまたは返す場合、古い例外は破棄されます。次のコードでは、MailLoginNotAvailableExceptionではどのログインが使用できないかを知ることができず、不便です。さらに、スタックトレースは不完全になります:

    _private void rethrow(EntityAlreadyExistsException e)
        throws MailLoginNotAvailableException, MailEmailAddressAlreadyForwardedToException {
        switch (e.getDetail()) {
            case LOGIN_ALREADY_TAKEN:
                throw new MailLoginNotAvailableException();
            case EMAIL_ADDRESS_ALREADY_FORWARDED_TO:
                throw new MailEmailAddressAlreadyForwardedToException();
        }
    }
    _
  • 元のコードが最初にこれらの特殊な例外をスローしないのはなぜですか? rethrowは、(メーリング)サブシステムとbusineessロジックの間の互換性レイヤーとして使用されていると思います(おそらく、スローされた例外などの実装の詳細を、カスタム例外で置き換えることによって非表示にすることを意図しています)。 ピートベッカーの回答 で示唆されているように、キャッチフリーコードを使用する方がよいと同意しても、catchを削除する機会はないと思いますここでは、rethrowコードに大きなリファクタリングはありません。

36
coredump

このrethrow(e);関数は、通常の状況下では関数が戻るのに対し、例外的な状況下では関数が例外をスローするという原則に違反しています。この関数は、通常の状況で例外をスローすることにより、この原則に違反しています。それがすべての混乱の原因です。

コンパイラーは、この関数が通常の状況で戻ると想定しているため、コンパイラーが認識できる限り、実行はretrieveUserMailConfiguration関数の終わりに到達する可能性があり、その時点でreturnステートメント。スローされたRuntimeExceptionは、コンパイラーのこの懸念を軽減することになっていますが、これはかなり不格好な方法です。 _function must return a value_エラーを防止する別の方法は、_return null; //to keep the compiler happy_ステートメントを追加することですが、私の意見では同じように扱いにくいです。

だから、個人的には、これを置き換えます:

_rethrow(e);
_

これとともに:

_report(e); 
throw e;
_

または、より良い方法として、( coredumpの推奨 、)とこれを使用します。

_throw reportAndTransform(e);
_

したがって、制御の流れはコンパイラに明らかになり、コンパイラによって到達不能コードとしてフラグが付けられるため、最終的なthrow new RuntimeException("cannot reach here");は冗長になるだけでなく、実際にはコンパイルできなくなります。

これは、この醜い状況から抜け出すための最もエレガントで実際に最も簡単な方法です。

52
Mike Nakis

throwはおそらく「メソッドは値を返す必要があります」エラーを回避するために追加された-Javaデータフローアナライザーはreturnthrowの後に必要ですが、カスタムrethrow()メソッドの後には必要ありません。これを修正するために使用できる@NoReturnアノテーションはありません。

それにもかかわらず、到達できないコードで新しい例外を作成することは不必要に思えます。私は単にreturn nullと書くだけで、実際には発生しないことを知っています。

7
Kilian Foth

_throw new RuntimeException("cannot reach here");
_

ステートメントは[〜#〜] person [〜#〜]何が起こっているかをコードに読み取らせるため、たとえばnullを返すよりもはるかに優れています。また、コードが予期しない方法で変更された場合のデバッグが容易になります。

ただし、rethrow(e)は間違っているようです。だからあなたの場合私はコードをリファクタリングする方が良いオプションだと思います。コードを整理する方法については、他の回答(コアダンプが一番好きです)を参照してください。

6
Ian

慣習があるかどうかわかりません。

とにかく、別のトリックはそうすることです:

private <T> T rethrow(Exception exception) {
    // or whatever it actually does
    Log.e("Ouch! " + exception.getMessage());
    throw new CustomWrapperException(exception);
}

これを可能にする:

try {
     return translate(mailManagementService.retrieveUserMailConfiguration(id));
} catch (Exception e) {
     return rethrow(e);
}

そして、もう人工的なRuntimeExceptionは必要ありません。 rethrowが実際に値を返すことは決してありませんが、コンパイラーにとっては十分です。

理論的には値(メソッドシグネチャ)を返しますが、代わりに例外をスローするため、実際にはそうではありません。

ええ、奇妙に見えるかもしれませんが、再び-ファントムRuntimeExceptionを投げるか、この世界では決して見られないnullを返します。

読みやすくするために、rethrowの名前を変更して、次のようにすることができます。

} catch (Exception e) {
     return nothingJustRethrow(e);
}
4
Konrad Morawski

tryブロックを完全に削除すると、rethrowthrowは必要なくなります。このコードは、元のコードとまったく同じです。

_public Configuration retrieveUserMailConfiguration(Long id) throws MailException {
    return translate(mailManagementService.retrieveUserMailConfiguration(id));
}
_

より熟練した開発者から来ているという事実に騙されないでください。これはコードの腐敗であり、常に発生します。修正するだけです。

編集:eを単に再スローするだけで、rethrow(e)を誤って読みました。そのrethrowメソッドが実際に例外を再スローする以外のことを行う場合、それを取り除くと、このメソッドのセマンティクスが変更されます。

2
Pete Becker