예외-“무슨 일이 있었는지”대“무엇을해야합니까?”


19

우리는 예외를 사용하여 코드 소비자가 예기치 않은 동작을 유용한 방식으로 처리하도록합니다. 일반적으로 예외는 " FileNotFound우리가 지정한 파일을 찾을 수 없었 습니다" 또는 ZeroDivisionError( 1/0작업 을 수행 할 수 없었던 ) "발생한 상황"시나리오를 중심으로 구축됩니다 .

소비자의 예상되는 행동을 명시 할 가능성이 있다면 어떨까요?

예를 들어, fetchHTTP 요청을 수행하고 검색된 데이터를 리턴하는 자원이 있다고 가정하십시오 . 그리고 오류 대신 ServiceTemporaryUnavailable또는 RateLimitExceeded우리는 RetryableError소비자에게 요청을 다시 시도하고 특정 실패에 신경 쓰지 말 것을 제안합니다. 따라서 우리는 기본적으로 발신자에게 "할 일"을 제안합니다.

우리는 소비자의 모든 유스 케이스를 알지 못하기 때문에 자주하지 않습니다. 그러나 이것이 발신자에게 가장 적합한 행동 방법을 알고있는 특정 구성 요소라고 상상해보십시오. 그렇다면 "해야 할 일"접근 방식을 사용해야합니까?


3
HTTP가 아직이 작업을 수행하지 않습니까? (503)는 요청자가 다시 시도해야한다, 그래서 404은 재시도 아무런 의미가 없다, 그래서 다시 시도해야하므로 근본적인 부재는, (301 개) 수단 "영구적으로 이동"이지만, 다른 주소 등으로 회신 일시적인 오류입니다
킬리안 Foth

7
많은 경우에, 우리가 "무엇을해야하는지"를 실제로 알고 있다면, 컴퓨터가 자동으로 수행하도록 할 수 있으며, 사용자는 아무것도 잘못되었다는 사실조차 알 필요가 없습니다. 내 브라우저가 301을 수신 할 때마다 나에게 묻지 않고 단순히 새 주소로 이동한다고 가정합니다.
Ixrec

@Ixrec-같은 아이디어가있었습니다. 그러나 소비자는 다른 요청을 기다리거나 항목을 무시하거나 완전히 실패하지 않을 수 있습니다.
Roman Bodnarchuk

1
@RomanBodnarchuk : 동의하지 않습니다. 중국어를하기 위해 중국어를 알 필요가 없다는 말과 같습니다. HTTP는 프로토콜이며 클라이언트와 서버 모두 프로토콜을 알고 따라야합니다. 프로토콜이 작동하는 방식입니다. 한쪽 만 알고 알고 있으면 의사 소통 할 수 없습니다.
Chris Pratt

1
이것은 예외를 catch 블록으로 바꾸려는 것처럼 솔직하게 들립니다. 그래서 우리는 예외로 옮겼 if( ! function() ) handle_something();지만 더 이상 호출 컨텍스트를 아는 곳에서 오류를 처리 할 수있었습니다. 즉, 연결이 끊어지면 서버가 실패하거나 자동으로 다시로드되면 클라이언트에게 sys 관리자를 호출하도록 지시하지만 발신자가 다른 마이크로 서비스 인 경우 캐치 블록이 캐치를 처리하도록합니다.
Sebb

답변:


47

그러나 이것이 발신자에게 가장 적합한 행동 방법을 알고있는 특정 구성 요소라고 상상해보십시오.

이것은 거의 항상 하나 이상의 발신자에게 실패합니다.이 행동은 엄청나게 자극적입니다. 가장 잘 알고 있다고 가정하지 마십시오. 사용자가해야 할 일이 아니라 현재 상황을 알려줍니다. 많은 경우에, 제정신의 행동 과정이 무엇인지 명확하게 알 수 있습니다 (그렇지 않은 경우 사용자 매뉴얼에 제안 하십시오).

