예외 또는 반환 코드를 선호하는 이유는 무엇입니까?


92

내 질문은 대부분의 개발자가 오류 처리, 예외 또는 오류 반환 코드에 대해 선호하는 것입니다. 특정 언어 (또는 언어 계열)를 지정하고 다른 언어보다 선호하는 이유를 기재하십시오.

나는 호기심에서 이것을 묻는 것입니다. 개인적으로 오류 반환 코드는 덜 폭발적이고 사용자 코드가 원하지 않는 경우 예외 성능 패널티를 지불하도록 강요하지 않기 때문에 선호합니다.

업데이트 : 모든 답변에 감사드립니다! 예외가있는 코드 흐름의 예측 불가능 성을 싫어하지만 말해야합니다. 반환 코드에 대한 대답 (및 형 핸들)은 코드에 많은 노이즈를 추가합니다.


1
소프트웨어 세계의 닭고기 또는 계란 문제 ... 영원히 논쟁의 여지가 있습니다. :)
Gishu

미안하지만 다양한 의견이 있으면 사람들 (나 자신 포함)이 적절하게 선택하는 데 도움이되기를 바랍니다.
Robert Gould

답변:


107

일부 언어 (예 : C ++)의 경우 리소스 누출이 원인이되어서는 안됩니다.

C ++는 RAII를 기반으로합니다.

실패, 반환 또는 던질 수있는 코드가있는 경우 (즉, 대부분의 일반적인 코드) 스마트 포인터 안에 포인터를 래핑 해야합니다 (객체를 스택에 만들지 않을 좋은 이유 가 있다고 가정 ).

리턴 코드가 더 상세 함

장황하고 다음과 같이 발전하는 경향이 있습니다.

if(doSomething())
{
   if(doSomethingElse())
   {
      if(doSomethingElseAgain())
      {
          // etc.
      }
      else
      {
         // react to failure of doSomethingElseAgain
      }
   }
   else
   {
      // react to failure of doSomethingElse
   }
}
else
{
   // react to failure of doSomething
}

결국 코드는 식별 된 명령의 모음입니다 (프로덕션 코드에서 이러한 종류의 코드를 보았습니다).

이 코드는 다음과 같이 잘 번역 될 수 있습니다.

try
{
   doSomething() ;
   doSomethingElse() ;
   doSomethingElseAgain() ;
}
catch(const SomethingException & e)
{
   // react to failure of doSomething
}
catch(const SomethingElseException & e)
{
   // react to failure of doSomethingElse
}
catch(const SomethingElseAgainException & e)
{
   // react to failure of doSomethingElseAgain
}

어떤 깨끗하게 별도의 코드와 오류 처리 할 수좋은의 일.

반환 코드가 더 부서지기 쉽습니다.

한 컴파일러의 모호한 경고가 아니라면 ( "phjr"의 주석 참조) 쉽게 무시할 수 있습니다.

위의 예를 통해 누군가가 가능한 오류를 처리하는 것을 잊는다 고 가정합니다 (이런 일이 발생합니다 ...). 오류는 "반환"될 때 무시되며 나중에 폭발 할 가능성이 있습니다 (예 : NULL 포인터). 예외로 동일한 문제가 발생하지 않습니다.

오류는 무시되지 않습니다. 때로는 폭발하지 않기를 원하지만 ... 그러니 신중하게 선택해야합니다.

반환 코드는 때때로 번역되어야합니다.

다음과 같은 기능이 있다고 가정 해 보겠습니다.

  • NOT_FOUND_ERROR라는 정수를 반환 할 수있는 doSomething
  • bool "false"를 반환 할 수있는 doSomethingElse (실패한 경우)
  • doSomethingElseAgain : Error 객체를 반환 할 수 있습니다 (__LINE__, __FILE__ 및 스택 변수의 절반 포함).
  • doTryToDoSomethingWithAllThisMess that, well ... 위의 함수를 사용하고 유형의 오류 코드를 반환합니다 ...

호출 된 함수 중 하나가 실패 할 경우 doTryToDoSomethingWithAllThisMess의 반환 유형은 무엇입니까?

반환 코드는 보편적 인 솔루션이 아닙니다.

연산자는 오류 코드를 반환 할 수 없습니다. C ++ 생성자도 그렇게 할 수 없습니다.

반환 코드는 표현식을 연결할 수 없음을 의미합니다.

위 요점의 결과. 작성하려면 어떻게해야합니까?

CMyType o = add(a, multiply(b, c)) ;

반환 값이 이미 사용 되었기 때문에 할 수 없습니다 (때로는 변경할 수없는 경우도 있음). 따라서 반환 값은 참조로 전송되는 첫 번째 매개 변수가됩니다.

예외가 입력되었습니다.

각 예외 유형에 대해 서로 다른 클래스를 보낼 수 있습니다. 리소스 예외 (즉, 메모리 부족)는 가벼워 야하지만 그 밖의 모든 것은 필요한만큼 무거울 수 있습니다 (전체 스택을 제공하는 Java 예외가 마음에 듭니다).

그런 다음 각 캐치를 특수화 할 수 있습니다.

