예외를 디자인하는 방법


11

나는 매우 간단한 질문으로 고심하고 있습니다.

이제 서버 응용 프로그램을 만들고 있는데 예외에 대한 계층 구조를 만들어야합니다 (일부 예외는 이미 존재하지만 일반 프레임 워크가 필요합니다). 이 작업을 어떻게 시작합니까?

이 전략을 따르는 것을 생각하고 있습니다.

1) 무엇이 잘못 되었습니까?

  • 허용되지 않는 것이 있습니다.
  • 잘못된 매개 변수로 인해 무언가가 요청되고 허용되지만 작동하지 않습니다.
  • 내부 오류로 인해 무언가가 요청되고 허용되지만 작동하지 않습니다.

2) 누가 요청을 시작합니까?

  • 클라이언트 애플리케이션
  • 다른 서버 응용 프로그램

3) 메시지 전달 : 우리는 서버 응용 프로그램을 다루면서 메시지를주고받는 것에 관한 것입니다. 메시지를 잘못 보내면 어떻게 될까요?

따라서 다음과 같은 예외 유형이 발생할 수 있습니다.

  • ServerNotAllowedException
  • ClientNotAllowedException
  • ServerParameterException
  • ClientParameterException
  • InternalException (서버가 요청의 출처를 모르는 경우)
    • ServerInternalException
    • ClientInternalException
  • MessageHandlingException

이것은 예외 계층 구조를 정의하는 매우 일반적인 접근 방법이지만 명확한 사례가 없을 수도 있습니다. 내가 다루지 않은 영역에 대한 아이디어가 있거나,이 방법의 단점을 알고 있거나, 이런 종류의 질문에 대한보다 일반적인 접근 방법이 있습니까 (후자의 경우 어디서 찾을 수 있습니까)?

미리 감사드립니다


5
예외 클래스 계층 구조로 달성하려는 것을 언급하지 않았습니다 (그리고 전혀 명확하지는 않습니다). 의미있는 로깅? 고객이 다른 예외에 합리적으로 대응할 수있게 하시겠습니까? 또는 무엇을?
Ralf Kleberhoff

2
일부 사례와 사용 사례를 먼저 살펴보고 무엇이 나오는지 확인하는 것이 좋습니다. 예를 들어 , 클라이언트 요청 X 는 허용되지 않습니다. 클라이언트가 X를 요청 했지만 요청이 유효하지 않습니다. 예외를 처리해야하는 사람, 예외를 통해 수행 할 수있는 작업 (프롬프트, 재시도 등)과이를 수행하는 데 필요한 정보에 대해 생각하십시오. 그런 다음 구체적인 예외가 무엇인지, 핸들러가 처리해야하는 정보를 알고 나면 멋진 계층 구조로 만들 수 있습니다.
쓸모없는

1
관련성이 있다고 생각합니다. softwareengineering.stackexchange.com/questions/278949/…
Martin Ba

1
나는 많은 다른 예외 유형을 사용하려는 열망을 실제로 이해하지 못했습니다. 일반적으로 사용하는 대부분의 catch블록에 대해 포함 된 오류 메시지보다 예외에 더 많이 사용하지 않습니다. 나는 파일을 읽는 과정에서 메모리를 할당하지 못하는 것으로 파일을 읽지 못하는 것과 관련된 예외에 대해 실제로 할 수있는 다른 것을 가지고 있지 않기 std::exception때문에 포함 된 오류 메시지를 포착 하고보고하는 경향이 있습니다. 그것에 "Failed to open file: %s", ex.what()인쇄하기 전에 스택 버퍼.

3
게다가 처음에 발생할 모든 예외 유형을 예상 할 수는 없습니다. 나는 지금 그것들을 예상 할 수 있지만, 동료들은 미래에 새로운 것들을 소개 할 것이다. 예를 들어, 지금 그리고 영원히, 과정에서 발생할 수있는 모든 다른 유형의 예외를 알고있는 것은 희망이 없다. 조작. 그래서 나는 단지 하나의 캐치 블록으로 일반적으로 슈퍼를 잡습니다. catch단일 복구 사이트에서 여러 블록을 사용하는 사람들의 예를 보았지만 종종 예외 내부의 메시지를 무시하고보다 현지화 된 메시지를 인쇄하는 것입니다.

답변:


5

총론

(약간의 의견 편견)

나는 일반적으로 자세한 예외 계층 구조를 가지 않을 것입니다.

