[exception] “try”- “catch”에서 모든 블록을 래핑해서는 안되는 이유는 무엇입니까?

나는 항상 메소드가 예외를 던질 수 있다면 의미있는 try 블록 으로이 호출을 보호하지 않는 것이 무모하다는 것을 항상 믿었습니다.

난 그냥 ‘게시 시도, 캐치 블록을 던질 수있는 통화를 포장해야합니다. 이 질문에 대해 ‘매우 나쁜 조언 ‘이라고 들었습니다. 이유를 이해하고 싶습니다.



답변

적절한 방법으로 처리 할 수있는 경우에만 메소드가 예외를 포착해야합니다.

그렇지 않으면 호출 스택을 높이는 방법이 의미가 있기를 바랍니다.

다른 사람들이 지적했듯이, 치명적인 오류가 기록되도록하려면 호출 스택의 최상위 수준에서 처리되지 않은 예외 처리기 (로깅 포함)를 사용하는 것이 좋습니다.


답변

으로 미치 다른 사람이 언급 한, 당신은 당신이 어떤 방법으로 처리 할 계획하지 않는 예외를 잡을 것이다. 응용 프로그램에서 디자인 할 때 예외를 체계적으로 처리하는 방법을 고려해야합니다. 이는 일반적으로 추상화를 기반으로 한 오류 처리 계층을 갖습니다. 예를 들어, 데이터 액세스 코드에서 모든 SQL 관련 오류를 처리하여 도메인 개체와 상호 작용하는 응용 프로그램의 일부가 해당 사실에 노출되지 않도록합니다. 어딘가에있는 DB입니다.

“모든 곳에서 잡기” 냄새 외에도 피하고 싶은 몇 가지 관련 코드 냄새가 있습니다 .

  1. “catch, log, rethrow” : 범위 기반 로깅을 원하는 경우 예외 (ala std::uncaught_exception()) 로 인해 스택이 언 롤링 될 때 소멸자에 로그 명령문을 생성하는 클래스를 작성하십시오 . 관심이있는 범위 내에서 로깅 인스턴스를 선언하고 로깅이 있으며 불필요한 try/ catch논리가 없어야 합니다.

  2. “캐치, 던지기 번역” : 일반적으로 추상화 문제를 나타냅니다. 몇 가지 특정 예외를 하나의 일반적인 예외로 변환하는 페더레이션 솔루션을 구현하지 않는 한 불필요한 추상화 계층이 있을 수 있습니다 . “내일 필요할 수도 있습니다”라고 말하지 마십시오 .

  3. “잡기, 청소, 다시 던지기” : 이것은 내 애완 동물 주인공 중 하나입니다. 이 항목이 많으면 Resource Acquisition is Initialization (초기화 기술) 기법을 적용 하고 정리 부분을 관리인 개체 인스턴스 의 소멸자에 배치해야 합니다.

try/ catch블록으로 흩어진 코드는 코드 검토 및 리팩토링을위한 좋은 대상으로 생각합니다. 예외 처리가 잘 이해되지 않았거나 코드가 am–ba가되어 리팩토링이 심각하게 필요함을 나타냅니다.


답변

다음 질문은 “예외가 있는데 다음에 무엇을해야합니까?”입니다. 무엇을 하시겠습니까? 당신이 아무것도하지 않으면-그것은 오류 숨기기이며 프로그램은 무슨 일이 있었는지 찾을 수있는 기회없이 “작동하지 않을 수 있습니다”. 예외가 발생하면 정확히 무엇을하는지 이해하고 알아야 할 경우에만 파악해야합니다.


답변

try-catch는 여전히 호출 스택 아래로 함수에서 발생하는 처리되지 않은 예외를 포착 할 수 있으므로 try-catches로 모든 블록 을 처리 할 필요는 없습니다 . 따라서 모든 함수에 try-catch가있는 것이 아니라 응용 프로그램의 최상위 논리에서 하나를 가질 수 있습니다. 예를 들어, SaveDocument()다른 메소드 등을 호출하는 많은 메소드를 호출하는 최상위 루틴 이있을 수 있습니다 .이 서브 메소드는 자체 시도 캐치가 필요하지 않습니다.SaveDocument() .