다시 던지지 않고 catch (...)를 사용하지 마십시오.

일반적으로 오류를 숨기면 안됩니다. 다시 던지지 않으면 최소한 파일에 오류를 기록하고 메시지 상자를여십시오.

예외는 ... NUKE

예외의 문제는 그것들을 남용하면 try / catches로 가득 찬 코드가 생성된다는 것입니다. 그러나 문제는 다른 곳에 있습니다. 누가 STL 컨테이너를 사용하여 코드를 시도 / 캐치합니까? 그래도 이러한 컨테이너는 예외를 보낼 수 있습니다.

물론 C ++에서는 예외가 소멸자를 종료하지 않도록합니다.

예외는 ... 동기

스레드가 무릎을 꿇거나 Windows 메시지 루프 내부로 전파되기 전에이를 포착해야합니다.

해결책은 그것들을 혼합하는 것일 수 있습니까?

내가 추측 그래서 해결책은 무엇인가시기를 던질 것입니다 하지 일어난다. 그리고 어떤 일이 발생하면 반환 코드 나 매개 변수를 사용하여 사용자가 이에 반응 할 수 있도록합니다.

따라서 유일한 질문은 "발생해서는 안되는 일이 무엇입니까?"입니다.

그것은 당신의 기능의 계약에 달려 있습니다. 함수가 포인터를 받아들이지 만 포인터가 NULL이 아니어야한다고 지정하면 사용자가 NULL 포인터를 보낼 때 예외를 throw해도 괜찮습니다 (문제는 C ++에서 함수 작성자가 대신 참조를 사용하지 않았을 때) 하지만 ...)

또 다른 해결책은 오류를 표시하는 것입니다.

때때로 문제는 오류를 원하지 않는다는 것입니다. 예외 나 오류 반환 코드를 사용하는 것은 멋지지만 ... 그것에 대해 알고 싶습니다.

제 직업에서 우리는 일종의 "Assert"를 사용합니다. 디버그 / 릴리스 컴파일 옵션에 관계없이 구성 파일의 값에 따라 다음과 같이됩니다.

  • 오류 기록
  • "Hey, you have a problem"이있는 메시지 상자를 엽니 다.
  • "문제가 있습니다. 디버그 하시겠습니까?"라는 메시지 상자를 엽니 다.

개발과 테스트 모두에서 사용자는 문제가 발견 된 후가 아니라 (일부 코드가 반환 값에 관심이 있거나 catch 내부에있을 때) 정확히 발견 된 시점에 문제를 정확히 찾아 낼 수 있습니다.

레거시 코드에 쉽게 추가 할 수 있습니다. 예를 들면 :

void doSomething(CMyObject * p, int iRandomData)
{
   // etc.
}

다음과 유사한 종류의 코드를 유도합니다.

void doSomething(CMyObject * p, int iRandomData)
{
   if(iRandomData < 32)
   {
      MY_RAISE_ERROR("Hey, iRandomData " << iRandomData << " is lesser than 32. Aborting processing") ;
      return ;
   }

   if(p == NULL)
   {
      MY_RAISE_ERROR("Hey, p is NULL !\niRandomData is equal to " << iRandomData << ". Will throw.") ;
      throw std::some_exception() ;
   }

   if(! p.is Ok())
   {
      MY_RAISE_ERROR("Hey, p is NOT Ok!\np is equal to " << p->toString() << ". Will try to continue anyway") ;
   }

   // etc.
}

(디버그에서만 활성화되는 유사한 매크로가 있습니다).

프로덕션에서는 구성 파일이 존재하지 않으므로 클라이언트는이 매크로의 결과를 볼 수 없습니다. 그러나 필요할 때 활성화하는 것은 쉽습니다.

결론

반환 코드를 사용하여 코드를 작성할 때 실패에 대비하고 테스트 요새가 충분히 안전하기를 바랍니다.

예외를 사용하여 코드를 작성할 때 코드가 실패 할 수 있음을 알고 일반적으로 코드에서 선택한 전략적 위치에 반격을가합니다. 그러나 일반적으로 코드는 "해야 할 일"보다 "내가 두려워하는 일"에 대한 것입니다.

그러나 코드를 작성할 때는 최선의 도구를 사용해야하며 때로는 "오류를 숨기지 않고 가능한 한 빨리 표시"하는 경우가 있습니다. 위에서 언급 한 매크로는이 철학을 따릅니다.


