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


31

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

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

12
rethrow실제로 throw예외에 실패하면 기술적으로 접근 할 수 있습니다. (구현이 변경되면 언젠가 일어날 수 있음)
njzk2

34
따로, AssertionError는 RuntimeException보다 의미 상 더 나은 선택 일 수 있습니다.
JvR

1
마지막 던지는 중복입니다. findbugs 또는 기타 정적 분석 도구를 사용하면 이러한 유형의 행에 플래그를 지정하여 제거합니다.
Bon Ami

7
이제 코드의 나머지 부분을 보았으므로 "숙련 된 개발자"를 " 고급 개발자" 로 변경해야한다고 생각합니다 . Code Review 는 그 이유를 기꺼이 설명 할 것입니다.
JvR

3
예외가 끔찍한 이유에 대한 가상의 예를 만들려고 노력했습니다. 그러나 내가 한 일은 이것에 촛불을 들지 않습니다.
Paul Draper

답변:


36

먼저, 질문을 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주요 리팩토링없이 여기에 코드를.


1
캐스트는 쓸모가 없습니다. 인수 유형을 기반으로하는 동적 디스패치가 없으므로 필수입니다. 올바른 메소드를 호출하려면 컴파일시 인수 유형을 알아야합니다.
Joe23

당신에 logAndWrap방법 대신 그것을 반환의 예외를 던져,하지만 반환 형식을 유지할 수 있습니다 (Runtime)Exception. 이런 식으로 catch 블록은 도달 할 수없는 코드를 던지지 않고 작성한 방식으로 유지할 수 있습니다. 그러나을 잊을 수 없다는 추가 이점이 있습니다 throw. 예외를 반환하고 던지기를 잊어 버리면 예외가 자동으로 삼켜집니다. 반환 유형 RuntimeException이있는 동안 발생하면 컴파일러는 이러한 오류를 피하면서도 만족할 것입니다. 그것이 구아바 Throwables.propagate가 구현되는 방식입니다.
Joe23

@ Joe23 정적 디스패치에 대한 설명을 해주셔서 감사합니다. 함수 내에서 예외를 던지는 방법 : 설명하는 가능한 오류가 호출 logAndWrap하지만 예외가 반환되지 않은 catch 블록이라는 것을 의미 합니까? 그 흥미 롭군요. 값을 반환해야하는 함수 내에서 컴파일러는 값이 반환되지 않은 경로가 있음을 알지만 아무 것도 반환하지 않는 메서드에 유용합니다. 다시 감사합니다.
코어 덤프

1
그것이 바로 내가 언급 한 것입니다. 그리고 다시 요점을 강조하기 위해 : 그것은 내가 생각해 낸 것이 아니라 구아바 소스에서 얻은 것입니다.
Joe23

@ Joe23 라이브러리에 대한 명시적인 참조를 추가했습니다
coredump

52

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

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


1
-1 함수를 두 문장으로 나눌 것을 제안하면 잘못된 복사 / 붙여 넣기로 인해 프로그래밍 오류가 발생할 수 있습니다. 또한 본인이 답변을 게시 한 후 몇 분 동안 제안 throw reportAndTransform(e)하도록 질문을 편집 한 사실에 대해 약간 걱정하고 있습니다 . 어쩌면 당신은 당신이 그것에 대해 독립적으로 질문을 수정했을 것입니다. 그렇다면 미리 사과드립니다. 그렇지 않으면 약간의 신용을주는 것이 사람들이 일반적으로하는 일입니다.
코어 덤프

4
@coredump 나는 실제로 당신의 제안을 읽었으며,이 제안을 당신에게 귀속시키는 또 다른 대답을 읽었으며, 과거에 실제로 그러한 구성을 정확하게 사용했음을 기억했습니다. 그래서 나는 그것을 나의 대답에 포함하기로 결정했습니다. 여기에 독창적 인 아이디어에 대해 이야기하고 있지 않기 때문에 귀속을 포함시키는 것이 중요하지 않다고 생각했지만 문제가 발생하면 당신을 악화시키고 싶지 않으므로 귀속이 있습니다. 링크로 완성하십시오. 건배.
Mike Nakis

내가이 구조에 대한 특허를 가지고있는 것은 아니다. 그러나 당신이 답을 쓰고 얼마 지나지 않아 다른 답이 "동화"한다고 상상해보십시오. 따라서 귀속은 매우 높이 평가됩니다. 고맙습니다.
코어 덤프

3
아무것도 잘못있어 그 자체로 반환하지 않는 함수와 함께. 예를 들어 실시간 시스템에서 항상 발생합니다. 궁극적 인 문제는 언어가 언어의 정확성 개념을 분석하고 적용한다는 점에서 Java의 결함이지만, 언어는 어떻게 든 함수가 반환되지 않는다는 주석을 달기위한 메커니즘을 제공하지 않습니다. 그러나 이와 같은 문제를 해결하는 디자인 패턴이 있습니다 throw reportAndTransform(e). 많은 디자인 패턴에는 패치에 언어 기능이 없습니다. 이것은 그들 중 하나입니다.
David Hammen

