예외 객체를 던지는 대신 반환하는 합법적 인 이유가 있습니까? 외부 스택 프레임) 의 절에서 발견되는 예외가

이 질문은 예외 처리를 지원하는 모든 OO 프로그래밍 언어에 적용됩니다. 설명 목적으로 만 C #을 사용하고 있습니다.

일반적으로 코드가 즉시 처리 할 수 ​​없다는 문제가 발생 catch하면 다른 위치 (일반적으로 외부 스택 프레임) 의 절에서 발견되는 예외가 발생합니다 .

Q : 예외가 발생하지 않고 잡히지 않고 단순히 메서드에서 반환 된 다음 오류 개체로 전달되는 합법적 인 상황이 있습니까?

이 질문은 .NET 4의 System.IObserver<T>.OnError방법이 다음과 같이 제안 하기 때문에 나에게 제기되었습니다 . 예외가 오류 객체로 전달됩니다.

다른 시나리오 인 유효성 검사를 살펴 보겠습니다. 나는 일반적인 통념을 따르고 있으며 따라서 오류 객체 유형 IValidationErrorValidationException예기치 않은 오류를보고하는 데 사용되는 별도의 예외 유형 을 구별한다고 가정 해 봅시다 .

partial interface IValidationError { }

abstract partial class ValidationException : System.Exception
{
    public abstract IValidationError[] ValidationErrors { get; }
}

( System.Component.DataAnnotations네임 스페이스 는 상당히 비슷한 일을합니다.)

이러한 유형은 다음과 같이 채택 될 수 있습니다.

partial interface IFoo { }  // an immutable type

partial interface IFooBuilder  // mutable counterpart to prepare instances of above type
{
    bool IsValid(out IValidationError[] validationErrors);  // true if no validation error occurs
    IFoo Build();  // throws ValidationException if !IsValid(…)
}

이제 궁금합니다. 위를 위와 같이 단순화 할 수 없었습니다.

partial class ValidationError : System.Exception { }  // = IValidationError + ValidationException

partial interface IFoo { }  // (unchanged)

partial interface IFooBuilder
{
    bool IsValid(out ValidationError[] validationErrors);
    IFoo Build();  // may throw ValidationError or sth. like AggregateException<ValidationError>
}

Q : 이 두 가지 접근 방식의 장단점은 무엇입니까?



답변

예외가 발생하지 않고 잡히지 않고 단순히 메서드에서 반환 된 다음 오류 개체로 전달되는 합법적 인 상황이 있습니까?

결코 던져지지 않으면 예외가 아닙니다. derived동작을 따르지 않지만 Exception 클래스 의 객체 입니다. 그것을 예외라고 부르는 것은 순전히 의미 론적이지만, 그것을 던지지 않는 데 아무런 문제가 없습니다. 내 관점에서 예외를 던지지 않는 것은 내부 함수가이를 포착하여 전파하는 것을 방지하는 것과 똑같습니다.

함수가 예외 객체를 반환하는 것이 유효합니까?

물론. 다음은 이것이 적절한 예의 간단한 목록입니다.

  • 예외 공장.
  • 사용 가능한 예외로 이전 오류가 있었는지보고하는 컨텍스트 객체입니다.
  • 이전에 발견 된 예외를 유지하는 함수입니다.
  • 내부 유형의 예외를 생성하는 타사 API입니다.

그것을 던지지 않습니까?

예외를 던지는 것은 “감옥에 가십시오. 가십시오!” 보드 게임 독점에서. 컴파일러는 해당 소스 코드를 실행하지 않고 모든 소스 코드를 catch로 건너 뛰도록 컴파일러에 지시합니다. 오류, 버그보고 또는 중지와는 아무런 관련이 없습니다. 함수에 대한 “슈퍼 리턴”문으로 예외를 던지는 것을 생각하고 싶습니다. 원래 호출자보다 훨씬 높은 곳으로 코드 실행을 반환합니다.

여기서 중요한 것은 예외의 진정한 가치가 try/catch예외 객체의 인스턴스화가 아니라 패턴 에 있다는 것을 이해하는 것입니다 . 예외 개체는 상태 메시지 일뿐입니다.

귀하의 질문 에이 두 가지 사용법을 혼동하는 것 같습니다 : 예외 처리기로 점프하고 예외가 나타내는 오류 상태. 오류 상태를 취하여 예외로 래핑했다고해서 try/catch패턴이나 그 이점을 따르는 것은 아닙니다 .


답변

예외를 throw하는 대신 예외를 반환하면 상황을 분석하고 호출자가 해당 예외를 반환 할 수있는 도우미 메서드가있는 경우 의미가 의미가 있습니다 ( “예외 팩토리”라고 할 수 있음). 이 오류 분석기 기능에서 예외를 발생시키는 것은 분석 자체에서 문제가 발생한 것을 의미하며 예외를 반환하면 오류의 종류가 성공적으로 분석되었음을 의미합니다.