3
: 그래,하지만 첫 번째 예에 대하여, 그 다음처럼 쉽게 작성할 수 있습니다 if( !doSomething() ) { puts( "ERROR - doSomething failed" ) ; return ; // or react to failure of doSomething } if( !doSomethingElse() ) { // react to failure of doSomethingElse() }
bobobobo

왜 안 되 겠어요 ...하지만 doSomething(); doSomethingElse(); ...if / while / etc를 추가해야한다면 여전히 더 나은 방법을 찾습니다 . 정상적인 실행 목적을위한 문을 if / while / etc와 혼합하고 싶지 않습니다. 예외적 인 목적으로 추가 된 문 ... 예외 사용에 대한 실제 규칙은 catch가 아니라 throw 하는 것이므로 try / catch 문은 일반적으로 침입 적이 지 않습니다.
paercebal

1
첫 번째 요점은 예외 문제가 무엇인지 보여줍니다. 제어 흐름이 이상하고 실제 문제와 분리됩니다. 일부 수준의 식별을 일련의 캐치로 대체하고 있습니다. 나는 예상 하지 못한 것들에 대한 가능한 오류와 예외에 대해 반환 코드 (또는 무거운 정보가있는 객체를 반환)를 모두 사용합니다 .
Peter

2
@Peter Weber : 이상하지 않습니다. 정상적인 실행 흐름의 일부가 아니기 때문에 실제 문제와 분리됩니다 . 그것은이다 뛰어난 실행. 그리고, 다시 예외에 대한 요점은, 경우에 예외적 인 오류,하기 자주 던져 , 그리고 거의 잡을 어느 경우. 따라서 catch 블록은 코드에 거의 나타나지 않습니다.
paercebal

이런 종류의 논쟁의 예는 매우 단순합니다. 일반적으로 "doSomething ()"또는 "doSomethingElse ()"는 실제로 어떤 객체의 상태를 변경하는 것과 같은 작업을 수행합니다. 예외 코드는 객체를 이전 상태로 되 돌리는 것을 보장하지 않으며 캐치가 throw에서 매우 멀 때도 더 적습니다. 예외를 포착 할 때 한두 번 감소해야한다는 것을 어떻게 알 수 있습니까? 일반적으로 장난감 예제가 아닌 예외 안전 코드를 작성하는 것은 매우 어렵습니다 (불가능합니까?).
xryl669

36

실제로 둘 다 사용합니다.

알려진 가능한 오류 인 경우 반환 코드를 사용합니다. 내가 아는 시나리오가 가능하고 일어날 수 있다면 다시 전송되는 코드가 있습니다.

예외는 내가 기대하지 않는 것들에만 사용됩니다.


아주 간단한 방법은 +1입니다. Atleast 내가 아주 빨리 읽을 수있을만큼 충분히 짧습니다. :-)
Ashish Gupta

1
" 예외는 내가 예상 하지 못한 것에 대해서만 사용됩니다. " 예외를 예상하지 않는 경우 왜 사용하거나 어떻게 사용할 수 있습니까?
anar khalilov 2013

5
그것이 일어날 것으로 예상하지 않는다고해서 그것이 어떻게 될 수 있는지 볼 수 없다는 의미는 아닙니다. SQL Server가 켜져 있고 응답 할 것으로 예상합니다. 하지만 예상치 못한 다운 타임이 발생하면 정상적으로 실패 할 수 있도록 기대치를 코딩합니다.
Stephen Wrighton

응답하지 않는 SQL Server는 "알려진 가능한 오류"로 쉽게 분류되지 않습니까?
tkburbidge

21

Framework Design Guidelines : Conventions, Idioms and Patterns for Reusable .NET Libraries의 7 장 "예외"에 따르면 C #과 같은 OO 프레임 워크에서 반환 값에 대한 예외를 사용해야하는 이유에 대한 많은 근거가 제공됩니다.

아마도 이것이 가장 설득력있는 이유 일 것입니다 (179 페이지).

"예외는 객체 지향 언어와 잘 통합됩니다. 객체 지향 언어는 비 OO 언어의 함수에 의해 부과되지 않는 멤버 서명에 제약을 부과하는 경향이 있습니다. 예를 들어 생성자, 연산자 오버로드 및 속성의 경우 개발자는 반환 값에 선택의 여지가 없습니다. 이러한 이유로 객체 지향 프레임 워크에 대한 반환 값 기반 오류보고를 표준화 할 수 없습니다. 예외와 같은 오류보고 방법은 메서드 서명 범위를 벗어납니다. 유일한 옵션입니다. "


10

내 선호도 (C ++ 및 Python)는 예외를 사용하는 것입니다. 언어 제공 기능은 예외를 발생, 포착 및 (필요한 경우) 다시 던지는 잘 정의 된 프로세스를 만들어 모델을 쉽게보고 사용할 수 있도록합니다. 개념적으로는 특정 예외가 이름으로 정의 될 수 있고 추가 정보가 수반된다는 점에서 리턴 코드보다 깔끔합니다. 반환 코드를 사용하면 오류 값으로 만 제한됩니다 (ReturnStatus 객체 등을 정의하려는 경우 제외).

작성중인 코드가 시간이 중요하지 않은 경우 스택 해제와 관련된 오버 헤드는 걱정할만큼 중요하지 않습니다.


2
예외를 사용하면 프로그램 분석이 더 어려워집니다.
Paweł Hajdan

7

예상하지 못한 일이 발생하는 경우에만 예외가 반환되어야합니다.

역사적으로 예외의 또 다른 점은 반환 코드가 본질적으로 독점적이라는 것입니다. 때로는 성공을 나타 내기 위해 C 함수에서 0이 반환 될 수 있고, 때로는 -1이 반환 될 수도 있고, 실패 할 경우에는 둘 중 하나가 반환되고 성공하면 1이 반환 될 수 있습니다. 열거 된 경우에도 열거가 모호 할 수 있습니다.

