나는 항상 메소드가 예외를 던질 수 있다면 의미있는 try 블록 으로이 호출을 보호하지 않는 것이 무모하다는 것을 항상 믿었습니다.
난 그냥 '게시 시도, 캐치 블록을 던질 수있는 통화를 포장해야합니다. ' 이 질문에 대해 '매우 나쁜 조언 '이라고 들었습니다. 이유를 이해하고 싶습니다.
나는 항상 메소드가 예외를 던질 수 있다면 의미있는 try 블록 으로이 호출을 보호하지 않는 것이 무모하다는 것을 항상 믿었습니다.
난 그냥 '게시 시도, 캐치 블록을 던질 수있는 통화를 포장해야합니다. ' 이 질문에 대해 '매우 나쁜 조언 '이라고 들었습니다. 이유를 이해하고 싶습니다.
답변:
적절한 방법으로 처리 할 수있는 경우에만 메소드가 예외를 포착해야합니다.
그렇지 않으면 호출 스택을 높이는 방법이 의미가 있기를 바랍니다.
다른 사람들이 지적했듯이, 치명적인 오류가 기록되도록하려면 호출 스택의 최상위 수준에서 처리되지 않은 예외 처리기 (로깅 포함)를 사용하는 것이 좋습니다.
try
블록 생성 비용 (생성 된 코드 측면에서)이 있음을 주목할 가치가 있습니다 . Scott Meyers의 "더 효과적인 C ++"에 대한 좋은 토론이 있습니다.
try
블록은 최신 C 컴파일러에서 무료이며 정보는 Nick입니다. 또한 지역 정보 (실제로 명령이 실패한 곳)를 잃어 버렸기 때문에 최상위 예외 처리기에 동의하지 않습니다.
terminate
) . 안전 메커니즘에 가깝습니다. 또한 try/catch
예외가없는 경우 거의 무료입니다. 전파하는 것이있을 때마다 던져지고 잡힐 때마다 시간이 소비되므로, try/catch
그 재연 의 체인은 비용이 들지 않습니다.
으로 미치 와 다른 사람이 언급 한, 당신은 당신이 어떤 방법으로 처리 할 계획하지 않는 예외를 잡을 것이다. 응용 프로그램에서 디자인 할 때 예외를 체계적으로 처리하는 방법을 고려해야합니다. 이는 일반적으로 추상화를 기반으로 한 오류 처리 계층을 갖습니다. 예를 들어, 데이터 액세스 코드에서 모든 SQL 관련 오류를 처리하여 도메인 개체와 상호 작용하는 응용 프로그램의 일부가 해당 사실에 노출되지 않도록합니다. 어딘가에있는 DB입니다.
"모든 곳에서 잡기" 냄새 외에도 피하고 싶은 몇 가지 관련 코드 냄새가 있습니다 .
"catch, log, rethrow" : 범위 기반 로깅을 원하는 경우 예외 (ala std::uncaught_exception()
) 로 인해 스택이 언 롤링 될 때 소멸자에 로그 명령문을 생성하는 클래스를 작성하십시오 . 관심이있는 범위 내에서 로깅 인스턴스를 선언하고 로깅이 있으며 불필요한 try
/ catch
논리가 없어야 합니다.
"캐치, 던지기 번역" : 일반적으로 추상화 문제를 나타냅니다. 몇 가지 특정 예외를 하나의 일반적인 예외로 변환하는 페더레이션 솔루션을 구현하지 않는 한 불필요한 추상화 계층이 있을 수 있습니다 . "내일 필요할 수도 있습니다"라고 말하지 마십시오 .
"잡기, 청소, 다시 던지기" : 이것은 내 애완 동물 주인공 중 하나입니다. 이 항목이 많으면 Resource Acquisition is Initialization (초기화 기술) 기법을 적용 하고 정리 부분을 관리인 개체 인스턴스 의 소멸자에 배치해야 합니다.
try
/ catch
블록으로 흩어진 코드는 코드 검토 및 리팩토링을위한 좋은 대상으로 생각합니다. 예외 처리가 잘 이해되지 않았거나 코드가 am–ba가되어 리팩토링이 심각하게 필요함을 나타냅니다.
Logger
것과 유사한 클래스를 사용하고 log4j.Logger
예외가 활성화되었을 때 소멸자에서 경고를 생성했습니다.
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
블록이 모든 약간 다른 그들은 모두 같은 끝나야 현실에서 약간 다른 메시지와 함께 몇 가지 오류의 순열, 최대 플래그 시도 : 거래 나 프로그램이 실패하고 종료! 예외적 인 실패가 발생하면 대부분의 사용자는 10 가지 수준의 메시지를 처리하지 않고 혼자 할 수있는 것을 구하고 싶어합니다.
허브 셔터는이 문제에 대해 쓴 여기 . 읽을 가치가 있습니다.
티저 :
"예외 안전 코드 작성은 기본적으로 올바른 위치에 'try'와 'catch'를 작성하는 것에 관한 것입니다." 논의하다.
솔직히 말해서, 그 진술은 예외 안전에 대한 근본적인 오해를 반영합니다. 예외는 오류보고의 또 다른 형태 일 뿐이며, 오류 안전 코드 작성은 리턴 코드를 확인하고 오류 조건을 처리 할 수있는 곳이 아니라는 것을 확실히 알고 있습니다.
실제로, 예외 안전은 'try'와 'catch'를 작성하는 것이 거의 아니며, 더 나은 경우는 거의 없습니다. 또한 예외 안전이 코드 디자인에 영향을 미친다는 것을 잊지 마십시오. 조미료처럼 몇 가지 추가 catch 문으로 개조 할 수있는 것은 결코 사후 생각이 아닙니다.
제가 들었던 가장 좋은 조언은 예외적 인 조건에 대해 현명하게 무언가를 할 수있는 지점에서만 예외를 잡아야하며 "캐치, 로그 및 릴리스"가 좋은 전략이 아니라는 것입니다 (라이브러리에서 가끔 피할 수없는 경우).
가장 낮은 수준에서 가능한 많은 예외를 처리하기 위해 귀하의 질문의 기본 방향에 동의합니다.
기존 답변 중 일부는 "예외를 처리 할 필요가 없습니다. 다른 사람이 스택에서 처리합니다." 내 경험 에 따르면 현재 개발 된 코드에서 예외 처리를 생각하지 않고 다른 사람의 문제를 예외 처리 하는 것은 나쁜 변명 입니다.
이 문제는 분산 개발에서 급격히 커져 동료가 구현 한 메서드를 호출해야 할 수 있습니다. 그런 다음 중첩 된 메소드 호출 체인을 검사하여 왜 예외가 발생했는지 파악해야합니다.이 메소드는 가장 깊은 중첩 된 메소드에서 훨씬 쉽게 처리 할 수 있습니다.
컴퓨터 과학 교수가 한 번 알려준 조언은 "표준 수단으로는 오류를 처리 할 수없는 경우에만 Try and Catch 블록을 사용하십시오."
예를 들어, 그는 프로그램이 다음과 같은 일을 할 수없는 곳에서 심각한 문제가 발생하면 다음과 같이 말했습니다.
int f()
{
// Do stuff
if (condition == false)
return -1;
return 0;
}
int condition = f();
if (f != 0)
{
// handle error
}
그런 다음 try, catch 블록을 사용해야합니다. 예외를 사용하여이를 처리 할 수 있지만 예외는 성능이 비싸기 때문에 일반적으로 권장되지 않습니다.
모든 함수의 결과를 테스트하려면 리턴 코드를 사용하십시오.
예외의 목적은 결과를 자주 테스트 할 수 있도록하는 것입니다. 이 아이디어는 더 일반적인 코드에서 예외적 인 (특별하고 드문) 조건을 분리하는 것입니다. 이것은 일반적인 코드를 더 깨끗하고 단순하게 유지하지만 여전히 예외적 인 조건을 처리 할 수 있습니다.
잘 설계된 코드에서는 더 깊은 함수가 발생하고 더 높은 함수가 잡힐 수 있습니다. 그러나 핵심은 "사이에있는"많은 기능이 예외적 인 조건을 처리해야하는 부담에서 벗어날 수 있다는 것입니다. 그것들은 "예외 안전"이어야만하므로 반드시 붙잡아 야한다는 의미는 아닙니다.
이 토론에 C ++ 11부터 모든 catch
블록 rethrow
이 처리 할 수 있거나 처리해야 할 시점까지 예외가 발생하는 한 많은 의미가 있음을이 토론에 추가하고 싶습니다 . 이 방법 으로 역 추적을 생성 할 수 있습니다 . 따라서 이전 의견은 부분적으로 구식이라고 생각합니다.
std::nested_exception
및std::throw_with_nested
여기 및 여기 에 StackOverflow에 설명되어 있습니다 .
파생 된 예외 클래스로이 작업을 수행 할 수 있으므로 이러한 역 추적에 많은 정보를 추가 할 수 있습니다! 역 추적이 다음과 같은 GitHub 에서 내 MWE를 살펴볼 수도 있습니다 .
Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"
여러 프로젝트를 구제 할 수있는 "기회"가 주어졌고 앱에 오류가 너무 많고 사용자가 문제와 해결 방법에 지 쳤기 때문에 경영진이 전체 개발 팀을 교체했습니다. 이 코드베이스는 모두 최상위 투표 답변이 설명하는 것처럼 앱 수준에서 중앙 집중식 오류 처리를했습니다. 그 대답이 최선의 방법이라면 왜 효과가 없었으며 이전 개발자 팀이 문제를 해결할 수 있었습니까? 아마도 때때로 작동하지 않습니까? 위의 답변은 개발자가 단일 문제를 해결하는 데 시간이 얼마나 걸리는지 언급하지 않습니다. 문제를 해결하는 시간이 핵심 측정 기준 인 경우 try..catch 블록을 사용하여 계측 코드를 작성하는 것이 좋습니다.
팀이 UI를 크게 변경하지 않고 어떻게 문제를 해결 했습니까? 간단하고 모든 메소드는 try..catch가 차단되어 계측되고 메소드 이름, 오류 메시지, 오류 메시지, 앱 이름, 날짜와 함께 전달 된 문자열로 연결된 메소드 매개 변수 값으로 실패 시점에 모든 것이 기록됩니다. 그리고 버전. 이 정보를 통해 개발자는 오류에 대한 분석을 실행하여 가장 많이 발생하는 예외를 식별 할 수 있습니다! 또는 오류 수가 가장 많은 네임 스페이스. 또한 모듈에서 발생하는 오류가 올바르게 처리되고 여러 가지 이유로 인한 것이 아닌지 확인할 수 있습니다.
이것의 또 다른 장점은 개발자가 오류 로깅 방법에서 하나의 중단 점을 설정하고 하나의 중단 점과 "step out"디버그 버튼을 한 번만 클릭하면 실제에 대한 전체 액세스에 실패한 방법에 있다는 것입니다. 고장 시점의 물체는 즉시 창에서 편리하게 사용할 수 있습니다. 디버깅이 매우 쉬우 며 실행을 메소드의 시작 부분으로 끌어서 문제점을 복제하여 정확한 행을 찾을 수 있습니다. 중앙 집중식 예외 처리를 통해 개발자가 30 초 내에 예외를 복제 할 수 있습니까? 아니.
"방법은 합리적인 방법으로 처리 할 수있을 때만 예외를 잡아야합니다." 이는 개발자가 출시 전에 발생할 수있는 모든 오류를 예측하거나 발생할 수 있음을 의미합니다. 이것이 최상위 수준 인 경우 앱 예외 처리기가 필요하지 않으며 Elastic Search 및 logstash에 대한 시장이 없습니다.
이 접근 방식을 통해 개발자는 프로덕션에서 간헐적 인 문제를 찾아 수정할 수 있습니다! 프로덕션 환경에서 디버거없이 디버깅 하시겠습니까? 아니면 화가 난 사용자로부터 전화를 받고 전자 메일을 받으시겠습니까? 이를 통해 다른 사람이 알기 전에 문제를 해결할 수 있으며 문제를 해결하는 데 필요한 모든 것이 있으므로 이메일, IM 또는 Slack을 지원하지 않아도됩니다. 이슈의 95 %를 재현 할 필요가 없습니다.
제대로 작동하려면 네임 스페이스 / 모듈, 클래스 이름, 메서드, 입력 및 오류 메시지를 캡처하고 데이터베이스에 저장할 수있는 중앙 집중식 로깅과 결합해야합니다. 먼저 고정.
때때로 개발자는 catch 블록에서 스택을 예외로 처리하려고하지만이 방법은 일반 코드보다 100 배 느립니다. 로깅을 통한 캐치 및 릴리스가 선호됩니다.
이 기술은 2 년 동안 12 명의 개발자가 개발 한 Fortune 500 대 기업에서 대부분의 사용자에게 1 시간마다 실패한 앱을 빠르게 안정화하는 데 사용되었습니다. 이 3000 가지 다른 예외를 사용하여 4 개월 만에 식별, 수정, 테스트 및 배포되었습니다. 평균적으로 4 개월 동안 평균 15 분마다 수정됩니다.
코드를 작성하는 데 필요한 모든 것을 입력하는 것이 재미없고 반복 코드를 보지 않는 것을 선호하지만 각 방법에 4 줄의 코드를 추가하는 것이 장기적으로 가치가 있습니다.
위의 조언 외에도 개인적으로 try + catch + throw를 사용합니다. 다음과 같은 이유로 :
Mike Wheat의 대답이 주요 요점을 잘 요약했지만 다른 대답을 추가해야한다고 생각합니다. 나는 이것을 이렇게 생각합니다. 여러 가지 작업을 수행하는 메소드가있는 경우 추가하지 않고 복잡성을 곱합니다.
다시 말해, try catch에 싸여있는 방법에는 두 가지 가능한 결과가 있습니다. 예외가 아닌 결과와 예외 결과가 있습니다. 많은 방법을 다룰 때 이것은 이해를 넘어 기하 급수적으로 증가합니다.
기하 급수적으로 각 방법이 서로 다른 두 가지 방식으로 분기되는 경우 다른 방법을 호출 할 때마다 이전 수의 잠재적 결과가 제곱되기 때문입니다. 다섯 가지 방법을 호출 할 때 최소 256 가지의 가능한 결과를 얻을 수 있습니다. 모든 단일 방법에서 try / catch를 수행 하지 않는 것과 이것을 비교하면 하나의 경로 만 따라야합니다.
그것이 기본적으로 내가 보는 방법입니다. 응용 프로그램의 상태가 기본적으로 정의되지 않기 때문에 모든 유형의 분기가 동일한 작업을 수행하지만 try / catch는 특별한 경우라고 주장 할 수 있습니다.
간단히 말해 try / catch는 코드를 이해하기 훨씬 어렵게 만듭니다.
내부 코드의 모든 부분을 다룰 필요는 없습니다 try-catch
. try-catch
블록 의 주요 용도는 오류 처리 및 프로그램에 버그 / 예외가있는 것입니다. 일부 사용법 try-catch
-
try-catch
블록 을 사용할 수 있습니다 .try
/ catch
는 그것과 완전히 분리 / 직교 적입니다. 더 작은 범위의 개체를 처리하려면, 당신은 단지 새로운를 열 수 없습니다 { Block likeThis; /* <- that object is destroyed here -> */ }
-이 포장 할 필요 try
/ catch
당신이 실제로 필요하지 않는 한 catch
물론, 아무것도.