2
반면에, 많은 현대 언어는 충분히 복잡합니다. 모든 디자인 패턴을 핵심 언어 기능으로 바꾸면 더욱 풍성해집니다. 이것은 핵심 언어에 속하지 않는 디자인 패턴의 좋은 예입니다. 작성된 것처럼 컴파일러뿐만 아니라 코드를 구문 분석 해야하는 다른 많은 도구에서도 잘 이해됩니다.
MSalters

7

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

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


네 말이 맞아 나는 throw new...줄을 주석 처리하면 어떤 일이 일어날 지 점검했으며 반환 진술이 없다고 불평합니다. 나쁜 프로그래밍입니까? 도달 할 수없는 반환 진술을 대체하는 것에 대한 협약이 있습니까? 더 많은 정보를 얻으려면 잠시 동안이 질문에 답하지 않습니다. 그럼에도 귀하의 답변에 감사드립니다.
Sok Pomaranczowy

3
@SokPomaranczowy 그래, 나쁜 프로그래밍이야. 이 rethrow()방법 catch은 예외를 다시 발생 시킨다는 사실을 숨 깁니다 . 나는 그런 일을 피할 것입니다. 물론 rethrow()실제로 예외를 다시 발생시키지 못할 수도 있습니다.이 경우 다른 일이 발생합니다.
로스 패터슨

26
예외를로 바꾸지 마십시오 return null. 예외적으로, 미래에 누군가가 코드를 깨서 마지막 줄에 도달 할 수있게되면 예외가 발생하면 즉시 명확 해집니다. 대신 return nullnull응용 프로그램에서 예상치 못한 값이 떠 있습니다. 응용 프로그램에서 어디에서 NullPointerException의지가 발생 하는지 누가 알 수 있습니까? 운이 좋으면이 함수를 호출 한 후에 중간에 있지 않으면 실제 문제를 추적하기가 매우 어려울 것입니다.
unholysampler

5
@MatthewRead 도달 할 수없는 코드의 실행은 심각한 버그이며, 이 버그에서 return null돌아 null오는 다른 경우를 구별 할 수 없기 때문에 버그를 가릴 가능성이 높습니다 .
코드 InChaos

4
또한 일부 상황에서 함수가 이미 널을 리턴하고이 상황에서 널 리턴을 사용하는 경우 사용자는 널 리턴을 얻을 때 두 가지 가능한 시나리오를 갖게됩니다. null 반환은 이미 "객체가없고 이유를 지정하지 않음"을 의미하기 때문에 문제가되지 않기 때문에 차이가 거의없는 이유를 하나 더 추가하면됩니다. 그러나 애매 모호함을 추가하는 것은 종종 좋지 않으며, 이는 버그가 즉시 "이것이 깨졌습니다"처럼 보이지 않고 "확실한 상황"처럼 취급 될 것이라는 것을 의미합니다. 일찍 실패하십시오.
Steve Jessop

6

그만큼

throw new RuntimeException("cannot reach here");

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

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


4

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

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

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

2

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

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

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

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


사람들은 아무것도 처리하지 않는 와일드 카드 캐치가 실제로 예외 처리기라고 계속 믿고 있습니다. 귀하의 버전은 할 수없는 것을 처리하지 않는 것으로 가정에 결함이 있음을 무시합니다. retrieveUserMailConfiguration ()의 호출자는 무언가를 되 찾을 것으로 예상합니다. 그 함수가 합리적인 것을 반환 할 수 없다면, 빠르고 크게 실패해야합니다. 마치 다른 답변이 문제가 catch(Exception)되는 것처럼 보이지만 코드 냄새가 심합니다.
msw

1
@msw-카고 컬트 코딩.
피트 베커

@msw-반면에 중단 점을 설정할 수있는 공간을 제공합니다.
피트 베커

1
논리의 전체 부분을 버리는 데 아무런 문제가없는 것처럼 보입니다. 귀하의 버전은 원본과 동일하게 작동하지 않으며, 이미 이미 크게 실패했습니다. 귀하의 답변에 캐치 프리 메소드를 작성하는 것을 선호하지만 기존 코드로 작업 할 때 다른 작업 파트와 계약을 위반해서는 안됩니다. 수행 된 작업 rethrow은 쓸모가 없을 수 있지만 (여기서는 요점이 아닙니다) 좋아하지 않는 코드를 제거 할 수는 없으며 명확하게 그렇지 않은 경우 원래 코드와 동일하다고 말할 수는 없습니다. 사용자 정의 rethrow기능 에는 부작용이 있습니다.
코어 덤프

1
@ jpmc26이 문제의 핵심은 앞의 try / catch 블록과는 다른 이유로 발생할 수있는 "여기에 도달 할 수 없음"예외에 관한 것입니다. 그러나 나는 그 블록이 비린내가 나는 냄새에 동의하고,이 대답이 원래 조심스럽게 쓰여졌다면 훨씬 더 많은 찬사를 얻었을 것입니다. 마지막 편집이 좋습니다.
coredump
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.