예외는 또한 더 많은 정보를 제공 할 수 있으며 특히 '뭔가 잘못되었습니다. 여기에 스택 추적 및 컨텍스트에 대한 일부 지원 정보'를 잘 설명합니다.

즉, 잘 열거 된 반환 코드는 알려진 결과 집합에 유용 할 수 있습니다. 간단한 '여기에 함수의 n 개의 결과가 있으며이 방식으로 실행되었습니다.'


6

Java에서는 다음을 사용합니다 (다음 순서로).

  1. 계약 별 설계 ( 실패 할 수 있는 작업 을 시도하기 전에 사전 조건이 충족되는지 확인 ). 이것은 대부분의 것을 포착하고 이에 대한 오류 코드를 반환합니다.

  2. 작업을 처리하는 동안 오류 코드를 반환하고 필요한 경우 롤백을 수행합니다.

  3. 예외이지만 예상치 못한 일 에만 사용 됩니다 .


1
계약에 단언을 사용하는 것이 약간 더 정확하지 않을까요? 계약이 깨지면 당신을 구할 것이 없습니다.
Paweł Hajdan

@ PawełHajdan, 어설 션은 기본적으로 비활성화되어 있다고 믿습니다. 이것은 assert항상 어설 션으로 실행하지 않는 한 프로덕션 코드에서 문제를 포착하지 않는다는 점에서 C와 동일한 문제를 가지고 있습니다. 내가 개발하는 동안 캐치 문제에 대한 방법으로 주장을 참조하는 경향이 있지만 에만 (예 : 상수와 물건으로하지 어설 일관되게 주장이나됩니다 물건 하지 변수 또는 런타임에 변경할 수 있습니다 무엇과 물건).
paxdiablo

그리고 당신의 질문에 답하는 데 12 년이 걸렸습니다. 헬프 데스크를 운영해야합니다 :-)
paxdiablo

6

반환 코드는 거의 매번 " 성공의 구덩이 "테스트에 실패합니다 .

  • 반환 코드를 확인하는 것을 잊고 나중에 red-herring 오류가 발생하는 것은 너무 쉽습니다.
  • 반환 코드에는 호출 스택, 내부 예외와 같은 훌륭한 디버깅 정보가 없습니다.
  • 반환 코드는 전파되지 않으며, 위의 요점과 함께 하나의 중앙 위치 (응용 프로그램 및 스레드 수준 예외 처리기)에 로깅하는 대신 과도하고 짜여진 진단 로깅을 유도하는 경향이 있습니다.
  • 반환 코드는 중첩 된 'if'블록 형태로 복잡한 코드를 유도하는 경향이 있습니다.
  • 명백한 예외 (성공의 구덩이) 였을 알려지지 않은 문제를 디버깅하는 데 소요 된 개발자 시간은 비용이 많이 듭니다.
  • C # 팀이 제어 흐름을 제어하는 ​​예외를 의도하지 않았다면 예외가 입력되지 않고 catch 문에 "when"필터가 없으며 매개 변수가없는 'throw'문이 필요하지 않습니다. .

성능 관련 :

  • 예외는 전혀 던지지 않는 것에 비해 계산 비용이 많이들 수 있지만, 이유 때문에 EXCEPTIONS라고 불립니다. 속도 비교는 항상 100 % 예외 율을 가정하여 관리합니다. 예외가 100 배 더 느리더라도 1 %의 시간에만 발생하는 경우 실제로 얼마나 중요합니까?
  • 그래픽 응용 프로그램이나 이와 유사한 것에 대한 부동 소수점 연산에 대해 이야기하지 않는 한 CPU주기는 개발자 시간에 비해 저렴합니다.
  • 시간 관점에서의 비용은 같은 주장을합니다. 데이터베이스 쿼리, 웹 서비스 호출 또는 파일로드와 비교하여 일반적인 애플리케이션 시간은 예외 시간보다 작습니다. 예외는 2006 년에 거의 마이크로 초 미만 이었습니다.
    • .net에서 일하는 모든 사람에게 감히 디버거가 모든 예외를 중단하고 내 코드 만 비활성화하도록 설정하고 알지도 못하는 예외가 이미 발생하고 있는지 확인합니다.

4

내가 The Pragmatic Programmer 로부터받은 훌륭한 조언 은 "당신의 프로그램은 예외를 전혀 사용하지 않고 모든 주요 기능을 수행 할 수 있어야한다"는 내용이었습니다.


4
당신은 그것을 잘못 해석하고 있습니다. 그들이 의미하는 바는 "당신의 프로그램이 정상적인 흐름에서 예외를 던진다면 그것은 잘못된 것"입니다. 즉, "예외적 인 경우에만 예외를 사용하십시오".
Paweł Hajdan

4

나는 이것에 대해 얼마 전에 블로그 게시물을 썼다 .

예외 발생으로 인한 성능 오버 헤드가 결정에 영향을주지 않아야합니다. 제대로하고 있다면 예외는 예외 입니다.