예를 들어, 귀하의 질문에 주어진도 예외는 깨진 가정을 증명하십시오 ServiceTemporaryUnavailable"나중에 다시 시도"를 동일시하고 RateLimitExceeded동일합니다 "워 어쩌면 당신의 타이머 매개 변수를 조정하고, 몇 분 후에 다시 시도가 진정". 그러나 사용자는 일종의 경보를 켜고 싶을 수도 있지만 ServiceTemporaryUnavailable(서버 문제를 나타냄) 그렇지 않은 경보를 원할 수도 있습니다 RateLimitExceeded.

그들에게 선택권을 주십시오 .


4
나는 동의한다. 서버는 정보를 올바르게 전달해야합니다. 반면에 문서는 그러한 경우에 적절한 행동 과정을 명확하게 설명해야합니다.
Neil

1
이것의 하나의 작은 결함은 일부 해커에게 특정 예외가 코드에서 수행하는 작업에 대해 많은 것을 알려주고 코드를 사용하여 악용하는 방법을 알아낼 수 있다는 것입니다.
Pharap

3
@Pharap 해커가 오류 메시지 대신 예외 자체에 액세스 할 수 있으면 이미 손실 된 것입니다.
corsiKa

2
이 답변이 마음에 들지만 예외 요구 사항을 고려한 내용이 누락되었습니다 ... 복구 방법을 알고 있다면 예외가 아닙니다! 잘못된 입력, 잘못된 상태, 잘못된 보안-프로그래밍 방식으로 수정할 수없는 경우에만 예외가 발생합니다.
corsiKa

1
동의했다. 당신이 경우 주장 재시 가능 여부를 나타내는에, 당신은 언제나 상속 관련 구체적인 예외를 만들 수 있습니다 RetryableError.
sapi dec

18

경고! C ++ 프로그래머가 다른 언어에 관한 질문에 대답하려고 예외 처리를 어떻게 수행해야하는지에 대한 다른 생각을 가지고 여기에옵니다!

이 아이디어가 주어지면 :

예를 들어 HTTP 요청을 수행하고 검색된 데이터를 반환하는 인출 리소스가 있다고 가정합니다. 그리고 ServiceTemporaryUnavailable 또는 RateLimitExceeded와 같은 오류 대신 소비자에게 요청을 다시 시도하고 특정 실패에 신경 쓰지 말 것을 제안하는 RetryableError를 발생시킵니다.

... 내가 제안하는 한 가지는 코드의 일반성을 저하 시키거나 예외에 대해 많은 "번역 포인트"를 요구할 수있는 방식으로 오류를보고하는 것에 대한 우려를 행동 과정과 혼동 할 수 있다는 것입니다. .

예를 들어 파일로드와 관련된 트랜잭션을 모델링하면 여러 가지 이유로 실패 할 수 있습니다. 아마도 파일을로드하는 것은 사용자 컴퓨터에 존재하지 않는 플러그인을로드하는 것과 관련이 있습니다. 파일이 단순히 손상되어 파싱하는 동안 오류가 발생했을 수 있습니다.

어떤 일이 발생하든, 행동 과정은 사용자에게 일어난 일을보고하고 그에 대해 무엇을하고 싶은지 묻는 메시지 ( "다시 시도, 다른 파일로드, 취소")를 가정합니다.

던지는 사람 대 포수

이 과정은이 경우 어떤 종류의 오류가 발생했는지에 관계없이 적용됩니다. 구문 분석 오류의 일반적인 아이디어에 포함되지 않고 플러그인을로드하지 못하는 일반적인 아이디어에 포함되지 않습니다. 파일을로드하는 정확한 컨텍스트 (파일로드와 실패의 조합)에서 이러한 오류가 발생한다는 아이디어에 포함되어 있습니다. 따라서 일반적으로 나는 catcher's예외가 아닌 (예 : 사용자에게 옵션을 묻는 메시지 표시) 응답에 대한 조치 과정을 결정 하는 책임으로 생각합니다 thrower's.

달리 말하면, throw예외 가 발생 하는 사이트는 일반적으로 이러한 종류의 상황 정보가 부족합니다. 특히 던지는 함수가 일반적으로 적용 가능한 경우. 이 정보가있을 때 완전히 퇴화되지 않은 상황에서도 복구 행동에 관한 정보를 throw사이트 에 포함시킴으로써 스스로를 코너링하게 됩니다. catch일반적으로 조치 과정을 결정하는 데 가장 많은 양의 정보가있는 사이트 인 사이트 는 해당 트랜잭션에 대해 해당 조치 과정이 변경되어야하는 경우 하나의 중앙 위치를 제공합니다.

