"try"- "catch"에서 모든 블록을 래핑해서는 안되는 이유는 무엇입니까?
나는 메소드가 예외를 던질 수 있다면 의미있는 try 블록 으로이 호출을 보호하지 않는 것이 무모하다는 것을 항상 믿었습니다.
난 그냥 '게시 시도, 캐치 블록을 던질 수있는 통화를 포장해야합니다. ' 이 질문에 대해 '놀랍게도 나쁜 조언 '이라고 들었습니다. 이유를 이해하고 싶습니다.
적절한 방법으로 처리 할 수있는 경우에만 메소드가 예외를 포착해야합니다.
그렇지 않으면 호출 스택을 높이는 방법이 의미가 있기를 바랍니다.
다른 사람들이 지적했듯이, 치명적 오류가 기록되도록하려면 호출 스택의 최상위 수준에서 처리되지 않은 예외 처리기 (로깅 포함)를 사용하는 것이 좋습니다.
으로 미치 와 다른 사람이 언급 한, 당신은 당신이 어떤 방법으로 처리 할 계획하지 않는 예외를 잡을 것이다. 애플리케이션을 설계 할 때 애플리케이션이 시스템 적으로 예외를 처리하는 방법을 고려해야합니다. 이는 일반적으로 추상화를 기반으로 한 오류 처리 계층을 갖습니다. 예를 들어, 데이터 액세스 코드에서 모든 SQL 관련 오류를 처리하여 도메인 개체와 상호 작용하는 응용 프로그램의 일부가 해당 사실에 노출되지 않도록합니다. 어딘가에있는 DB입니다.
"모든 곳에서 잡기" 냄새 외에도 피하고 싶은 몇 가지 관련 코드 냄새가 있습니다 .
"catch, log, rethrow" : 범위 기반 로깅을 원하는 경우 예외 (ala
std::uncaught_exception()
) 로 인해 스택이 언 롤링 될 때 소멸자에 로그 명령문을 생성하는 클래스를 작성하십시오 . 관심이있는 범위 내에서 로깅 인스턴스를 선언하기 만하면됩니다. 자, 로깅 및 불필요한try
/catch
논리가 없습니다 ."캐치, 던지기 번역" : 일반적으로 추상화 문제를 나타냅니다. 몇 가지 특정 예외를 하나의 일반적인 예외로 변환하는 페더레이션 솔루션을 구현하지 않는 한 불필요한 추상화 계층이 있을 수 있습니다 . "내일 필요할 수도 있습니다"라고 말하지 마십시오 .
"잡기, 청소, 다시 던지기" : 이것은 내 애완 동물 주인공 중 하나입니다. 이 항목이 많으면 Resource Acquisition is Initialization (초기화 기술) 기법을 적용 하고 정리 부분을 관리인 개체 인스턴스 의 소멸자에 배치해야 합니다.
try
/ catch
블록으로 흩어진 코드는 코드 검토 및 리팩토링을위한 좋은 대상으로 생각합니다. 예외 처리가 잘 이해되지 않았거나 코드가 am–ba가되어 리팩토링이 심각하게 필요함을 나타냅니다.
다음 질문은 "예외가 있는데 다음에 무엇을해야합니까?"입니다. 당신은 무엇을 할 것인가? 당신이 아무것도하지 않으면-그것은 오류 숨기기이며 프로그램은 무슨 일이 있었는지 찾을 수있는 기회없이 "작동하지 않을 수 있습니다". 예외가 발생하면 정확히 무엇을하는지 이해하고 알아야 할 경우에만 파악해야합니다.
try-catch는 여전히 호출 스택 아래로 함수에서 발생 된 처리되지 않은 예외를 포착 할 수 있기 때문에 try-catches로 모든 블록 을 처리 할 필요는 없습니다 . 따라서 모든 함수에 try-catch가있는 것이 아니라 응용 프로그램의 최상위 논리에서 하나를 가질 수 있습니다. 예를 들어, SaveDocument()
다른 메소드 등을 호출하는 많은 메소드를 호출 하는 최상위 레벨 루틴 이있을 수 있습니다 .이 서브 메소드에는 자체 시도 캐치가 필요하지 않습니다. 던질 경우 여전히 캐치에 걸리기 때문 SaveDocument()
입니다.
이는 세 가지 이유에서 유용합니다. 오류를보고 할 수있는 단일 장소 SaveDocument()
(캐치 블록)가 있으므로 편리합니다 . 모든 하위 방법에서이 작업을 반복 할 필요가 없으며, 원하는 방식으로 사용자에게 잘못된 문제에 대한 유용한 진단을 제공 할 수있는 단일 장소입니다.
둘째, 예외가 발생할 때마다 저장이 취소됩니다. 모든 하위 메소드 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 컨테이너, 스마트 포인터 및 소멸자에서 리소스를 해제하는 기타 개체 에 대해 알고 있어야 합니다.
Herb Sutter는이 문제에 대해 여기에 썼습니다 . 읽을 가치가 있습니다.
티저 :
"예외 안전 코드 작성은 기본적으로 올바른 위치에 'try'와 'catch'를 작성하는 것에 관한 것입니다." 논의하다.
솔직히 말해서, 그 진술은 예외 안전에 대한 근본적인 오해를 반영합니다. 예외는 오류보고의 또 다른 형태 일 뿐이며, 오류 안전 코드 작성은 리턴 코드를 확인하고 오류 조건을 처리 할 수있는 곳이 아니라는 것을 확실히 알고 있습니다.
실제로, 예외 안전은 'try'와 'catch'를 작성하는 것에 대한 경우가 거의 없으며, 더 나은 경우는 거의 없습니다. 또한 예외 안전이 코드 디자인에 영향을 미친다는 것을 잊지 마십시오. 조미료처럼 몇 가지 추가 catch 문으로 개조 할 수있는 것은 결코 사후 생각이 아닙니다.
다른 답변에서 언급했듯이 합리적인 오류 처리를 할 수있는 경우에만 예외를 잡아야합니다.
예를 들어, 질문 을 생성 한 질문에서 질문자는 lexical_cast
정수에서 문자열까지의 예외를 무시해도 안전한지 묻습니다 . 그러한 캐스트는 절대 실패해서는 안됩니다. 실패하면 프로그램에서 무언가 잘못되었습니다. 그러한 상황에서 어떻게 회복 할 수 있습니까? 신뢰할 수없는 상태이기 때문에 프로그램을 죽이는 것이 가장 좋습니다. 따라서 예외를 처리하지 않는 것이 가장 안전한 방법 일 수 있습니다.
예외를 던질 수있는 메소드의 호출자에서 항상 예외를 즉시 처리하면 예외는 쓸모 없게되고 오류 코드를 사용하는 것이 좋습니다.
예외는 콜 체인의 모든 메소드에서 처리 할 필요가 없다는 것입니다.
제가 들었던 가장 좋은 조언은 예외적 인 조건에 대해 현명하게 무언가를 할 수있는 지점에서만 예외를 잡아야하며 "캐치, 로그 및 릴리스"가 좋은 전략이 아니라는 것입니다 (라이브러리에서 가끔 피할 수없는 경우).
가장 낮은 수준에서 가능한 많은 예외를 처리하기 위해 귀하의 질문의 기본 방향에 동의합니다.
기존 답변 중 일부는 "예외를 처리 할 필요가 없습니다. 다른 사람이 스택에서 처리합니다." 내 경험 에 따르면 현재 개발 된 코드 조각에서 예외 처리를 생각하지 않고 다른 사람의 문제를 예외 처리 하는 것은 나쁜 변명 입니다.
이 문제는 분산 개발에서 급격히 커져 동료가 구현 한 메서드를 호출해야 할 수 있습니다. 그런 다음 중첩 된 메소드 호출 체인을 검사하여 왜 예외가 발생했는지 확인해야합니다.이 메소드는 가장 깊은 중첩 된 메소드에서 훨씬 쉽게 처리 할 수 있습니다.
컴퓨터 과학 교수가 한 번 알려 준 조언은 "표준 수단으로는 오류를 처리 할 수없는 경우에만 Try and Catch 블록을 사용하십시오."
예를 들어, 그는 프로그램이 다음과 같은 일을 할 수없는 곳에서 심각한 문제가 발생하면 다음과 같이 말했습니다.
int f()
{
// Do stuff
if (condition == false)
return -1;
return 0;
}
int condition = f();
if (f != 0)
{
// handle error
}
그런 다음 try, catch 블록을 사용해야합니다. 예외를 사용하여이를 처리 할 수 있지만 예외는 성능이 비싸기 때문에 일반적으로 권장되지 않습니다.
모든 함수의 결과를 테스트하려면 리턴 코드를 사용하십시오.
예외의 목적은 결과를 자주 테스트 할 수 있도록하는 것입니다. 아이디어는 더 일반적인 코드에서 예외적 인 (특별하고 드문) 조건을 분리하는 것입니다. 이것은 일반적인 코드를 더 깨끗하고 단순하게 유지하지만 여전히 예외적 인 조건을 처리 할 수 있습니다.
잘 설계된 코드에서는 더 깊은 함수가 발생하고 더 높은 함수가 잡힐 수 있습니다. 그러나 핵심은 "사이에있는"많은 기능이 예외적 인 조건을 처리해야하는 부담에서 벗어날 수 있다는 것입니다. 그것들은 "예외 안전"이어야만하므로 반드시 붙잡아 야한다는 의미는 아닙니다.
위의 조언 외에도 개인적으로 try + catch + throw를 사용합니다. 다음과 같은 이유로 :
- 다른 코더의 경계에서 다른 사람이 작성한 호출자에게 예외가 발생하기 전에 내 자신이 작성한 코드에서 try + catch + throw를 사용하면 내 코드에서 발생한 오류 조건을 알 수 있습니다. 이 장소는 코드에 훨씬 더 가깝기 때문에 처음에는 예외를 던지고, 더 가깝고, 이유를 찾기가 더 쉽습니다.
- 모듈의 경계에서 다른 모듈이 같은 사람으로 작성 될 수 있습니다.
- 학습 + 디버그 목적,이 경우 C ++에서 catch (...)를 사용하고 C #에서 catch (Exception ex)를 사용합니다. C ++의 경우 표준 라이브러리가 너무 많은 예외를 throw하지 않으므로이 경우는 C ++에서는 드 in니다. 그러나 C #의 일반적인 장소 인 C #에는 거대한 라이브러리와 성숙한 예외 계층 구조가 있으며 C # 라이브러리 코드는 많은 예외를 던집니다. 이론에서 I (및 당신)는 호출 한 함수의 모든 예외를 알아야하며 이유 / 이유를 알아야합니다. 이러한 예외가 발생하고이를 적절하게 처리 (통과 또는 적절하게 처리 및 처리)하는 방법을 알고 있습니다. 불행히도 실제로 한 줄의 코드를 작성하기 전에 잠재적 예외에 대한 모든 것을 아는 것은 매우 어렵습니다. 따라서 예외가 발생할 때 로깅 (제품 환경에서) / 어설 션 대화 상자 (개발 환경에서)를 통해 모두를 잡아 내 코드를 크게 말하게합니다. 이런 식으로 예외 처리 코드를 점진적으로 추가합니다. 나는 그것이 좋은 조언과 충돌한다는 것을 알고 있지만 실제로 그것은 나에게 효과적 이며이 문제에 대한 더 좋은 방법을 모른다.
이 토론에 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가 차단 된 상태로 계측되었으며 메소드 이름, 오류 메시지, 오류 메시지, 앱 이름, 날짜와 함께 전달 된 문자열에 연결된 메소드 매개 변수 값으로 실패 시점에 모든 것이 기록되었습니다. 그리고 버전. 이 정보를 통해 개발자는 오류에 대한 분석을 실행하여 가장 많이 발생하는 예외를 식별 할 수 있습니다! 또는 오류 수가 가장 많은 네임 스페이스. 또한 모듈에서 발생하는 오류가 올바르게 처리되고 여러 가지 이유로 인한 것이 아닌지 확인할 수 있습니다.
이것의 또 다른 장점은 개발자가 오류 로깅 방법에서 하나의 중단 점을 설정하고 하나의 중단 점과 "스텝 아웃"디버그 버튼을 한 번 클릭하면 실제에 대한 전체 액세스에 실패한 방법에 있다는 것입니다. 고장 시점의 물체는 즉시 창에서 편리하게 사용할 수 있습니다. 디버깅이 매우 쉬우 며 실행을 메소드의 시작 부분으로 끌어서 문제점을 복제하여 정확한 행을 찾을 수 있습니다. 중앙 집중식 예외 처리를 통해 개발자가 30 초 내에 예외를 복제 할 수 있습니까? 아니.
"방법은 합리적인 방법으로 처리 할 수있을 때만 예외를 잡아야합니다." 이는 개발자가 출시 전에 발생할 수있는 모든 오류를 예측하거나 발생할 수 있음을 의미합니다. 이것이 최상위 수준 인 경우 앱 예외 처리기가 필요하지 않으며 Elastic Search 및 logstash에 대한 시장이 없습니다.
이 접근 방식을 통해 개발자는 프로덕션에서 간헐적 인 문제를 찾아 수정할 수 있습니다! 프로덕션 환경에서 디버거없이 디버깅 하시겠습니까? 아니면 화가 난 사용자로부터 전화를 받고 전자 메일을 받으시겠습니까? 이를 통해 다른 사람이 알기 전에 문제를 해결할 수 있으며 문제를 해결하는 데 필요한 모든 것이 있으므로 이메일, IM 또는 Slack을 지원하지 않아도됩니다. 문제의 95 %를 재현 할 필요가 없습니다.
제대로 작동하려면 네임 스페이스 / 모듈, 클래스 이름, 메서드, 입력 및 오류 메시지를 캡처하고 데이터베이스에 저장할 수있는 중앙 집중식 로깅과 결합해야합니다. 먼저 고정.
때때로 개발자는 catch 블록에서 스택을 예외로 처리하려고하지만이 방법은 일반 코드보다 100 배 느립니다. 로깅을 통한 캐치 및 릴리스가 선호됩니다.
이 기술은 2 년 동안 12 명의 개발자가 개발 한 Fortune 500 대 기업에서 대부분의 사용자에게 1 시간마다 실패한 앱을 빠르게 안정화하는 데 사용되었습니다. 이 3000 가지 다른 예외를 사용하여 4 개월 내에 식별, 수정, 테스트 및 배포되었습니다. 이는 평균적으로 4 개월 동안 평균 15 분마다 수정됩니다.
코드를 작성하는 데 필요한 모든 것을 입력하는 것이 재미 없으며 반복적 인 코드를 보지 않는 것을 선호하지만 각 방법에 4 줄의 코드를 추가하는 것이 장기적으로 가치가 있습니다.
Mike Wheat의 답변이 주요 요점을 잘 요약했지만 다른 답변을 추가해야한다고 생각합니다. 나는 이것을 이렇게 생각합니다. 여러 가지 작업을 수행하는 메소드가있는 경우 추가하지 않고 복잡성을 곱합니다.
다시 말해, try catch에 싸여있는 방법에는 두 가지 가능한 결과가 있습니다. 예외가 아닌 결과와 예외 결과가 있습니다. 많은 방법을 다룰 때 이것은 이해를 넘어 기하 급수적으로 증가합니다.
기하 급수적으로 각 방법이 서로 다른 두 가지 방식으로 분기되면 다른 방법을 호출 할 때마다 이전 수의 잠재적 결과가 제곱되기 때문입니다. 5 가지 방법을 호출 할 때 최소 256 가지의 가능한 결과를 얻을 수 있습니다. 모든 단일 방법에서 try / catch를 수행 하지 않는 것과 이것을 비교하면 하나의 경로 만 따라야합니다.
그것이 기본적으로 내가 보는 방법입니다. 응용 프로그램의 상태가 기본적으로 정의되지 않기 때문에 모든 유형의 분기가 동일한 작업을 수행하지만 try / catch는 특별한 경우라고 주장 할 수 있습니다.
간단히 말해 try / catch는 코드를 이해하기 훨씬 어렵게 만듭니다.
내부 코드의 모든 부분을 다룰 필요는 없습니다 try-catch
. try-catch
블록 의 주요 용도는 오류 처리 및 프로그램에 버그 / 예외가있는 것입니다. 일부 사용법 try-catch
-
- 예외를 처리하려는 위치에서이 블록을 사용하거나 작성된 코드 블록이 예외를 발생시킬 수 있다고 간단히 말할 수 있습니다.
- 사용 직후 객체를 폐기하려면
try-catch
블록 을 사용할 수 있습니다 .
참고 URL : https://stackoverflow.com/questions/2737328/why-should-i-not-wrap-every-block-in-try-catch
'development' 카테고리의 다른 글
파이썬에서 숫자를 어떻게 반올림합니까? (0) | 2020.02.19 |
---|---|
Android에서 진행률 표시 줄의 진행 색상을 변경하는 방법 (0) | 2020.02.19 |
동적 라이브러리와 정적 라이브러리를 사용하는 경우 (0) | 2020.02.19 |
파이썬에 왜 ++와-연산자가 없는가? (0) | 2020.02.19 |
여러 공간을 단일 공간으로 대체하는 정규식 (0) | 2020.02.18 |