링크 된 블로그 게시물은 도약하기 전에보기 (확인)에 대한 것이지, 허가 (예외 또는 반환 코드)보다 용서를 구하기가 더 쉽습니다. 나는 그 문제에 대한 내 생각으로 거기에 대답했다 (힌트 : TOCTTOU). 그러나이 질문은 다른 문제에 관한 것입니다. 즉, 특수 속성을 가진 값을 반환하는 대신 언어의 예외 메커니즘을 사용할 조건이 무엇인지에 대한 것입니다.
Damian Yerrick

완전히 동의 해. 지난 9 년 동안 한두 가지를 배운 것 같습니다.)
Thomas

4

반환 코드는 코드 전체에 걸쳐 다음 패턴이 버섯을 일으키기 때문에 싫어합니다.

CRetType obReturn = CODE_SUCCESS;
obReturn = CallMyFunctionWhichReturnsCodes();
if (obReturn == CODE_BLOW_UP)
{
  // bail out
  goto FunctionExit;
}

머지 않아 4 개의 함수 호출로 구성된 메서드 호출이 12 줄의 오류 처리로 부풀려집니다.이 중 일부는 결코 발생하지 않을 것입니다. If and switch case는 풍부합니다.

예외를 잘 사용하면 예외가 더 깨끗해집니다. 예외적 인 이벤트를 알리기 위해 .. 실행 경로를 계속할 수 없습니다. 오류 코드보다 설명적이고 정보를 제공하는 경우가 많습니다.

메서드 호출 후 다르게 처리해야하는 (예외적 인 경우는 아님) 여러 상태가있는 경우 오류 코드 또는 out 매개 변수를 사용합니다. Personaly가 드물다고 생각했지만 ..

C ++ / COM 세계에서는 '성능 패널티'반론에 대해 좀 더 알아 보았지만 새로운 언어에서는 그 차이가 그다지 크지 않다고 생각합니다. 어쨌든 무언가가 터지면 성능 문제가 backburner로 넘어갑니다. :)


3

괜찮은 컴파일러 또는 런타임 환경 예외로 인해 심각한 불이익이 발생하지 않습니다. 예외 처리기로 이동하는 GOTO 문과 비슷합니다. 또한 런타임 환경 (예 : JVM)에서 예외를 포착하면 버그를 훨씬 쉽게 격리하고 수정하는 데 도움이됩니다. C의 segfault를 통해 Java에서 NullPointerException이 발생합니다.


2
예외는 매우 비쌉니다. 잠재적 인 예외 처리기를 찾기 위해 스택을 걸어야합니다. 이 스택 워크는 저렴하지 않습니다. 스택 추적이 빌드되면 전체 스택 구문 분석 해야 하기 때문에 훨씬 더 비쌉니다 .
Derek Park

컴파일러가 예외가 적어도 어느 정도 포착 될 위치를 결정할 수 없다는 것에 놀랐습니다. 또한 예외가 코드 흐름을 변경한다는 사실은 제 생각에 오류가 발생한 위치를 정확하게 식별하는 것을 더 쉽게 만들어 성능 저하를 보상합니다.
Kyle Cronin

호출 스택은 런타임에 매우 복잡해질 수 있으며 컴파일러는 일반적으로 그런 종류의 분석을 수행하지 않습니다. 그래도 추적을 얻으려면 스택을 걸어야합니다. finally스택 할당 객체에 대한 블록 및 소멸자 를 처리하려면 스택을 풀어야 합니다.
Derek Park

하지만 예외의 디버깅 이점이 종종 성능 비용을 보충한다는 데 동의합니다.
Derek Park

1
Derak Park, 예외가 발생하면 비용이 많이 듭니다. 이것이 과용해서는 안되는 이유입니다. 그러나 이러한 일이 발생하지 않으면 비용이 거의 들지 않습니다.
paercebal

3

간단한 규칙이 있습니다.

1) 즉각적인 발신자가 반응 할 것으로 예상되는 항목에 반환 코드를 사용합니다.

2) 범위가 더 넓은 오류에 예외를 사용하고 호출자보다 많은 수준에서 처리 할 것으로 예상되는 오류에 대한 예외를 사용하여 오류에 대한 인식이 여러 계층을 통과하지 않아도되므로 코드가 더 복잡해집니다.

Java에서는 확인되지 않은 예외 만 사용했으며, 확인 된 예외는 결국 다른 형태의 반환 코드가되었으며, 경험상 메서드 호출에 의해 "반환"될 수있는 이중성은 일반적으로 도움말보다 방해가되었습니다.


3

예외 및 비 예외 상황 모두에서 파이썬에서 예외를 사용합니다.

오류 값을 반환하는 대신 예외를 사용하여 "요청을 수행 할 수 없음"을 나타낼 수있는 것이 좋습니다. 그것은 당신이 / always / 반환 값이 임의적으로 None 또는 NotFoundSingleton 등이 아닌 올바른 유형임을 알고 있음을 의미합니다. 다음은 반환 값에 대한 조건부 대신 예외 처리기를 사용하는 것을 선호하는 좋은 예입니다.