하나의 가능한 사용 사례는 HTTP 응답 코드 를 예외로 바꾸는 함수일 수 있습니다 .

Exception analyzeHttpError(int errorCode) {
    if (errorCode < 400) {
         throw new NotAnErrorException();
    }
    switch (errorCode) {
        case 403:
             return new ForbiddenException();
        case 404:
             return new NotFoundException();
        case 500:
             return new InternalServerErrorException();
        …
        default:
             throw new UnknownHttpErrorCodeException(errorCode);
     }
}

참고 던지는 방법이 잘못 사용되거나 동안 내부 오류가 발생한 것을 예외 수단 반환 오류 코드가 성공적으로 확인 된 것을 예외 수단.


답변

개념적으로 예외 개체가 작업의 예상 결과이면 예입니다. 그러나 내가 생각할 수있는 경우에는 항상 특정 시점에서 스로 잡기가 필요합니다.

  • “Try”패턴의 변형 (메소드가 두 번째 예외 발생 메소드를 캡슐화하지만 예외를 포착하고 대신 성공을 나타내는 부울을 리턴 함). 부울을 반환하는 대신 throw 된 예외 (성공한 경우 null)를 반환하여 catch-less 성공 또는 실패 표시를 유지하면서 최종 사용자에게 더 많은 정보를 제공 할 수 있습니다.

  • 워크 플로우 오류 처리 실제로 “패턴 시도”메소드 캡슐화와 유사하게, 일련의 명령 패턴으로 워크 플로우 단계를 추상화 할 수 있습니다. 체인의 링크에서 예외가 발생하는 경우 추상 객체에서 해당 예외를 포착 한 다음 해당 작업의 결과로 반환하여 워크 플로 엔진과 실제 코드가 워크 플로 단계로 실행되므로 광범위한 시도가 필요하지 않습니다. -자신의 논리를 잡으십시오.


답변

일부 스레드 풀에서 실행되도록 일부 작업을 대기열에 추가했다고 가정하겠습니다. 이 작업에서 예외가 발생하면 다른 스레드이므로 잡을 수 없습니다. 실행 된 스레드는 그냥 죽습니다.

이제 무언가 (해당 작업의 코드 또는 스레드 풀 구현)가 해당 예외를 작업과 함께 저장하고 작업이 완료된 것으로 간주합니다 (불필요). 이제 작업이 완료되었는지 여부를 묻거나 스레드에서 다시 던질 수 있는지 또는 스레드에서 다시 던질 수 있는지 (또는 원본으로 원인이있는 새로운 예외) 질문 할 수 있습니다.

수동으로 수행하면 새 예외를 작성하여 던지고 잡은 다음 다른 스레드에서 던지기, 잡기 및 반응을 검색하여 저장하고 있음을 알 수 있습니다. 따라서 던지기와 잡기를 건너 뛰고 저장하고 작업을 마친 다음 예외가 있는지 묻고 반응하는 것이 합리적입니다. 그러나 같은 장소에서 실제로 예외가 발생할 수 있으면 더 복잡한 코드가 발생할 수 있습니다.