이는 세 가지 이유에서 유용합니다. 오류를보고 할 수있는 단일 장소가 SaveDocument()catch 블록 이라는 점에서 편리합니다 . 모든 하위 방법에 걸쳐이 작업을 반복 할 필요는 없습니다. 어쨌든 원하는 것은 바로 사용자에게 잘못된 일에 대한 유용한 진단을 제공하는 단일 장소입니다.

둘째, 예외가 발생할 때마다 저장이 취소됩니다. 모든 하위 메소드 try-catching에서 예외가 발생하면 해당 메소드의 catch 블록에 들어가 실행이 함수를 떠나고 통해 전달됩니다SaveDocument() . 무언가 잘못 되었다면 바로 멈추고 싶을 것입니다.

셋째, 모든 하위 메소드 는 모든 호출이 성공했다고 가정 할 수 있습니다 . 호출이 실패하면 실행이 catch 블록으로 이동하여 후속 코드가 실행되지 않습니다. 이렇게하면 코드를 훨씬 더 깨끗하게 만들 수 있습니다. 예를 들어, 오류 코드는 다음과 같습니다.

int ret = SaveFirstSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

ret = SaveSecondSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

ret = SaveThirdSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

예외로 작성하는 방법은 다음과 같습니다.

// these throw if failed, caught in SaveDocument's catch
SaveFirstSection();
SaveSecondSection();
SaveThirdSection();

이제 무슨 일이 일어나고 있는지 훨씬 명확 해졌습니다.

예외 안전 코드는 다른 방법으로 작성하기가 더 까다로울 수 있습니다. 예외가 발생하면 메모리를 유출하지 않으려 고합니다. 개체는 예외 전에 항상 소멸되므로 RAII , STL 컨테이너, 스마트 포인터 및 소멸자에서 리소스를 해제하는 기타 개체 에 대해 알고 있어야 합니다.


답변

허브 셔터는이 문제에 대해 쓴 여기 . 읽을 가치가 있습니다.
티저 :

“예외 안전 코드 작성은 기본적으로 올바른 위치에 ‘try’와 ‘catch’를 작성하는 것에 관한 것입니다.” 논의하다.

솔직히 말해서, 그 진술은 예외 안전에 대한 근본적인 오해를 반영합니다. 예외는 오류보고의 또 다른 형태 일 뿐이며, 오류 안전 코드 작성은 리턴 코드를 확인하고 오류 조건을 처리 할 수있는 곳이 아니라는 것을 확실히 알고 있습니다.

실제로, 예외 안전은 ‘try’와 ‘catch’를 작성하는 것이 거의 아니며, 더 나은 경우는 거의 없습니다. 또한 예외 안전이 코드 디자인에 영향을 미친다는 것을 잊지 마십시오. 조미료처럼 ​​몇 가지 추가 catch 문으로 개조 할 수있는 것은 결코 사후 생각이 아닙니다.


답변

다른 답변에서 언급했듯이 합리적인 오류 처리를 수행 할 수있는 경우에만 예외를 잡아야합니다.

예를 들어, 질문 을 생성 한 질문에서 질문자는 lexical_cast정수에서 문자열까지의 예외를 무시해도 안전한지 묻습니다 . 그러한 캐스트는 절대 실패해서는 안됩니다. 실패하면 프로그램에서 무언가 잘못되었습니다. 그런 상황에서 어떻게 회복 할 수 있습니까? 신뢰할 수없는 상태이기 때문에 프로그램을 죽이는 것이 가장 좋습니다. 따라서 예외를 처리하지 않는 것이 가장 안전한 방법 일 수 있습니다.


답변

예외를 던질 수있는 메소드의 호출자에서 항상 예외를 즉시 처리하면 예외는 쓸모 없게되고 오류 코드를 사용하는 것이 좋습니다.

예외는 콜 체인의 모든 메소드에서 처리 할 필요가 없다는 것입니다.