try:
    dataobj = datastore.fetch(obj_id)
except LookupError:
    # could not find object, create it.
    dataobj = datastore.create(....)

부작용은 datastore.fetch (obj_id)가 실행될 때 반환 값이 None인지 확인할 필요가 없으며 즉시 무료로 오류가 발생한다는 것입니다. 이것은 "당신의 프로그램은 예외를 전혀 사용하지 않고 모든 주요 기능을 수행 할 수 있어야한다"는 주장에 반대됩니다.

다음은 경쟁 조건에 영향을받지 않는 파일 시스템을 처리하기위한 코드를 작성하기 위해 예외가 '예외적으로'유용한 또 다른 예입니다.

# wrong way:
if os.path.exists(directory_to_remove):
    # race condition is here.
    os.path.rmdir(directory_to_remove)

# right way:
try: 
    os.path.rmdir(directory_to_remove)
except OSError:
    # directory didn't exist, good.
    pass

두 개 대신 하나의 시스템 호출, 경쟁 조건 없음. 이것은 디렉토리가 존재하지 않는 것보다 더 많은 상황에서 OSError로 인해 실패 할 것이기 때문에 좋지 않은 예이지만, 엄격하게 제어되는 많은 상황에 대해 '충분히 좋은'솔루션입니다.


두 번째 예는 오해의 소지가 있습니다. os.path.rmdir 코드가 예외를 발생 시키도록 설계 되었기 때문에 잘못된 방법은 잘못된 것 같습니다. 'RMDIR (...)이 == 실패 할 경우 : 패스'리턴 코드 흐름에서 올바른 구현 될 것이다

3

반환 코드가 코드 노이즈를 추가한다고 생각합니다. 예를 들어, 반환 코드로 인해 COM / ATL 코드의 모양이 항상 싫었습니다. 모든 코드 줄에 대해 HRESULT 검사가 필요했습니다. 오류 반환 코드는 COM 설계자가 내린 잘못된 결정 중 하나라고 생각합니다. 코드의 논리적 그룹화를 어렵게하여 코드 검토가 어려워집니다.

모든 줄에 반환 코드를 명시 적으로 검사 할 때 성능 비교에 대해 잘 모르겠습니다.


1
예외를 지원하지 않는 언어에서 사용할 수 있도록 설계된 COM wad.
Kevin

그것은 좋은 지적입니다. 스크립팅 언어에 대한 오류 코드를 처리하는 것이 좋습니다. 적어도 VB6은 오류 코드 세부 정보를 Err 개체에 캡슐화하여 잘 숨겨서 더 깨끗한 코드에 도움이됩니다.
rpattabi

동의하지 않습니다 : VB6는 마지막 오류 만 기록합니다. 악명 높은 "on error resume next"와 함께 사용하면 문제를 볼 때까지 문제의 원인을 모두 놓칠 수 있습니다. 이것이 Win32 APII (GetLastError 함수 참조)에서 오류 처리의 기초라는 점에 유의하십시오.
paercebal

2

오류 처리에 예외를 사용하고 함수의 정상적인 결과로 값 (또는 매개 변수)을 반환하는 것을 선호합니다. 이것은 쉽고 일관된 오류 처리 체계를 제공하며 올바르게 수행되면 훨씬 깔끔한 코드를 만듭니다.


2

큰 차이점 중 하나는 예외로 인해 오류를 처리해야하는 반면 오류 반환 코드는 확인되지 않을 수 있다는 것입니다.

오류 반환 코드를 많이 사용하면 다음 형식과 유사한 if 테스트가 많은 매우보기 흉한 코드가 발생할 수도 있습니다.

if(function(call) != ERROR_CODE) {
    do_right_thing();
}
else {
    handle_error();
}

개인적으로 나는 호출 코드가 처리해야하거나 처리해야하는 오류에 예외를 사용하는 것을 선호하고, 무언가를 반환하는 것이 실제로 유효하고 가능한 "예상 된 실패"에 대해서만 오류 코드를 사용하는 것을 선호합니다.


적어도 C / C ++ 및 gcc에서는 반환 값이 무시 될 때 경고를 생성하는 속성을 함수에 제공 할 수 있습니다.
Paweł Hajdan

phjr : "return error code"패턴에 동의하지 않지만 귀하의 의견은 아마도 완전한 답변이 될 것입니다. 충분히 흥미 롭습니다. 최소한 유용한 정보를 제공했습니다.
paercebal

2

리턴 코드보다 예외를 선호하는 데는 여러 가지 이유가 있습니다.

  • 일반적으로 가독성을 위해 사람들은 메서드에서 return 문 수를 최소화하려고합니다. 이렇게하면 예외가 잘못된 상태에서 추가 작업을 수행하는 것을 방지하여 잠재적으로 더 많은 데이터를 손상시키는 것을 방지합니다.
  • 예외는 일반적으로 반환 값보다 더 장황하고 쉽게 확장 할 수 있습니다. 메서드가 자연수를 반환하고 오류가 발생하면 반환 코드로 음수를 사용한다고 가정하고, 메서드의 범위가 변경되어 이제 정수를 반환하면 약간만 조정하는 대신 모든 메서드 호출을 수정해야합니다. 예외.
  • 예외를 사용하면 정상적인 동작의 오류 처리를 더 쉽게 분리 할 수 ​​있습니다. 이를 통해 일부 작업이 어떻게 든 원자 적 작업으로 수행되도록 할 수 있습니다.