더 이상 잘못된 것을보고하지 않고 예외를 처리하려고 할 때 예외 처리를 시작하면 코드의 일반 성과 유연성이 저하 될 수 있습니다. 구문 분석 오류가 항상 이런 종류의 프롬프트로 이어지는 것은 아니며, 예외가 발생하는 컨텍스트 (발생한 트랜잭션)에 따라 다릅니다.

맹인 던지는 사람

일반적으로 많은 예외 처리 설계는 종종 맹인 던지기의 아이디어를 중심으로 이루어집니다. 예외가 어떻게 발생하는지 또는 어디에서 발생하는지 알 수 없습니다. 수동 오류 전파를 사용하는 이전 형식의 오류 복구에도 동일하게 적용됩니다. 오류가 발생한 사이트에는 사용자 행동 과정이 포함되어 있지 않으며 최소한의 정보 만 포함하여 어떤 종류의 오류가 발생했는지보고합니다.

거꾸로 된 책임과 포수 일반화

이것에 대해 더 신중하게 생각하면서, 이것이 유혹이 될 수있는 일종의 코드베이스를 상상하려고 노력했습니다. 제 상상은 (아마도 잘못된 것입니다) 귀하의 팀은 여전히 ​​여기에서 "소비자"의 역할을 수행하고 있으며 대부분의 호출 코드를 구현하고 있습니다. 아마도 여러분은 try모두 동일한 오류 집합을 겪을 수 있는 많은 이질적인 트랜잭션 (많은 블록)을 가지고 있으며 디자인 관점에서 볼 때 균일 한 복구 조치 과정을 초래해야합니다.

Lightness Races in Orbit's훌륭한 답변 의 현명한 조언을 고려할 때 (실제로 고급 라이브러리 지향 사고 방식에서 비롯된 것으로 생각됨) 트랜잭션 복구 사이트에 더 근접한 "할 일"예외를 던지려는 유혹에 빠질 수 있습니다.

여기서 "중요한 것"문제를 중앙 집중화하지만 여전히 잡기의 맥락에서 다루는 중개자, 일반적인 거래 처리 사이트를 찾을 수 있습니다.

여기에 이미지 설명을 입력하십시오

이것은 모든 외부 트랜잭션이 사용하는 일종의 일반 함수를 디자인 할 수있는 경우에만 적용됩니다 (예 : 호출 할 다른 함수를 입력하는 함수 또는 복잡한 잡기를 수행하는이 중개 트랜잭션 사이트를 모델링하는 무시할 수있는 동작을 갖는 추상 트랜잭션 기본 클래스 ).

그럼에도 불구하고 다양한 오류에 대응하여 사용자의 행동을 중앙 집중화 할 책임이 있으며 여전히 던지기보다는 잡기의 맥락에 있습니다. 간단한 예 (Python-ish pseudocode, 나는 경험이 많은 Python 개발자가 아니기 때문에 이에 대한 관용적 인 방법이있을 수 있습니다) :

def general_catcher(task):
    try:
       task()
    except SomeError1:
       # do some uniformly-designed recovery stuff here
    except SomeError2:
       # do some other uniformly-designed recovery stuff here
    ...

[보다 좋은 이름으로 general_catcher]. 이 예제에서는 수행 할 작업이 포함 된 함수를 전달할 수 있지만 관심있는 모든 유형의 예외에 대해 일반화 / 통합 잡기 동작의 이점을 얻을 수 있으며 "할 일"부분을 계속 확장하거나 수정하십시오. 당신은이 중심적인 위치를 좋아하지만 여전히 catch이것이 권장 되는 상황에 있습니다. 무엇보다도, 우리는 던지는 장소가 "무엇을해야하는지"( "맹인 던지기"의 개념을 유지하면서) 자신에 대해 걱정하지 않아도됩니다.