추신 : 이것은 Java에 대한 경험으로 작성되었으며 예외를 만들 때 스택 추적 정보가 생성됩니다 (던질 때 C #이 생성되는 것과 달리). 따라서 자바에서 예외 접근 방식은 C #보다 느리게 처리되지만 (사전 생성 및 재사용되지 않는 한) 스택 추적 정보를 사용할 수 있습니다.

일반적으로 프로파일 링 후 성능 최적화가 병목 현상을 지적하지 않는 한 예외를 작성하지 말고 절대 예외를 피하십시오. 적어도 예외 생성이 비싼 Java에서는 (스택 추적). C #에서는 가능하지만 IMO는 놀랍기 때문에 피해야합니다.


답변

그것은 당신의 디자인에 달려 있으며, 일반적으로 호출자에게 예외를 반환하지 않을 것입니다. 일반적으로 코드는 조기에 빠르게 실패하도록 작성되었습니다. 예를 들어 파일을 열고 처리하는 경우 (C # PsuedoCode)를 고려하십시오.

        private static void ProcessFileFailFast()
        {
            try
            {
                using (var file = new System.IO.StreamReader("c:\\test.txt"))
                {
                    string line;
                    while ((line = file.ReadLine()) != null)
                    {
                        ProcessLine(line);
                    }
                }
            }
            catch (Exception ex)
            {
                LogException(ex);
            }
        }

        private static void ProcessLine(string line)
        {
            //TODO:  Process Line
        }

        private static void LogException(Exception ex)
        {
            //TODO:  Log Exception
        }

이 경우 레코드가 잘못되면 오류가 발생하여 파일 처리가 중지됩니다.

그러나 요구 사항은 하나 이상의 라인에 오류가 있어도 파일 처리를 계속하고 싶다고 말합니다. 코드는 다음과 같이 보일 수 있습니다.

    private static void ProcessFileFailAndContinue()
    {
        try
        {
            using (var file = new System.IO.StreamReader("c:\\test.txt"))
            {
                string line;
                while ((line = file.ReadLine()) != null)
                {
                    Exception ex = ProcessLineReturnException(line);
                    if (ex != null)
                    {
                        _Errors.Add(ex);
                    }
                }
            }

            //Do something with _Errors Here
        }
        //Catch errors specifically around opening the file
        catch (System.IO.FileNotFoundException fnfe)
        {
            LogException(fnfe);
        }
    }

    private static Exception ProcessLineReturnException(string line)
    {
        try
        {
            //TODO:  Process Line
        }
        catch (Exception ex)
        {
            LogException(ex);
            return ex;
        }

        return null;
    }

따라서이 경우 예외를 다시 반환하지는 않지만 예외가 포착되어 이미 처리되었으므로 일종의 오류 객체가 발생하지만 예외를 호출자에게 다시 반환합니다. 이것은 예외를 돌려주는 것에 해를 끼치 지 않지만, 예외 객체는 그 행동이 있기 때문에 바람직하지 않은 예외를 다시 던질 수 있습니다. 호출자가 다시 던질 수있는 기능을 원하면 예외를 리턴하고, 그렇지 않으면 예외 오브젝트에서 정보를 가져 와서 더 작은 경량 오브젝트를 구성하여 대신 리턴하십시오. 빠른 실패는 일반적으로 더 깨끗한 코드입니다.

유효성 검사 예제의 경우 유효성 검사 오류가 일반적 일 수 있으므로 예외 클래스에서 상속하지 않거나 예외를 throw하지 않을 수 있습니다. 사용자의 50 %가 첫 번째 시도에서 양식을 올바르게 작성하지 못하면 예외를 던지기보다는 객체를 반환하는 것이 오버 헤드가 줄어 듭니다.


답변

Q : 예외가 발생하지 않고 잡히지 않고 단순히 메서드에서 반환 된 다음 오류 개체로 전달되는 합법적 인 상황이 있습니까?

예. 예를 들어, 최근에 ASMX 웹 서비스에서 발생한 예외에 결과 SOAP 메시지에 요소가 포함되어 있지 않아서이를 생성해야하는 상황이 발생했습니다.

예시 코드 :

Public Sub SomeWebMethod()
    Try
        ...
    Catch ex As Exception
        Dim soapex As SoapException = Me.GenerateSoapFault(ex)
        Throw soapex
    End Try
End Sub

Private Function GenerateSoapFault(ex As Exception) As SoapException
    Dim document As XmlDocument = New XmlDocument()
    Dim faultDetails As XmlNode = document.CreateNode(XmlNodeType.Element, SoapException.DetailElementName.Name, SoapException.DetailElementName.Namespace)
    faultDetails.InnerText = ex.Message
    Dim exceptionType As String = ex.GetType().ToString()
    Dim soapex As SoapException = New SoapException("SoapException", SoapException.ClientFaultCode, Context.Request.Url.ToString, faultDetails)
    Return soapex
End Function


답변

대부분의 언어 (afaik)에서는 예외가 추가로 제공됩니다. 대부분 현재 스택 추적의 스냅 샷이 객체에 저장됩니다. 이것은 디버깅에는 좋지만 메모리에 영향을 줄 수 있습니다.

@ThinkingMedia가 이미 말했듯이 실제로 예외를 오류 객체로 사용하고 있습니다.

코드 예제에서는 주로 코드 재사용 및 구성을 피하기 위해이 작업을 수행하는 것 같습니다. 개인적으로 이것이 이것이 좋은 이유라고 생각하지 않습니다.

또 다른 가능한 이유는 스택 추적이있는 오류 객체를 제공하도록 언어를 속이는 것입니다. 이것은 오류 처리 코드에 추가 컨텍스트 정보를 제공합니다.

반면에 스택 추적을 유지하는 데 메모리 비용이 있다고 가정 할 수 있습니다. 예를 들어 이러한 개체를 어딘가에 모으기 시작하면 불쾌한 메모리 영향이 나타날 수 있습니다. 물론 이것은 주로 언어 / 엔진에서 예외 스택 추적이 어떻게 구현되는지에 달려 있습니다.

좋은 생각입니까?

지금까지 나는 그것이 사용되거나 권장되는 것을 보지 못했습니다. 그러나 이것은별로 의미가 없습니다.

문제는 : 스택 추적이 오류 처리에 실제로 유용합니까? 또는 더 일반적으로 개발자 나 관리자에게 어떤 정보를 제공 하시겠습니까?

일반적인 throw / try / catch 패턴은 스택 트레이스가 필요합니다. 반환 된 객체의 경우 항상 호출 된 함수에서 가져옵니다. 스택 추적에는 개발자가 필요로하는 모든 정보가 포함될 수 있지만 덜 무겁고 구체적인 정보로 충분할 수 있습니다.