2

예외는 오류 처리, IMO가 아닙니다. 예외는 바로 그것입니다. 예상하지 못한 예외적 인 이벤트. 내가 말한주의해서 사용하십시오.

오류 코드는 괜찮을 수 있지만 메서드에서 404 또는 200을 반환하는 것은 좋지 않습니다. IMO. 대신 열거 형 (.Net)을 사용하면 코드를 더 읽기 쉽고 다른 개발자가 더 쉽게 사용할 수 있습니다. 또한 숫자와 설명에 대한 표를 유지할 필요가 없습니다.

또한; try-catch-finally 패턴은 내 책에서 안티 패턴입니다. Try-finally는 좋을 수 있고 try-catch도 좋을 수 있지만 try-catch-finally는 결코 좋지 않습니다. try-finally는 종종 더 나은 IMO 인 "using"문 (IDispose 패턴)으로 대체 될 수 있습니다. 그리고 실제로 처리 할 수있는 예외를 포착하는 Try-catch는 좋습니다.

try{
    db.UpdateAll(somevalue);
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}

예외가 계속해서 버블 링되도록하는 한 괜찮습니다. 또 다른 예는 다음과 같습니다.

try{
    dbHasBeenUpdated = db.UpdateAll(somevalue); // true/false
}
catch (ConnectionException ex) {
    logger.Exception(ex, "Connection failed");
    dbHasBeenUpdated = false;
}

여기서 실제로 예외를 처리합니다. 업데이트 방법이 실패 할 때 try-catch 밖에서 내가하는 일은 또 다른 이야기이지만 내 요점은 내 생각이다. :)

그렇다면 try-catch-finally가 안티 패턴 인 이유는 무엇입니까? 그 이유는 다음과 같습니다.

try{
    db.UpdateAll(somevalue);
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}
finally {
    db.Close();
}

db 객체가 이미 닫혀 있으면 어떻게됩니까? 새로운 예외가 발생하고 처리해야합니다! 이게 낫다:

try{
    using(IDatabase db = DatabaseFactory.CreateDatabase()) {
        db.UpdateAll(somevalue);
    }
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}

또는 db 개체가 IDisposable을 구현하지 않는 경우 다음을 수행하십시오.

try{
    try {
        IDatabase db = DatabaseFactory.CreateDatabase();
        db.UpdateAll(somevalue);
    }
    finally{
        db.Close();
    }
}
catch (DatabaseAlreadyClosedException dbClosedEx) {
    logger.Exception(dbClosedEx, "Database connection was closed already.");
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}

어쨌든 내 2 센트 야! :)


객체가 .Close ()를 가지고 있지만 .Dispose ()가없는 경우 그것은 이상한 것
abatishchev

저는 Close ()를 예로 사용하고 있습니다. 그것을 다른 것으로 생각하십시오. 내가 말했듯이; 가능한 경우 사용 패턴을 사용해야합니다 (doh!). 이것은 물론 클래스가 IDisposable을 구현하므로 Dispose를 호출 할 수 있음을 의미합니다.
noocyte

1

예외 만 사용하고 반환 코드는 사용하지 않습니다. 여기서 Java에 대해 이야기하고 있습니다.

내가 따르는 일반적인 규칙은 호출 된 메서드가있는 doFoo()경우 "do foo"가 아닌 경우 예외적 인 일이 발생하고 예외가 발생해야한다는 것입니다.


1

예외에 대해 내가 두려워하는 한 가지는 예외를 던지면 코드 흐름을 망칠 것이라는 것입니다. 예를 들어

void foo()
{
  MyPointer* p = NULL;
  try{
    p = new PointedStuff();
    //I'm a module user and  I'm doing stuff that might throw or not

  }
  catch(...)
  {
    //should I delete the pointer?
  }
}

또는 내가 가져서는 안되는 것을 삭제했지만 나머지 정리 작업을 수행하기 전에 잡기 위해 던져지면 어떻게 될까요? 던지는 것은 가난한 사용자 IMHO에 많은 무게를 둡니다.


이것이 <code> finally </ code> 구문의 목적입니다. 하지만 슬프게도 C ++ 표준에는 없습니다 ...
Thomas

C ++에서는 "constructor에서 리소스를 획득하고 destrucotor에서 해제합니다.이 경우 auto_ptr은 완벽하게 수행됩니다.
Serge

토마스, 당신 틀렸어요. C ++는 그것이 필요하지 않기 때문에 마침내되지 않았습니다. 대신 RAII가 있습니다. Serge의 솔루션은 RAII를 사용하는 하나의 솔루션입니다.
paercebal

