일부 언어 (예 : 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.
}
(디버그에서만 활성화되는 유사한 매크로가 있습니다).
프로덕션에서는 구성 파일이 존재하지 않으므로 클라이언트는이 매크로의 결과를 볼 수 없습니다. 그러나 필요할 때 활성화하는 것은 쉽습니다.
결론
반환 코드를 사용하여 코드를 작성할 때 실패에 대비하고 테스트 요새가 충분히 안전하기를 바랍니다.
예외를 사용하여 코드를 작성할 때 코드가 실패 할 수 있음을 알고 일반적으로 코드에서 선택한 전략적 위치에 반격을가합니다. 그러나 일반적으로 코드는 "해야 할 일"보다 "내가 두려워하는 일"에 대한 것입니다.
그러나 코드를 작성할 때는 최선의 도구를 사용해야하며 때로는 "오류를 숨기지 않고 가능한 한 빨리 표시"하는 경우가 있습니다. 위에서 언급 한 매크로는이 철학을 따릅니다.