예외 전파 : 언제 예외를 포착해야합니까?


44

MethodA는 MethodB를 호출하여 MethodC를 호출합니다.

MethodB 또는 MethodC에는 예외 처리가 없습니다. 그러나 MethodA에는 예외 처리가 있습니다.

MethodC에서는 예외가 발생합니다.

이제 예외는 MethodA까지 버블 링되어 적절하게 처리됩니다.

이것에 무슨 문제가 있습니까?

내 마음에, 어떤 시점에서 호출자는 MethodB 또는 MethodC를 실행할 것이고, 그 메소드에서 예외가 발생하면 해당 메소드 내에서 예외를 처리하여 얻을 수있는 것은 본질적으로 그냥 let 대신에 try / catch / finally 블록입니다. 그들은 수신자에게 버블 링?

예외 처리에 대한 진술 또는 합의는 예외로 인해 실행을 계속할 수 없을 때 발생하는 것입니다. 나는 그것을 얻는다. 그러나 try / catch 블록을 완전히 없애는 대신 체인에서 예외를 잡으십시오.

리소스를 확보해야 할 때 이해합니다. 그것은 완전히 다른 문제입니다.


46
컨센서스가 캐치 스루 캐치 체인을 갖는 이유는 무엇이라고 생각하십니까?
Caleth

좋은 IDE와 적절한 코딩 스타일을 사용하면 메소드가 호출 될 때 일부 예외가 발생할 수 있음을 알 수 있습니다. 처리하거나 전파하도록 허용하는 것은 발신자의 결정입니다. 나는 이것에 아무런 문제가 없다.
Hieu Le

14
메소드가 예외를 처리 할 수없고 단순히 다시 던지면 코드 냄새라고 말할 수 있습니다. 메소드가 예외를 처리 할 수없고 예외가 발생할 때 다른 작업을 수행 할 필요가 없으면 try-catch블록 이 전혀 필요하지 않습니다 .
Greg Burghardt

7
"무엇이 잘못 되었나요?" : 아무것도
Ewan

5
패스 스루 캐치 (다른 유형의 예외 또는 이와 유사한 것을 랩핑하지 않는)는 예외의 전체 목적을 무효화합니다. 예외 던지는 것은 복잡한 메커니즘이며 의도적으로 만들어졌습니다. 패스 스루 캐치가 사용 된 유스 케이스 인 경우 Result<T>유형 (계산 결과 또는 오류를 저장하는 유형 )을 구현하고 던지는 함수에서 반환하면됩니다. 스택에 오류를 전파하면 모든 반환 값을 읽고, 오류가 있는지 확인하고, 그렇다면 오류를 반환해야합니다.
Alexander

답변:


139

일반적으로 예외 처리 방법을 모른다면 예외를 포착하지 마십시오. MethodC가 예외를 발생 시키지만 MethodB가이를 처리하는 유용한 방법이 없다면, 예외가 MethodA까지 전파 될 수 있도록해야합니다.

메소드가 catch 및 rethrow 메커니즘을 가져야하는 유일한 이유는 다음과 같습니다.

  • 하나의 예외를 위의 호출자에게 더 의미있는 다른 예외로 변환하려고합니다.
  • 예외에 추가 정보를 추가하려고합니다.
  • 유출되지 않은 리소스를 정리하려면 catch 절이 필요합니다.

그렇지 않으면, 잘못된 레벨에서 예외를 발견하면 호출 코드 (및 궁극적으로 소프트웨어 사용자)에게 유용한 피드백을 제공하지 않고 코드가 자동으로 실패하는 경향이 있습니다. 예외를 잡아서 즉시 다시 던지는 대안은 의미가 없습니다.


28
@GregBurghardt 언어에 비슷한 것이 있다면 그것을 try ... finally ...사용하고 다시 던지지 말고 사용하십시오
Caleth

19
"예외를 잡아서 즉시 다시 던지는 것은 의미가 없습니다" 이것을 시도하는 사람들은 종종 원래 스택 추적과 같은 예외에 대한 많은 정보를 제거합니다. 호출자가 예외가 발생하여 발생하는 상황과 위치에 대해 완전히 오해하는 코드를 처리했습니다.
JimmyJames

7
"무엇을 처리해야할지 모른다면 예외를 포착하지 마십시오". 언뜻보기에는 합리적으로 들리지만 나중에 문제가 발생합니다. 여기서하는 일은 발신자에게 구현 세부 정보를 유출하는 것입니다. 구현시 특정 ORM을 사용하여 데이터를로드한다고 가정하십시오. ORM의 특정 예외를 파악하지 못하고 거품을 내버려두면 기존 사용자와의 호환성을 유지하지 않고 데이터 계층을 교체 할 수 없습니다. 그것은 더 명백한 경우 중 하나이지만, 그것은 교활하고 어려워 질 수 있습니다.
Voo