Robert, Serge의 솔루션을 사용하면 문제가 해결 될 것입니다. 이제 throws보다 더 많은 try / catches를 작성하면 (코멘트를 판단하여) 코드에 문제가있을 수 있습니다. 물론 다시 던지지 않고 catch (...)를 사용하면 오류를 더 잘 무시할 수 있기 때문에 일반적으로 좋지 않습니다.
paercebal

1

예외 대 반환 코드 인수의 일반적인 규칙 :

  • 지역화 / 국제화가 필요한 경우 오류 코드를 사용하십시오. .NET에서는 이러한 오류 코드를 사용하여 리소스 파일을 참조한 다음 적절한 언어로 오류를 표시 할 수 있습니다. 그렇지 않으면 예외를 사용하십시오.
  • 정말 예외적 인 오류에만 예외를 사용하십시오 . 상당히 자주 발생하는 경우 부울 또는 열거 형 오류 코드를 사용하십시오.

l10n / i18n을 수행 할 때 예외를 사용할 수없는 동안에는 이유가 없습니다. 예외에는 지역화 된 정보도 포함될 수 있습니다.
gizmo

1

반환 코드가 예외보다 덜 추한 것을 찾지 못했습니다. 예외를 제외 try{} catch() {} finally {}하고는 반환 코드와 같은 위치에 있습니다 if(){}. 나는 게시물에 주어진 이유로 예외를 두려워했습니다. 포인터를 지울 필요가 있는지, 무엇을 가지고 있는지 알 수 없습니다. 그러나 반환 코드와 관련하여 동일한 문제가 있다고 생각합니다. 문제의 함수 / 메소드에 대한 세부 사항을 알지 못하면 매개 변수의 상태를 알 수 없습니다.

어쨌든 가능하면 오류를 처리해야합니다. 반환 코드를 무시하고 프로그램이 segfault를 수행하도록하는 것만 큼 쉽게 예외가 최상위 수준으로 전파되도록 할 수 있습니다.

결과 값 (열거 형?)을 반환하고 예외적 인 경우 예외를 반환하는 아이디어가 마음에 듭니다.


0

Java와 같은 언어의 경우, 예외가 처리되지 않으면 컴파일러가 컴파일 시간 오류를 제공하기 때문에 예외를 사용합니다. 이로 인해 호출 함수가 예외를 처리 / 발생하도록 강제합니다.

Python의 경우 더 충돌합니다. 컴파일러가 없으므로 호출자가 런타임 예외로 이어지는 함수에서 throw 된 예외를 처리하지 않을 수 있습니다. 리턴 코드를 사용하는 경우 제대로 처리되지 않으면 예기치 않은 동작이 발생할 수 있으며 예외를 사용하면 런타임 예외가 발생할 수 있습니다.


0

일반적으로 반환 코드 는 호출자가 실패가 예외인지 여부를 결정할 수 있도록 하기 때문에 선호 합니다. 입니다.

이 접근 방식은 Elixir 언어에서 일반적입니다.

# I care whether this succeeds. If it doesn't return :ok, raise an exception.
:ok = File.write(path, content)

# I don't care whether this succeeds. Don't check the return value.
File.write(path, content)

# This had better not succeed - the path should be read-only to me.
# If I get anything other than this error, raise an exception.
{:error, :erofs} = File.write(path, content)

# I want this to succeed but I can handle its failure
case File.write(path, content) do
  :ok => handle_success()
  error => handle_error(error)
end

사람들은 리턴 코드로 인해 중첩 된 if문 이 많이 생길 수 있지만 더 나은 구문으로 처리 할 수 ​​있다고 언급했습니다. Elixir에서이 with명령문을 사용하면 일련의 happy-path 반환 값을 실패와 쉽게 구분할 수 있습니다.

with {:ok, content} <- get_content(),
  :ok <- File.write(path, content) do
    IO.puts "everything worked, happy path code goes here"
else
  # Here we can use a single catch-all failure clause
  # or match every kind of failure individually
  # or match subsets of them however we like
  _some_error => IO.puts "one of those steps failed"
  _other_error => IO.puts "one of those steps failed"
end

Elixir에는 여전히 예외를 발생시키는 기능이 있습니다. 첫 번째 예제로 돌아가서 파일을 쓸 수없는 경우 예외를 발생시키기 위해 다음 중 하나를 수행 할 수 있습니다.

# Raises a generic MatchError because the return value isn't :ok
:ok = File.write(path, content)

# Raises a File.Error with a descriptive error message - eg, saying
# that the file is read-only
File.write!(path, content)

호출자로서 쓰기가 실패하면 오류를 발생시키고 싶다는 것을 알고 있다면 File.write!대신 을 호출하도록 선택할 수 있습니다 File.write. 또는 File.write실패의 가능한 각 원인을 다르게 호출 하고 처리 하도록 선택할 수 있습니다 .

물론 rescue우리가 원한다면 예외 가 항상 가능 합니다. 그러나 유익한 반환 값을 처리하는 것과 비교할 때 나에게는 어색해 보입니다. 함수 호출이 실패하거나 실패해야한다는 것을 알고 있다면 그 실패는 예외적 인 경우가 아닙니다.

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