여기에서 이러한 제안 중 어느 것도 도움이되지 않고 어쨌든 "무엇을해야하는지"예외를 던지려는 강한 유혹이 있다면, 이는 적어도 이것이 매우 반이 디오 적이며 일반화 된 사고 방식을 잠재적으로 낙담시킬 수 있다는 점에 유의하십시오.


2
+1. "Blind Thrower"라는 아이디어에 대해 들어 본 적이 없지만 예외 처리에 대한 생각에는 맞지 않습니다. 오류가 발생한 상태, 처리 방법에 대해서는 생각하지 마십시오. 전체 스택에 대한 책임이있는 경우 책임을 명확하게 구분하기가 어렵지만 (중요!), 수신자는 문제에 대해 알리고 호출자는 문제를 처리해야합니다. 수신자는 이유가 아니라 요청 된 내용 만 알 수 있습니다. 처리 오류는 호출 이유에서 '이유'와 관련하여 수행되어야합니다.
Sjoerd Job Postmus

1
(또한 : 귀하의 회신은 C ++에만 해당되는 것은 아니지만 일반적으로 예외 처리에 적용 가능하다고 생각합니다)
Sjoerd Job Postmus

1
트윗 담아 가기 "Blind Thrower"는 내가 여기에서 생각 해낸 바보 같은 비유 일뿐입니다. 복잡한 기술 개념을 소화하는 데 현명하지도 않고 빠르지 않기 때문에 종종 자신의 이해를 설명하고 향상시키기 위해 작은 이미지와 비유를 찾고 싶습니다. 사물의. 언젠가 나는 많은 만화 그림으로 가득 찬 작은 프로그래밍 책을 작성하기 위해 노력할 수 있습니다. :-D

1
허, 좋은 작은 이미지입니다. 눈가리개를 착용하고 야구 모양의 예외를 던지는 작은 만화 캐릭터.
Blacklight Shining

1
@DrunkCoder : 게시물을 손상시키지 마십시오. 우리는 이미 인터넷에 충분한 돈을 가지고 있습니다. 삭제 사유가있는 경우 게시물에 운영자의 관심을 끌도록 신고하고 사례를 작성하십시오.
Robert Harvey

2

대부분의 경우 이러한 상황을 처리하는 방법을 알려주는 인수를 함수에 전달하는 것이 좋습니다.

예를 들어, 함수를 고려하십시오.

Response fetchUrl(URL url, RetryPolicy retryPolicy);

RetryPolicy.noRetries () 또는 RetryPolicy.retries (3) 등을 전달할 수 있습니다. 재시도 가능한 실패의 경우 정책을 참조하여 재시도 여부를 결정합니다.


그러나 실제로 콜 사이트에 예외를 던지는 것과는 관련이 없습니다. 당신은 잘하지만 문제의 정말 일부 ... 뭔가 조금 다른,에 대해 얘기하고
모니카와 밝기 경주

@LightnessRacesinOrbit, 반대로. 콜 사이트에 예외를 다시 던지는 아이디어의 대안으로 제시하고 있습니다. OP의 예에서 fetchUrl은 RetryableException을 throw하며 대신 fetchUrl에게 다시 시도해야 할 시점을 알려야합니다.
Winston Ewert

2
@ WinstonEwert : LightnessRacesinOrbit에 동의하지만 요점도 알지만 동일한 컨트롤을 나타내는 다른 방법으로 생각하십시오. 그러나을 (를) 다르게 처리해야 할 수도 new RetryPolicy().onRateLimitExceeded(STOP).onServiceTemporaryUnavailable(RETRY, 3)있기 때문에 전달 하거나 무언가를 원한다고 생각 하십시오 . 그것을 작성한 후에, 내 생각은 : 더 유연한 제어를 제공하기 때문에 예외를 더 잘 던진다. RateLimitExceededServiceTemporaryUnavailable
Sjoerd Job Postmus

@SjoerdJobPostmus, 나는 그것이 달려 있다고 생각합니다. 라이브러리에서 속도 제한 및 재시도 논리를 수행 한 것일 수도 있습니다.이 경우 내 접근 방식이 적합하다고 생각합니다. 발신자에게 맡기는 것이 더 합리적이라면 반드시 던질 것입니다.
Winston Ewert
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.