11
@Voo 당신의 예에서 당신 그것으로 무엇을 해야할지 알고 있습니다. 예를 들어 코드에 문서화 된 예외 특정에 랩 LoadDataException원래의 예외 세부 사항을 포함 , 언어 기능에 따라 그래서 미래의 메인테이너이 문제를 재현하는 방법을 디버거와 그림을 첨부하지 않고 근본 원인을 볼 수 있음.
콜린 영

14
@Voo 당신은 catch / rethrow 시나리오에 대한 "한 예외를 위의 호출자에게 더 의미있는 다른 예외로 변환하고 싶다"고 생각하지 않은 것 같습니다.
jpmc26

21

이것에 무슨 문제가 있습니까?

물론 아무것도 아닙니다.

이제 예외는 MethodA까지 버블 링되어 적절하게 처리됩니다.

"적절하게 처리"는 중요한 부분입니다. 이것이 구조적 예외 처리의 핵심입니다.

코드가 예외와 함께 "유용한"무언가를 할 수 있다면 그것을 찾으십시오. 그렇지 않다면 혼자 내버려 두십시오.

. . . try / catch 블록을 끝까지 사용하는 대신 체인에서 예외를 잡으십시오.

그것이 바로 당신이 해야 할 일입니다. 핸들러 / 리 로더가 "완전히 아래에있는"코드를 읽는다면, 아마도 꽤 나쁜 코드를 읽는 것입니다.

안타깝게도 일부 개발자는 캐치 블록을 "보일러 플레이트"코드로 인식합니다. 예외 처리를 실제로 "받지"않고 무언가를 추가해야한다고 생각하기 때문 예외는 "탈출"하지 않고 프로그램을 종료합니다.

여기 어려움의 일부는,이다 대부분 의 시간을이 문제는 심지어 그들이 경우, 예외는 항상 슬로우되지 않기 때문에, 눈치 만되지 않습니다 이다 , 프로그램 시간이 엄청 많이 낭비가는됩니다 노력은 서서히 스택 스택을 선택 해제하여 실제로 예외와 함께 유용한 기능을 수행하는 어딘가에 도달합니다.


7
더 나쁜 것은 응용 프로그램이 예외를 포착 한 다음 기록하고 (영원히 거기에 영원히 머 무르지 않는 경우) 실제로 기록 할 수없는 경우에도 평소대로 계속하려고 시도하는 경우입니다.
솔로몬 유코

1
@SolomonUcko : 글쎄요. 예를 들어 간단한 RPC 서버를 작성하고 처리되지 않은 예외가 기본 이벤트 루프까지 계속 발생하는 경우 합리적인 옵션은 서버를 로깅하고 원격 피어에 RPC 오류를 보내고 이벤트 처리를 재개하는 것입니다. 다른 대안은 전체 프로그램을 종료하는 것인데, 서버가 프로덕션 환경에서 죽을 때 SRE 너트를 구동시킵니다.
케빈

@Kevin이 경우 catch에는 오류를 기록하고 오류 응답을 반환하는 최상위 수준 의 단일 항목이 있어야합니다 . catch어디에나 뿌리는 블록이 아닙니다 . 가능한 모든 확인 된 예외 (Java와 같은 언어로)를 나열하고 싶지 않다면, RuntimeException로깅하지 않고 계속 시도하고 더 많은 오류나 취약성에 빠지는 대신 랩핑하십시오 .
Solomon Ucko

8

라이브러리와 응용 프로그램간에 차이를 만들어야합니다.

라이브러리는 포착되지 않은 예외를 자유롭게 던질 수 있습니다

라이브러리를 디자인 할 때 어떤 시점에서 무엇이 잘못 될 수 있는지 생각해야합니다. 매개 변수의 범위가 잘못되었거나 null외부 자원을 사용할 수없는 경우 등

도서관은 대부분 현명한 방법으로 도서관을 다룰 방법이 없습니다 . 합리적인 해결책은 적절한 예외를 던져 응용 프로그램 개발자가 처리하도록하는 것입니다.

응용 프로그램은 항상 특정 시점에서 예외를 잡아야합니다.

예외가 발생하면 오류 또는 치명적 오류 로 분류하고 싶습니다 . 규칙적인 오류 는 내 응용 프로그램 내에서 단일 작업이 실패했음을 의미합니다. 예를 들어, 대상을 쓸 수 없어 열려있는 문서를 저장할 수 없습니다. 응용 프로그램이 수행 할 수있는 유일한 생각은 작업을 성공적으로 완료 할 수 없음을 사용자에게 알리고 문제와 관련하여 사람이 읽을 수있는 정보를 제공 한 다음 사용자가 다음에 수행 할 작업을 결정하도록하는 것입니다.