가장 중요한 것은 예외는 호출자가 메소드가 작업을 완료하지 못했음을 알려줍니다. 그리고 당신의 호출자 그것에 대해 통지를 받아야합니다 . 그래서 그는 단순히 계속하지 않습니다. 어떤 예외 클래스를 선택하든 관계없이 모든 예외에서 작동합니다.

두 번째 측면은 로깅입니다. 문제가 발생할 때마다 의미있는 로그 항목을 찾으려고합니다. 또한 다른 예외 클래스가 필요없고 잘 디자인 된 문자 메시지 만 필요합니다 (오류 로그를 읽는 데 자동 매트가 필요하지 않다고 가정합니다 ...).

세 번째 측면은 발신자의 반응입니다. 발신자가 예외를 받으면 어떻게 할 수 있습니까? 여기서 다른 예외 클래스를 갖는 것이 합리적이므로 호출자는 동일한 호출을 재 시도할지, 다른 솔루션을 사용하는지 (예를 들어 대체 소스 사용) 포기할지를 결정할 수 있습니다.

그리고 최종 사용자에게 문제를 알리기위한 기반으로 예외를 사용하고 싶을 수도 있습니다. 즉, 로그 파일에 대한 관리 텍스트 외에 사용자에게 친숙한 메시지를 작성하지만 다른 예외 클래스는 필요하지 않습니다 (단, 텍스트 생성이 더 쉬워 질 수 있습니다).

로깅 (및 사용자 오류 메시지)의 중요한 측면은 일부 계층에서 예외를 포착하고 컨텍스트 정보 (예 : 메소드 매개 변수)를 추가 한 후이를 다시 던져서 컨텍스트 정보를 사용하여 예외를 수정하는 기능입니다.

당신의 계층

누가 요청을 시작합니까? 요청을 시작한 정보가 필요하다고 생각하지 않습니다. 나는 당신이 어떻게 전화 스택 내부의 깊은 곳을 알고 있는지 상상조차 할 수 없습니다.

메시지 처리 : 그것은 다른 측면이 아니라 "무슨 일이 있습니까?"에 대한 추가 사례입니다.

주석에서 예외를 만들 때 " no logging "플래그 에 대해 이야기 합니다. 나는 예외를 만들고 던지는 곳에서 그 예외를 기록할지 여부를 신뢰할 수있는 결정을 내릴 수 있다고 생각하지 않습니다.

내가 상상할 수있는 유일한 상황은 일부 상위 계층이 때로는 예외를 생성하는 방식으로 API를 사용한다는 것입니다.이 계층은 예외가있는 관리자를 방해 할 필요가 없다는 것을 알고 있으므로 예외를 자동으로 삼습니다. 그러나 그것은 코드 냄새입니다. 예상되는 예외는 그 자체로 모순이며 API를 변경하는 힌트입니다. 예외 생성 코드가 아니라 결정해야하는 상위 계층입니다.


내 경험상 사용자 친화적 인 텍스트와 함께 오류 코드가 잘 작동합니다. 관리자는 오류 코드를 사용하여 추가 정보를 찾을 수 있습니다.
Sjoerd

1
나는 일반적 으로이 답변 뒤에 아이디어를 좋아합니다. 내 경험상 예외가 너무 복잡한 짐승으로 변해서는 안됩니다. 예외의 주요 목적은 호출 코드가 특정 문제를 해결하고 함수의 응답을 방해하지 않고 관련 디버깅 / 재시도 정보를 얻을 수 있도록하는 것입니다.
greggle138

2

