도달 할 수없는 코드에서 새로운 RuntimeExceptions을 던지는 것이 나쁜 스타일입니까?

숙련 된 개발자가 얼마 전에 작성한 응용 프로그램을 유지 관리하도록 배정되었습니다. 이 코드를 보았습니다.

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();
    }
}


답변

먼저, 질문을 udpating하고 우리에게 무엇을 보여 주셔서 감사합니다 rethrow. 실제로, 속성이있는 예외를보다 세분화 된 예외 클래스로 변환하는 것이 실제로 수행됩니다. 이것에 대해서는 나중에 더 설명하겠습니다.

원래 주요 질문에 실제로 대답하지 않았으므로 다음과 같이 진행됩니다. 그렇습니다. 일반적으로 도달 할 수없는 코드에서 런타임 예외를 발생시키는 것은 나쁜 스타일입니다. 어설 션을 더 잘 사용하거나 문제를 피하는 것이 좋습니다. 이미 지적했듯이, 여기의 컴파일러는 코드가 try/catch블록 밖으로 나가지 않을 것이라고 확신 할 수 없습니다 . 당신은 그것을 활용하여 코드를 리팩터링 할 수 있습니다 …

오류는 가치이다

(의외로, 그것은 이동 중에 잘 알려져 있습니다 )

편집하기 전에 사용한 간단한 예제를 사용하겠습니다 . 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)끝에 logAndWrap이 수행 될 때, Guava.Throwables . 그렇게하면 예외가 발생하고 타입 분석기가 만족 스럽다는 것을 알게됩니다. 그러나 사용자 정의 예외는 검사되지 않은 예외 여야하므로 항상 가능한 것은 아닙니다. 또한, 나는 쓰기에 누락 developper의 위험을 평가할 것입니다 throw매우 낮은 것으로 : 개발자가 잊어야 주변 방법은 아무것도 반환하지한다, 그렇지 않으면 컴파일러가 누락 된 수익을 감지한다. 이것은 타입 시스템과 싸우는 흥미로운 방법이며 작동합니다.

다시 던지다

실제 rethrow도 함수로 작성할 수 있지만 현재 구현에 문제가 있습니다.

  • 쓸모없는 캐스트가 많이 있습니다 실제로 캐스트가 필요합니다 (의견 참조).

    if (e instanceof ServiceUnavailableException) {
        ServiceUnavailableException ex = (ServiceUnavailableException) e;
        rethrow(ex);
    }
  • 새로운 예외를 던지거나 되돌릴 때, 오래된 예외는 버려집니다. 다음 코드에서 a 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이것이 (메일 링) 서브 시스템과 비즈니스 로직 사이의 호환성 계층으로 사용 된다고 생각합니다 (아마도 예외를 사용자 정의 예외로 대체하여 예외를 던지는 것과 같은 구현 세부 사항을 숨기려는 것일 수도 있습니다). 나는 동의하더라도 캐치없는 코드를 가지고 피트 베커의 대답에 제안이 더 나은 것 , 나는 당신이 제거 할 수있는 기회해야합니다 생각하지 않습니다 catchrethrow주요 리팩토링없이 여기에 코드를.


답변

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");는 중복 될뿐만 아니라 컴파일러에 의해 도달 할 수없는 코드로 표시되기 때문에 실제로 컴파일 할 수 없게됩니다.

그것은이 추악한 상황에서 벗어나는 가장 우아하고 실제로 가장 간단한 방법입니다.


답변

throw아마 그렇지 않으면 발생할 수있는 오류 “값을 반환해야하는 방법”절을 해결하기 위해 추가되었습니다 – 자바 데이터 분석이 더는 것을 이해하지 스마트 충분히 흘러 return후 필요가 throw있지만 사용자 정의 후에 rethrow()방법, 어떤이없는 @NoReturn주석이 이 문제를 해결하는 데 사용할 수 있습니다.

그럼에도 불구하고 도달 할 수없는 코드에서 새로운 예외를 만드는 것은 불필요한 것처럼 보입니다. 나는 return null실제로는 결코 일어나지 않는다는 것을 알고 간단하게 글을 쓸 것입니다.


답변

그만큼

throw new RuntimeException("cannot reach here");

statement는 PERSON 에게 진행중인 코드를 명확하게 알려주므로 null을 반환하는 것보다 훨씬 좋습니다. 또한 코드가 예상치 못한 방식으로 변경되면 디버그하기가 더 쉽습니다.

그러나 rethrow(e)그냥 잘못 보인다! 따라서 귀하의 경우 코드 리팩토링이 더 나은 옵션이라고 생각합니다. 코드를 정렬하는 방법은 다른 답변 (coredump가 가장 좋습니다)을 참조하십시오.


답변

컨벤션이 있는지 모르겠습니다.

어쨌든, 또 다른 트릭은 그렇게하는 것입니다 :

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실제로 어떤 값도 반환 하지는 않지만 이제는 컴파일러에 충분합니다.

이론적으로 값을 반환하고 (메소드 서명) 대신 예외를 throw하므로 실제로 그렇게하지 않습니다.

그래, 이상하게 보일지 모르지만 다시 팬텀을 던지 RuntimeException거나이 세상에서 볼 수없는 null을 반환하면 아름다움도 아닙니다.

가독성을 높이기 위해 이름을 바꾸고 rethrow다음과 같은 것을 가질 수 있습니다.

} catch (Exception e) {
     return nothingJustRethrow(e);
}

답변

당신이 제거하면 try완전히 블록을 당신은 필요하지 않습니다 rethrow또는를 throw. 이 코드는 원본과 정확히 동일한 기능을 수행합니다.

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

더 노련한 개발자가 제공한다는 사실을 속이지 마십시오. 이것은 코드 썩음이며 항상 발생합니다. 그냥 고쳐

편집 : 나는 rethrow(e)단순히 예외를 다시 던지는 것으로 잘못 읽었습니다 e. 해당 rethrow메소드가 실제로 예외를 다시 발생시키는 것 이외의 작업을 수행 하는 경우 해당 메소드를 제거하면이 메소드의 의미가 변경됩니다.