치명적인 오류는 주요 응용 프로그램 논리에서 복구 할 수없는 오류가 발생합니다. 예를 들어, 비디오 게임에서 그래픽 장치 드라이버가 충돌하면 응용 프로그램이 사용자에게 "정상적으로"알리는 방법이 없습니다. 이 경우 로그 파일을 작성해야하며 가능하면 사용자에게 어떤 식 으로든 알려 주어야합니다.

이러한 심각한 경우에도 응용 프로그램은이 예외를 의미있는 방식으로 처리해야합니다. 여기에는 로그 파일 작성, 충돌 보고서 전송 등이 포함될 수 있습니다. 애플리케이션이 어떤 식 으로든 예외에 응답하지 않는 이유는 없습니다.


실제로 디스크 쓰기 작업이나 다른 하드웨어 조작과 같은 라이브러리가 있다면 예기치 않은 모든 이벤트가 발생할 수 있습니다. 쓰기 도중 하드 드라이브가 끊어지면 어떻게합니까? 읽는 동안 어떤 CD 드라이브가 단락됩니까? 그것은 당신이 통제 할 없으며 당신 이 무언가 를 할 는 있지만 (예를 들어, 성공한 척) 도서관 사용자에게 예외를 던져서 결정하게하는 것이 종종 좋습니다. 어쩌면 a HDDPluggedOutDuringWritingException를 다룰 수 있고 치명적이지 않을 수도 있습니다. 프로그램은 그것으로 무엇을할지 결정할 수 있습니다.
VLAZ

1
@VLAZ 치명적인 것과 비치명적인 것은 응용 프로그램이 결정해야하는 것입니다. 도서관은 무슨 일이 있었는지 알려줘야합니다. 응용 프로그램은 이에 대응하는 방법을 결정해야합니다.
MechMK1

0

설명하는 패턴의 문제점은 방법 A가 세 가지 시나리오를 구분할 방법이 없다는 것입니다.

  1. 방법 B가 예상대로 실패했습니다.

  2. 방법 B는 방법 B에 의해 예상되지 않은 방식으로 실패했지만, 방법 B는 안전하게 포기할 수있는 작업을 수행하고있었습니다.

  3. 방법 C는 방법 B에 의해 예상되지 않은 방식으로 실패했지만, 방법 B는 C의 실패로 인해 B가 정리하지 못한 일시적인 불일치 상태에 놓이는 작업을 수행하는 중이었습니다.

메소드 A가 B에서 발생한 예외에 해당 목적에 충분한 정보가 포함되어 있거나 메소드 B의 스택 해제로 인해 오브젝트가 명시 적으로 무효화 된 상태 로 남아있는 경우 메소드 A가 해당 시나리오를 구별 할 수있는 유일한 방법입니다 . 불행히도, 대부분의 예외 프레임 워크는 두 패턴을 어색하게 만들어 프로그래머가 "더 악한"디자인 결정을하도록 강요합니다.


2
시나리오 2와 3은 방법 B의 버그입니다. 방법 A는이 문제를 해결하려고 시도해서는 안됩니다.
Sjoerd

@ Sjoerd : 방법 B는 방법 C가 실패 할 수있는 모든 방법을 어떻게 예상합니까?
슈퍼 캣트

임시 변수에 던질 수있는 모든 작업을 수행하는 것과 같이 잘 알려진 패턴에 의해, 예를 들어 스왑과 같이 던질 수없는 작업은 새로운 상태로 스왑합니다. 또 다른 패턴은 안전하게 반복 될 수있는 작업을 정의하므로 엉망이 될 염려없이 작업을 다시 시도 할 수 있습니다. '예외 안전 코드'를 작성하는 것에 대한 전체 책이 있으므로 여기에 모든 것을 말할 수는 없습니다.
Sjoerd

이것은 예외를 전혀 사용하지 않는 좋은 점입니다 (이는 훌륭한 결정이 될 것입니다). 그러나 OP는 처음에는 예외를 사용하려는 의도로 보이며 어획이 어디에 있어야하는지 묻기 때문에 실제로 질문에 대답하지는 않습니다 .
cmaster

@Sjoerd Method B는 언어에 의해 예외가 금지되는지 여부를 추론하기가 훨씬 쉬워졌습니다. 이 경우 실제로 B를 통한 모든 제어 흐름 경로가 표시되므로 시나리오 3을 피하기 위해 어떤 연산자가 던지는 방식 (C ++)으로 오버로드 될 수 있는지 추측 할 필요가 없습니다. 우리는 코드 측면에서 많은 비용을 지불하고 있습니다 예외를 "그냥"던져서 오류를 반환하는 것에 대해 게으 르기 위해 명확성과 안전. 결국 오류 처리 코드의 중요한 부분이기 때문입니다.
cmaster
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.