오류 응답 패턴을 설계 할 때 명심해야 할 것은 호출자에게 유용한 지 확인하는 것입니다. 이는 예외를 사용하든 정의 된 오류 코드를 사용하든 적용되지만 예외로 설명하는 것으로 제한됩니다.

  • 언어 나 프레임 워크가 이미 일반 예외 클래스를 제공하는 경우 적절한 예외 클래스 와 합리적으로 예상되는 위치에 클래스를 사용하십시오 . 자신 만의 클래스 ArgumentNullExceptionArgumentOutOfRange예외 클래스를 정의하지 마십시오 . 발신자들은이를 잡을 것으로 기대하지 않습니다.

  • MyClientServerAppException애플리케이션 컨텍스트 내에서 고유 한 오류를 포함하도록 기본 클래스를 정의하십시오 . 기본 클래스의 인스턴스를 버리지 마십시오 . 모호한 오류 응답은 최악의 것 입니다. "내부 오류"가 있으면 해당 오류가 무엇인지 설명하십시오.

  • 대부분의 경우 기본 클래스 아래의 계층 구조는 깊지 않고 넓어야합니다. 발신자에게 유용한 상황에서만 심화하면됩니다. 예를 들어, 메시지가 클라이언트에서 서버로 실패 할 수있는 5 가지 이유가있는 ServerMessageFault경우 예외를 정의한 다음 그 아래 5 개 오류 각각에 대해 예외 클래스를 정의 할 수 있습니다. 그렇게하면, 호출자는 필요하거나 원한다면 수퍼 클래스를 잡을 수 있습니다. 구체적이고 합리적인 경우로 제한하십시오.

  • 실제로 사용되기 전에 모든 예외 클래스를 정의하려고 시도하지 마십시오. 대부분의 작업을 다시 수행 할 수 있습니다. 코드를 작성하는 동안 오류가 발생하면 해당 오류를 가장 잘 설명하는 방법을 결정하십시오. 이상적으로는 호출자가 수행하려는 작업의 맥락에서 표현되어야합니다.

  • 이전 요점과 관련하여, 예외를 사용하여 오류에 응답한다고해서 오류 상태에 대해 예외 사용해야한다는 것을 의미하지는 않습니다 . 예외를 던지는 것은 일반적으로 비싸고 성능 비용은 언어마다 다를 수 있습니다. 일부 언어의 경우 호출 스택의 깊이에 따라 비용이 높아 지므로 호출 스택 내에 오류가있는 경우 간단한 기본 유형 (정수 오류 코드 또는 부울 플래그)을 사용하여 푸시 할 수 없는지 확인하십시오. 오류는 스택을 백업하므로 호출자의 호출에 더 가깝게 던져 질 수 있습니다.

  • 오류 응답의 일부로 로깅을 포함하는 경우 호출자가 컨텍스트 정보를 예외 객체에 쉽게 추가 할 수 있습니다. 로깅 코드에서 정보가 사용되는 곳에서 시작하십시오. 로그를 유용하게 사용하기 위해 필요한 정보의 양을 결정하십시오 (거대한 텍스트 벽없이). 그런 다음 뒤로 돌아가서 예외 클래스에 해당 정보를 쉽게 제공 할 수 있도록하십시오.

마지막으로, 응용 프로그램 에서 메모리 부족 오류를 완벽하게 처리 할 수 없다면 오류나 기타 치명적인 런타임 예외를 처리하려고하지 마십시오. 실제로 OS가 할 있는 전부이기 때문에 OS가 처리하도록하십시오 .


2
두 번째부터 마지막까지의 총알을 제외하고 (예외 비용에 대한) 대답은 좋습니다. 예외 수준에 대한 예외를 구현하는 모든 일반적인 방법에서 예외를 던지는 비용은 호출 스택의 깊이와 완전히 독립적이기 때문에 예외 비용에 대한 그 불릿은 오해의 소지가 있습니다. 오류를 즉시 호출자가 처리하지만 예외를 발생시키기 전에 호출 스택에서 몇 가지 함수를 가져 오지 않는 것을 알고 있으면 대체 오류보고 방법이 더 나을 수 있습니다.
Bart van Ingen Schenau

@BartvanIngenSchenau : 나는 이것을 특정 언어로 묶지 않으려 고 노력했다. 일부 언어 ( : Java )에서 호출 스택의 깊이는 인스턴스화 비용에 영향을줍니다. 잘리지 않고 건조되지 않았 음을 반영하도록 편집하겠습니다.
Mark Benningfield

0

우선 Exception응용 프로그램에서 발생할 수있는 모든 확인 된 예외에 대한 기본 클래스를 만드는 것이 가장 좋습니다 . 응용 프로그램이 호출 된 경우 DuckType, 다음 수 있도록 DuckTypeException기본 클래스를.

이를 통해 기본 DuckTypeException클래스의 예외 를 처리 할 수 있습니다 . 여기서 예외는 문제의 유형을 더 잘 강조 할 수있는 설명적인 이름으로 분기되어야합니다. 예를 들어 "DatabaseConnectionException"입니다.

프로그램에서 정상적으로 처리하고 싶을 수있는 상황에 대해서는 예외 사항을 모두 확인해야합니다. 즉, 데이터베이스에 연결할 수 없으므로 DatabaseConnectionException일정 시간이 지난 후 대기하고 재 시도 할 수있는 a 가 발생합니다.

유효하지 않은 SQL 쿼리 또는 널 포인터 예외와 같이 예상치 못한 문제에 대해 확인 된 예외 가 표시 되지 않으므로 주 예외에 도달 할 때까지 이러한 예외가 대부분의 catch 절을 넘어가도록 (또는 필요에 따라 잡히고 다시 던지도록) 권장합니다. 컨트롤러 자체 RuntimeException로 로깅 목적으로 만 사용할 수 있습니다 .

내 개인적인 취향은 RuntimeException확인되지 않은 예외의 본질에 따라, 예상하지 못한 다른 예외로 다시 던지는 것이 정보를 숨기고 있기 때문에 확인되지 않은 다른 예외로 다시 던지지 않는 것입니다. 즉 선호하는 경우 그러나, 당신은 여전히 잡을 수 RuntimeException와 슬로우 DuckTypeInternalException하는 달리 DuckTypeException에서 파생를 RuntimeException따라서 선택하지 않은 것입니다.

원하는 경우 DatabaseException데이터베이스 관련 과 같은 조직적 목적으로 예외를 하위 범주로 분류 할 수 있지만 여전히 하위 예외가 기본 예외 DuckTypeException에서 파생되어 추상적이어서 명시적인 설명 이름으로 파생되도록 권장 합니다.

일반적으로 예외 처리를 위해 호출자의 콜 스택을 위로 이동함에 따라 시도 어획이 점점 일반화되어야하며, 다시 메인 컨트롤러에서 확인 된 모든 예외가 파생되는 DatabaseConnectionException단순성 DuckTypeException을 처리하지 않을 것 입니다.


2
질문에는 "C ++"태그가 붙어 있습니다.
Martin Ba

0

그것을 단순화하십시오.

다른 전략으로 생각하는 데 도움이되는 첫 번째 사항은 많은 예외를 포착하는 것이 Java에서 확인 된 예외를 사용하는 것과 매우 유사합니다 (죄송합니다, C ++ 개발자는 아닙니다). 이것은 여러 가지 이유로 좋지 않기 때문에 항상 사용하지 않으려 고 노력하며 계층 구조 예외 전략에서 저를 많이 기억합니다.

따라서 확인되지 않은 예외 및 코드 오류를 사용하십시오.

예를 들어 다음 Java 코드를 참조하십시오.

public class SystemErrorCode implements ErrorCode {

    INVALID_NAME(101),
    ORDER_NOT_FOUND(102),
    PARAMETER_NOT_FOUND(103),
    VALUE_TOO_SHORT(104);

    private final int number;

    private ErrorCode(int number) {
        this.number = number;
    }

    @Override
    public int getNumber() {
        return number;
    }
}

그리고 당신의 독특한 예외 :

public class SystemException extends RuntimeException {

    private ErrorCode errorCode;

    public SystemException(ErrorCode errorCode) {
        this.errorCode = errorCode;
    }

}

링크 에서 찾은이 전략 은 위의 코드가 단순화 되었기 때문에 여기 에서 Java 구현을 찾을 수 있습니다.

"클라이언트"와 "다른 서버"응용 프로그램간에 서로 다른 예외를 구분해야하므로 ErrorCode 인터페이스를 구현하는 여러 개의 오류 코드 클래스가있을 수 있습니다.


2
질문에는 "C ++"태그가 붙어 있습니다.
Sjoerd

아이디어를 수정하기 위해 편집했습니다.
Dherik

0

예외는 제한이 없으며주의해서 사용해야합니다. 그들에게 가장 좋은 전략은 그들을 제한하는 것입니다. 호출 함수는 호출하는 함수 또는 프로그램이 죽는 모든 예외를 처리해야합니다. 호출 함수 만이 예외 처리를위한 올바른 컨텍스트를 갖습니다. 콜 트리를 처리하는 기능을 추가하면 제한이 없습니다.

예외는 오류가 아닙니다. 이러한 상황은 프로그램이 코드의 한 분기를 완료하지 못하고 따라야 할 다른 분기를 나타내는 경우에 발생합니다.

호출 된 함수와 관련하여 예외가 있어야합니다. 예를 들어 2 차 방정식 을 푸는 함수입니다 . division_by_zero 및 square_root_of_negative_number의 두 가지 예외를 처리해야합니다. 그러나 이러한 예외는이 2 차 방정식 솔버를 호출하는 함수에는 적합하지 않습니다. 그것들은 방정식을 풀고 단순히 다시 던지기 위해 사용되는 방법으로 인해 내부를 노출시키고 캡슐화를 중단합니다. 대신 division_by_zero를 not_quadratic 및 square_root_of_negative_number 및 no_real_roots로 다시 생각해야합니다.

예외 계층 구조가 필요하지 않습니다. 호출 함수는 예외를 처리해야하므로 함수에서 발생하는 예외 (예외를 식별하는)의 열거는 충분합니다. 통화 트리를 처리하도록 허용하는 것은 컨텍스트를 벗어난 (제한되지 않은) 이동입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.