예외는 예외적 인 경우에만 사용해야한다고 들었습니다. 내 사례가 예외적인지 어떻게 알 수 있습니까?


99

필자의 구체적인 사례는 사용자가 응용 프로그램에 문자열을 전달하고 응용 프로그램이 구문 분석하여 구조화 된 객체에 할당한다는 것입니다. 때때로 사용자는 잘못된 것을 입력 할 수 있습니다. 예를 들어, 그들의 의견은 사람을 묘사 할 수 있지만 나이는 "사과"라고 말할 수 있습니다. 이 경우 올바른 동작은 트랜잭션을 롤백하고 사용자에게 오류가 발생했음을 알리는 것이므로 다시 시도해야합니다. 첫 번째 오류뿐만 아니라 입력에서 찾을 수있는 모든 오류를보고해야 할 수도 있습니다.

이 경우 예외를 던져야한다고 주장했다. 그는 "예외는 예외적이어야한다. 사용자가 유효하지 않은 데이터를 입력 할 것으로 예상되므로 예외적 인 경우는 아니다"라고 동의하지 않았다. 옳은 것 같습니다.

그러나 이것이 예외가 처음에 발명 된 이유라는 것을 이해합니다. 그것은 당신이로 사용 했다 오류가 발생하는지 확인 결과를 검사 할 수 있습니다. 확인하지 않으면 알지 못하면 나쁜 일이 발생할 수 있습니다.

예외없이 스택의 모든 수준은 호출 한 메서드의 결과를 확인해야하며 프로그래머가 이러한 수준 중 하나를 체크인하는 것을 잊어 버린 경우 코드가 실수로 진행되어 잘못된 데이터를 저장할 수 있습니다 (예 :). 그런 식으로 오류가 발생하기 쉬운 것 같습니다.

어쨌든, 내가 말한 것을 자유롭게 수정하십시오. 내 주요 질문은 누군가 예외가 예외적이어야한다고 말하는 경우, 내 사례가 예외적인지 어떻게 알 수 있습니까?


3
가능한 중복? 때 예외가 발생합니다 . 비록 폐쇄되었지만 여기에 적합하다고 생각합니다. 그것은 여전히 ​​약간의 철학이며, 일부 사람들과 지역 사회는 예외를 일종의 흐름 제어로 보는 경향이 있습니다.
thorsten müller

8
사용자가 멍청하면 잘못된 입력을 제공합니다. 똑똑한 사용자는 잘못된 입력을 제공하여 게임을합니다. 따라서 유효하지 않은 사용자 입력은 예외가 아닙니다.
mouviciel

7
또한 Java와 .NET에서 매우 특정한 메커니즘 인 예외를 혼동하지 마십시오 . 훨씬 일반적인 용어 인 오류 가 있습니다. 예외를 던지는 것보다 오류 처리 가 더 많습니다. 이 토론은 예외오류 사이의 뉘앙스에 대해 다룹니다 .
Eric King

4
"Exceptional"! = "드물게 일어남"
ConditionRacer

3
Eric Lippert의 Vexing 예외 는 괜찮은 조언입니다.
브라이언

답변:


87

코드 혼잡을 줄이면서 오류 처리를보다 쉽게하기 위해 예외가 고안되었습니다. 코드 혼란이 적어 오류 처리가 더 쉬운 경우에 사용해야합니다. 이 "예외 상황에 대한 예외"비즈니스는 예외 처리가 허용 할 수없는 성능 저하로 간주 된 시점에서 비롯됩니다. 대부분의 코드에서는 더 이상 그렇지 않지만 사람들은 여전히 ​​그 이유를 기억하지 않고 규칙을 내뱉습니다.

특히 예외적으로 가장 사랑하는 언어 인 Java의 경우 코드를 단순화 할 때 예외를 사용하는 것에 대해 나쁘게 느끼지 않아야 합니다. 실제로 Java 자체 Integer클래스에는 잠재적으로을 발생시키지 않고 문자열이 유효한 정수인지 확인할 수있는 방법이 없습니다 NumberFormatException.

또한 UI 유효성 검사 에만 의존 할 수는 없지만 짧은 숫자 값을 입력하기 위해 스피너를 사용하는 등 UI가 올바로 설계되어 있다면 수치가 아닌 값은 백엔드에 실제로 적용됩니다. 예외적 인 조건.


10
스 와이프 사실, 내가 디자인 한 실제 응용 프로그램에서 성능 저하가 차이를 만들어, 나는 그것을 변경했다 없습니다 특정 구문 분석 작업에 대한 예외를 throw합니다.
Robert Harvey

17
나는 여전히 성능 저하가 유효한 이유가 아니라고 말하지는 않지만 그러한 경우는 규칙이 아닌 예외 (pun 예정)입니다.
Karl Bielefeldt

11
@RobertHarvey Java의 비법은을 사용하지 않고 미리 제작 된 예외 객체를 처리하는 것 throw new ...입니다. 또는 fillInStackTrace ()를 덮어 쓰는 사용자 정의 예외를 처리하십시오. 그러면 적중 에 대해 말하지 말고 성능 저하를 느끼지 않아야합니다 .
Ingo

3
+1 : 정확히 내가 대답하려고했던 것. 코드를 단순화 할 때 사용하십시오. 예외는 콜 스택의 모든 수준에서 반환 값을 확인하지 않아도되는보다 명확한 코드를 제공 할 수 있습니다. (그러나 다른 모든 방법과 마찬가지로 잘못된 방법으로 사용하면 코드를 끔찍한 혼란에 빠뜨릴 수 있습니다.)
Leo

4
@Brendan 일부 예외가 발생하고 오류 처리 코드가 호출 스택에서 4 단계 아래에 있다고 가정합니다. 오류 코드를 사용하는 경우 핸들러 위의 4 가지 함수 모두 오류 값의 반환 값을 가져야 if (foo() == ERROR) { return ERROR; } else { // continue }하며 모든 수준에서 체인을 수행해야 합니다. 확인되지 않은 예외를 throw하면 시끄럽고 중복 된 "오류 반환 오류"가 없습니다. 또한 함수를 인수로 전달하는 경우 오류가 발생하더라도 오류 코드를 사용하면 함수 서명이 호환되지 않는 유형으로 변경 될 수 있습니다.
Doval

72

언제 예외를 던져야합니까? 코드에 관해서는 다음 설명이 매우 도움이된다고 생각합니다.

회원이 이름으로 표시된대로 수행해야하는 작업을 완료하지 못한 경우는 예외입니다 . (Jeffry Richter, C #을 통한 CLR)

왜 도움이 되나요? 무언가를 예외로 처리해야하는지 여부에 따라 상황에 따라 달라집니다. 메소드 호출 레벨에서 컨텍스트는 (a) 이름, (b) 메소드의 서명 및 (b) 메소드를 사용하거나 사용할 것으로 예상되는 클라이언트 코드에 의해 제공됩니다.

질문에 대답하려면 사용자 입력이 처리되는 코드를 살펴 봐야합니다. 다음과 같이 보일 수 있습니다.

public void Save(PersonData personData) {  }

메소드 이름은 일부 유효성 검증이 완료되었음을 제안합니까? 아니요.이 경우 잘못된 PersonData는 예외를 발생시켜야합니다.

클래스에 다음과 같은 다른 메소드가 있다고 가정하십시오.

public ValidationResult Validate(PersonData personData) {  }

메소드 이름은 일부 유효성 검증이 완료되었음을 제안합니까? 예. 이 경우 잘못된 PersonData는 예외를 발생시키지 않아야합니다.

두 가지 방법을 모두 사용하려면 클라이언트 코드가 다음과 같아야합니다.

ValidationResult validationResult = personRegister.Validate(personData);
if (validationResult.IsValid())
{
    personRegister.Save(personData)
}
else
{
    // Throw an exception? To answer this look at the context!
    // That is: (a) Method name, (b) signature and
    // (c) where this method is (expected) to be used.
}

메소드가 예외를 처리해야하는지 확실하지 않은 경우, 메소드 이름 또는 서명이 잘못 선택되었을 수 있습니다. 수업의 디자인이 명확하지 않을 수 있습니다. 예외가 발생하지 않아야하는 경우 질문에 대한 명확한 답변을 얻기 위해 코드 디자인을 수정해야하는 경우가 있습니다.


어제 내가 만든 struct당신이 설명 내 코드 "에 ValidationResult"라는 구조화 된 방법을.
paul

4
귀하의 질문에 대답하는 데 도움이되지는 않지만 명령 쿼리 분리 원칙 ( en.wikipedia.org/wiki/Command-query_separation ) 을 암시 적으로 또는 의도적으로 따 랐음을 지적합니다 . ;-)
Theo Lenndorff

좋은 생각! 한 가지 단점 : 예에서 유효성 검사는 실제로 두 번 수행됩니다 Validate(잘못된 경우에는 False를 반환 Save). 물론 유효성 검사 결과는 개체 내부에 캐시 될 수 있지만 변경시 유효성 검사 결과를 무효화해야하기 때문에 복잡성이 더 추가 될 수 있습니다.
Heinzi

@Heinzi 동의합니다. 메소드 Validate()내에서 호출 되도록 리팩토링 될 수 있으며 Save(), 특정 세부 사항을 ValidationResult사용하여 예외에 대한 적절한 메시지를 구성 할 수 있습니다.
Phil

3
이것은 내가 생각하는 대답보다 낫습니다. 전화가해야 할 일을 할 수 없을 때 발생합니다.
Andy

31

예외는 예외입니다. 사용자가 유효하지 않은 데이터를 입력 할 것으로 예상되므로 예외가 아닙니다.

그 주장에 :

  • 파일이 존재하지 않을 것으로 예상되므로 예외가 아닙니다.
  • 서버와의 연결이 끊어 질 것으로 예상되므로 예외는 아닙니다.
  • 구성 파일이 깨져서 예외가 아닌 것으로 예상됩니다.
  • 요청이 때때로 누락 될 수 있으므로 예외가 아닙니다.

당신이 잡는 예외는 당신이 그것을 잡기로 결정했기 때문에 예상해야합니다. 따라서이 논리에 따라 실제로 잡으려고하는 예외를 절대로 던져서는 안됩니다.

그러므로 나는 "예외가 예외적이어야한다"는 끔찍한 규칙이라고 생각합니다.

해야 할 일은 언어에 따라 다릅니다. 언어마다 예외가 발생하는시기에 대한 규칙이 다릅니다. 예를 들어, 파이썬은 모든 것에 대해 예외를 던지며 파이썬에서는 적절하게 따라갑니다. 반면에 C ++은 예외가 거의 발생하지 않으며 이에 따라 적절하게 진행됩니다. C ++ 또는 Java를 Python과 같이 처리하고 모든 것에 대해 예외를 처리 할 수 ​​있지만, 언어 자체가 어떻게 사용되는지 예상하지 못합니다.

나는 파이썬의 접근 방식을 선호하지만 다른 언어를 사용하는 것은 나쁜 생각이라고 생각합니다.


1
@gnat, 알아 내 요점은 당신이 좋아하지 않더라도 언어의 규칙 (이 경우 Java)을 따라야한다는 것입니다.
Winston Ewert

6
+1 "exceptions should be exceptional" is a terrible rule of thumb.잘 말했다! 사람들이 생각하지 않고 반복하는 것 중 하나입니다.
Andres F.

2
"예상"은 주관적인 인수 나 규칙이 아니라 API / 함수의 계약에 의해 정의됩니다 (명시 적이지만 종종 암시 적입니다). 다른 기능 / API / 서브 시스템은 다른 기대치를 가질 수 있습니다. 아마 아닙니다 (따라서 예외를 던져야합니다). 이 답변은 중요한 점을 놓친 것 같습니다 ....
mikera

1
@ mikera, 그렇습니다. 함수는 계약에 정의 된 예외를 던져야합니다. 그러나 그것은 질문이 아닙니다. 문제는 계약을 어떻게 결정해야 하는가입니다. 나는“예외가 예외적이어야한다”라는 경험의 규칙은 그러한 결정을 내리는 데 도움이되지 않는다고 주장합니다.
Winston Ewert

1
@ supercat, 나는 그것이 더 일반적으로 끝나는 것이 실제로 중요하다고 생각하지 않습니다. 중요한 질문은 제정신이 아닙니다. 오류 조건을 명시 적으로 처리하지 않으면 코드에서 아무 변화가없는 것처럼 보이거나 유용한 오류 메시지가 표시됩니까?
Winston Ewert

30

나는 항상 예외를 생각할 때 데이터베이스 서버 또는 웹 API에 액세스하는 것과 같은 것을 생각합니다. 서버 / 웹 API는 작동 할 것으로 예상되지만 예외적 인 경우에는 서버가 작동하지 않을 수 있습니다. 일반적으로 웹 요청은 빠를 수 있지만 예외적 인 상황 (높은로드)에서는 시간이 초과 될 수 있습니다. 이것은 통제 할 수없는 것입니다.

사용자가 입력 한 내용을 확인하고 원하는 정보로 처리 할 수 ​​있으므로 사용자의 입력 데이터를 제어 할 수 있습니다. 귀하의 경우 저장하기 전에 사용자 입력의 유효성을 검사합니다. 그리고 유효하지 않은 데이터를 제공하는 사용자가 필요하다는 데 동의하는 경향이 있으며, 입력을 확인하고 사용자에게 친숙한 오류 메시지를 제공하여 앱이이를 처리해야합니다.

즉, 대부분의 도메인 모델 세터에서 예외를 사용합니다. 여기서 유효하지 않은 데이터가 들어올 가능성은 절대로 없어야합니다. 그러나 이것이 최후의 방어선이며 풍부한 검증 규칙으로 입력 양식을 작성하는 경향이 있습니다. 실제로 해당 도메인 모델 예외를 트리거 할 가능성이 없습니다. 따라서 세터가 한 가지를 기대하고 또 다른 것을 얻으면 예외적 인 상황이며 일반적인 상황에서는 발생하지 않아야합니다.

편집 (다른 고려 사항) :

사용자가 제공 한 데이터를 db로 전송할 때 테이블에 입력하거나 입력하지 않아야 할 사항을 미리 알고 있습니다. 이는 데이터가 일부 예상 형식에 대해 검증 될 수 있음을 의미합니다. 이것은 당신이 통제 할 수있는 것입니다. 제어 할 수없는 것은 쿼리 도중 서버가 실패하는 것입니다. 따라서 쿼리가 정상이고 데이터가 필터링 / 확인 된 것을 알고 쿼리를 시도해도 여전히 실패합니다. 이는 예외적 인 상황입니다.

웹 요청과 마찬가지로 요청을 보내기 전에 요청 시간이 초과되거나 연결에 실패하는지 알 수 없습니다. 따라서 요청을 보낼 때 몇 밀리 초 후에 서버가 작동하는지 물어볼 수 없으므로 try / catch 접근 방식도 필요합니다.


8
그런데 왜? 더 예상되는 문제를 처리하는 데 예외가 덜 유용한 이유는 무엇입니까?
Winston Ewert

6
@Pinetree, 파일을 열기 전에 파일 존재 여부를 확인하는 것은 좋지 않습니다. 검사와 열기 사이에 파일이 더 이상 존재하지 않을 수 있습니다. 파일을 열 수있는 권한이 없을 수 있으므로 존재 여부를 확인한 다음 파일을 열려면 두 개의 비싼 시스템 호출이 필요합니다. 파일을 열려고 시도하지 않는 것이 좋습니다.
Winston Ewert

10
내가 볼 수있는 한, 가능한 모든 실패는 실패로부터 복구하는 것이 아니라 미리 성공 여부를 확인하는 것이 좋습니다. 예외를 사용하는지 여부에 관계없이 실패를 나타내는 지 여부는 별도의 문제입니다. 실수로 무시할 수 없기 때문에 예외를 선호합니다.
Winston Ewert

11
유효하지 않은 사용자 데이터가 예상되기 때문에 예외적 인 것으로 간주 할 수 없다는 전제에 동의하지 않습니다. 파서를 작성하고 누군가 파싱 할 수없는 데이터를 공급하면 예외입니다. 구문 분석을 계속할 수 없습니다. 예외를 처리하는 방법은 전적으로 또 다른 질문입니다.
ConditionRacer

4
File.ReadAllBytes는 FileNotFoundException잘못된 입력 (예 : 존재하지 않는 파일 이름)이 주어 지면를 던집니다 . 이것이이 오류를 위협하는 유일한 유효한 방법입니다. 오류 코드를 반환하지 않고 다른 방법을 사용할 수 있습니까?
oɔɯǝɹ

16

참고

에서 실용주의 프로그래머 :

예외는 프로그램의 정상적인 흐름의 일부로 거의 사용되지 않아야한다고 생각합니다. 예기치 않은 이벤트에 대해서는 예외를 예약해야합니다. 포착되지 않은 예외가 프로그램을 종료하고 "모든 예외 처리기를 제거해도이 코드가 계속 실행됩니까?"라고 스스로에게 물어 봅니다. 대답이 "아니오"인 경우 예외가 아닌 상황에서 예외가 사용되고있을 수 있습니다.

계속해서 읽을 파일을 여는 예를 살펴보고 파일이 존재하지 않습니다. 예외가 발생합니까?

파일이 있어야 한다면 예외가 보증됩니다. [...] 반면에 파일이 존재하는지 여부를 모르는 경우 파일을 찾을 수없는 경우 예외적으로 보이지 않으며 오류 반환이 적합합니다.

나중에 그들은 왜이 접근법을 선택했는지 토론합니다.

[A] n 예외는 즉각적이고 로컬이 아닌 제어의 이전을 나타냅니다 goto. 이는 일종의 계단식 입니다. 정상적인 처리의 일부로 예외를 사용하는 프로그램은 클래식 스파게티 코드의 모든 가독성 및 유지 관리 문제로 인해 어려움을 겪습니다. 이러한 프로그램은 캡슐화를 깨뜨립니다. 루틴과 호출자는 예외 처리를 통해 더 밀접하게 연결됩니다.

당신의 상황에 대하여

귀하의 질문은 "유효성 검사 오류가 예외를 발생시켜야합니까?"로 귀결됩니다. 대답은 유효성 검사가 수행되는 위치에 따라 다릅니다.

문제의 메소드가 입력 데이터가 이미 검증 된 것으로 간주되는 코드 섹션 내에있는 경우 유효하지 않은 입력 데이터는 예외를 발생시켜야합니다. 이 메소드가 사용자가 입력 한 정확한 입력을 수신하도록 코드가 설계되면 유효하지 않은 데이터가 예상되며 예외가 발생하지 않아야합니다.


11

여기에는 많은 철학적 발화가 있지만 일반적으로 예외적 인 조건은 사용자 개입없이 처리 할 수 ​​없거나 원하지 않는 조건 (정리, 오류보고 등 제외)입니다. 다시 말해, 복구 할 수없는 조건입니다.

어떤 방식으로 해당 파일을 처리 할 의도로 프로그램에 파일 경로를 전달하고 해당 경로로 지정된 파일이 존재하지 않는 경우 예외적 인 조건입니다. 사용자에게 코드를보고하고 다른 파일 경로를 지정할 수있는 것 외에는 코드에서 아무것도 할 수 없습니다.


1
+1, 내가 말하려는 것에 매우 가깝습니다. 나는 그것이 범위에 관한 것이라고 말하고 실제로는 사용자와 아무런 관련이 없습니다. 이것의 좋은 예는 두 닷넷 기능 int.Parse와 int.TryParse의 차이이며, 전자는, 나중에는 예외가 던져해서는 안 나쁜 입력에 예외가 발생하지만 선택의 여지가 없습니다
jmoreno

1
@jmoreno : Ergo, 당신은 코드가 구문 분석 할 수없는 조건에 대해 무언가를 할 수있을 때 TryParse를 사용하고, 그렇지 않으면 Parse를 사용할 것입니다.
Robert Harvey

7

고려해야 할 두 가지 문제가 있습니다.

  1. 단일 관심사에 대해 논의합니다- Assigner이 관심사가 입력을 구조화 된 객체에 할당 하는 것이므로 호출하십시오 -입력이 유효하다는 제약 조건을 표현합니다

  2. 잘 구현 된 사용자 인터페이스가 추가 관심이 있습니다 오류에 대한 사용자 입력 및 건설적인 피드백의 검증 (의이 부분 부르 자 Validator)

Assigner구성 요소 의 관점에서, 예외를 발생시키는 것은 위반 된 제한 조건을 표현했기 때문에 완전히 합리적입니다.

사용자 경험 의 관점에서 볼 때, 사용자Assigner 는 처음 에 이것 과 직접 대화해서는 안됩니다 . 그들은를 통해 그것에 대해 이야기하고 있어야 합니다 Validator.

자, Validator유효하지 않은 사용자 입력은 예외적 인 경우 가 아니며 실제로 관심이있는 경우입니다. 따라서 예외는 적절하지 않으며 여기에서 모든 오류 를 식별하려는 것이 아닙니다. 처음에는 bai 다.

이러한 우려 사항 이 어떻게 구현 되는지 언급하지 않은 것을 알 수 있습니다. 에 대해 이야기하고있는 것 같습니다. Assigner동료가에 대해 이야기하고 있습니다 Validator+Assigner. 두 가지 분리 된 (또는 분리 가능한) 우려 있음을 알게되면 적어도 합리적으로 논의 할 수 있습니다.


르낭의 코멘트를 해결하기 위해, 난 그냥있어 가정 당신이 두 개의 별도의 우려를 확인하면, 그것은 각각의 상황에서 뛰어난 고려되어야 어떤 경우 분명있다.

실제로, 예외적 인 것으로 간주되어야하는지 확실 하지 않은 경우 솔루션에서 독립적 인 문제를 식별하지 못한 것으로 보입니다.

그에 대한 직접적인 대답은

... 내 사례가 예외적인지 어떻게 알 수 있습니까?

명확해질 때까지 계속 단순화하십시오 . 잘 이해하고있는 간단한 개념이 있다면 코드, 클래스, 라이브러리 등으로 다시 구성하는 것에 대해 명확하게 추론 할 수 있습니다.


-1 그러나 두 가지 우려가 있지만 "제 사례가 예외적인지 어떻게 알 수 있습니까?"라는 질문에 대답하지 않습니다.
RMalke

요점은 한 상황에서 예외가 아니라 다른 상황에서는 예외가 될 수 있다는 것입니다. 실제로 대화하고있는 문맥을 식별하면 (두 가지를 혼동하지 않고) 여기에 질문에 대한 답이됩니다.
쓸모없는

... 실제로, 그렇지 않을 수도 있습니다-대신 내 대답에서 요점을 해결했습니다.
쓸모없는

4

다른 사람들은 잘 대답했지만 여전히 여기에 짧은 대답이 있습니다. 예외는 환경의 무언가가 잘못되어 제어 할 수없고 코드가 전혀 진행되지 않는 상황입니다. 이 경우 사용자에게 무엇이 잘못되었는지, 왜 더 나아갈 수 없는지, 해결 방법을 알려 주어야합니다.


3

나는 예외적 인 경우에만 예외를 던져야한다는 충고를들은 적이 없었습니다. 부분적으로 아무 말도하지 않기 때문에 (식용 가능한 음식 만 먹어야한다고 말하는 것과 같습니다.) 매우 주관적이며 예외적 인 경우와 그렇지 않은 경우가 종종 없습니다.

그러나이 조언에 대한 충분한 이유가 있습니다. 예외 발생 및 포착이 느리고, Visual Studio의 디버거에서 예외가 발생할 때마다 알리도록 설정된 코드를 실행하면 수십 명이 스팸으로 분류 될 수 있습니다. 문제가 발생하기 오래 전에 수백 개의 메시지가 없다면

따라서 일반적으로 다음과 같은 경우 :

  • 코드에 버그가 없으며
  • 의존하는 서비스가 모두 사용 가능하며
  • 사용자가 의도 한 방식으로 프로그램을 사용하고 있습니다 (제공된 입력 중 일부가 유효하지 않은 경우에도)

그런 다음 코드는 예외가 발생하지 않아야합니다. 유효하지 않은 데이터를 트랩하려면 Int32.TryParse()프리젠 테이션 계층 과 같은 UI 레벨 또는 코드에서 유효성 검증기를 사용할 수 있습니다 .

다른 경우 에는 예외가 메서드가 이름이 말하는 것을 수행 할 수 없다는 것을 의미한다는 원칙을 고수해야합니다 . 일반적으로 TryParse()두 가지 이유로 리턴 코드를 사용하여 실패를 표시하는 것은 좋은 방법이 아닙니다 (예 : 메소드 이름이이를 명확하게 표시하지 않는 한 ). 첫째, 오류 코드에 대한 기본 응답은 오류 조건을 무시하고 관계없이 수행하는 것입니다. 둘째, 리턴 코드를 사용하는 일부 메소드와 예외를 사용하는 다른 메소드를 사용하여 어느 메소드를 잊어 버리는지도 쉽게 알 수 있습니다. 동일한 인터페이스의 서로 다른 두 가지 교환 가능한 구현이 여기에서 다른 접근 방식을 취하는 코드베이스도 보았습니다.


2

예외는 호출 메소드가 가능하더라도 즉시 호출 코드가 처리 할 준비가되지 않는 조건을 나타내야합니다 . 예를 들어, 파일에서 일부 데이터를 읽는 코드는 유효한 파일이 유효한 레코드로 끝나고 부분 레코드에서 정보를 추출 할 필요가 없다고 합법적으로 가정 할 수 있습니다.

읽기 데이터 루틴이 예외를 사용하지 않고 읽기 성공 여부를 단순히보고 한 경우 호출 코드는 다음과 같아야합니다.

temp = dataSource.readInteger();
if (temp == null) return null;
field1 = (int)temp;
temp = dataSource.readInteger();
if (temp == null) return null;
field2 = (int)temp;
temp = dataSource.readString();
if (temp == null) return null;
field3 = temp;

유용한 각 작업에 대해 세 줄의 코드를 사용하는 등 반대로, readInteger파일의 끝에서 발생하면 예외가 발생하고 호출자가 단순히 예외를 전달할 수 있으면 코드는 다음과 같이됩니다.

field1 = dataSource.readInteger();
field2 = dataSource.readInteger();
field3 = dataSource.readString();

일이 정상적으로 작동하는 경우에 훨씬 더 중점을 두어 훨씬 간단하고 깔끔하게 보입니다. 직접 호출자 조건을 처리 것으로 예상되는 경우 오류 코드를 리턴하는 메소드가 예외를 발생시키는 메소드보다 종종 도움이됩니다. 예를 들어 파일의 모든 정수를 합산하려면 다음을 수행하십시오.

do
{
  temp = dataSource.tryReadInteger();
  if (temp == null) break;
  total += (int)temp;
} while(true);

try
{
  do
  {
    total += (int)dataSource.readInteger();
  }
  while(true);
}
catch endOfDataSourceException ex
{ // Don't do anything, since this is an expected condition (eventually)
}

정수를 요구하는 코드는 이러한 호출 중 하나가 실패 할 것으로 예상합니다. 코드가 끝없는 루프를 사용하게되면 리턴 값을 통해 실패를 나타내는 방법을 사용하는 것보다 훨씬 효율적입니다.

클래스는 종종 클라이언트가 어떤 조건을 기대할 것인지 알지 못하기 때문에 일부 발신자가 기대할 수있는 방식으로 실패 할 수있는 다른 두 가지 버전의 메소드를 제공하는 것이 도움이 될 수 있습니다. 그렇게하면 두 가지 유형의 발신자 모두에게 이러한 방법을 깔끔하게 사용할 수 있습니다. 상황이 발생하면 호출자가 예상하지 못한 "try"메소드도 예외를 처리해야합니다. 예를 들어, tryReadInteger깨끗한 파일 끝 조건이 발생하면 예외를 발생시키지 않아야합니다 (발신자가 예상하지 못한 경우, 발신자는readInteger). 반면에 데이터를 포함하는 메모리 스틱이 분리되어 데이터를 읽을 수없는 경우 예외가 발생합니다. 이러한 이벤트는 항상 가능성으로 인식되어야하지만 즉각적인 호출 코드가 응답에 유용한 작업을 수행 할 준비가되지는 않습니다. 파일 끝 조건과 같은 방식으로보고되지 않아야합니다.


2

소프트웨어를 작성할 때 가장 중요한 것은 소프트웨어를 읽을 수있게 만드는 것입니다. 효율성을 높이고 올바르게 만드는 것을 포함하여 다른 모든 고려 사항은 부차적입니다. 읽을 수 있으면 나머지는 유지 관리에서 처리 할 수 ​​있으며 읽을 수없는 경우 버리는 것이 좋습니다. 따라서 가독성을 향상시킬 때 예외를 발생시켜야합니다.

알고리즘을 작성할 때 미래에 알고리즘을 읽을 사람을 생각하십시오. 잠재적 인 문제가 발생할 수있는 곳을 찾은 경우 독자가 해당 문제를 지금 어떻게 처리하고 있는지 알고 싶으십니까? 아니면 독자가 알고리즘을 계속 사용하기를 원하십니까?

나는 초콜릿 케이크 레시피를 생각하고 싶다. 계란을 넣을 것을 지시 할 때, 선택의 여지가 있습니다 : 계란이 있다고 가정하고 요리법을 익히거나 계란이없는 경우 계란을 얻는 방법에 대한 설명을 시작할 수 있습니다. 야생 닭 사냥 기술로 책 전체를 채울 수 있으며 케이크를 굽는 데 도움이됩니다. 그것은 좋지만 대부분의 사람들은 그 요리법을 읽고 싶지 않을 것입니다. 대부분의 사람들은 계란을 사용할 수 있다고 가정하고 조리법을 익히는 것을 선호합니다. 그것은 요리법을 쓸 때 저자가해야 할 판단입니다.

독자의 마음을 읽어야하기 때문에 좋은 예외를 만드는 것과 즉시 처리해야 할 문제에 대해 보장 된 규칙은 없습니다. 당신이 할 수있는 최선의 방법은 경험 법칙이며, "예외는 예외적 인 상황에 대한 것입니다"는 꽤 좋습니다. 일반적으로 독자가 여러분의 분석법을 읽을 때 분석법이 99 %의 시간을 어떻게 할 것인지를 찾고 있으며, 사용자가 불법 입력과 거의 일어나지 않는 다른 것들을 입력하는 것과 같은 기괴한 코너 케이스로 어수선하지 않을 것입니다. 그들은 문제가 발생하지 않는 것처럼 한 번에 한 명령 씩 소프트웨어의 정상적인 흐름을 직접보고 싶어합니다.


2

첫 번째 오류뿐만 아니라 입력에서 찾을 수있는 모든 오류를보고해야 할 수도 있습니다.

그렇기 때문에 여기서 예외를 던질 수 없습니다. 예외는 즉시 유효성 검사 프로세스를 중단합니다. 따라서이 작업을 수행하는 데 많은 해결 방법이 있습니다.

나쁜 예 :

Dog예외를 사용 하는 클래스의 유효성 검사 방법 :

void validate(Set<DogValidationException> previousExceptions) {
    if (!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        DogValidationException disallowedName = new DogValidationException(Problem.DISALLOWED_DOG_NAME);
        if (!previousExceptions.contains(disallowedName)){
            throw disallowedName;
        }
    }
    if (this.legs < 4) {
        DogValidationException invalidDog = new DogValidationException(Problem.LITERALLY_INVALID_DOG);
        if (!previousExceptions.contains(invalidDog)){
            throw invalidDog;
        }
    }
    // etc.
}

그것을 부르는 방법 :

Set<DogValidationException> exceptions = new HashSet<DogValidationException>();
boolean retry;
do {
    retry = false;
    try {
        dog.validate(exceptions);
    } catch (DogValidationException e) {
        exceptions.add(e);
        retry = true;
    }
} while (retry);

if(exceptions.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

여기서 문제는 모든 오류를 얻기 위해 유효성 검사 프로세스가 이미 발견 된 예외를 건너 뛰어야한다는 것입니다. 위의 방법으로 작동 할 수 있지만 예외를 분명히 잘못 사용 합니다. 요청한 유효성 검사 종류 는 데이터베이스를 만지기 전에 수행해야합니다 . 따라서 아무것도 롤백 할 필요가 없습니다. 그리고 유효성 검사 결과는 유효성 검사 오류 일 가능성이 높습니다 (물론 0).

더 나은 방법은 다음과 같습니다.

메소드 호출 :

Set<Problem> validationResults = dog.validate();
if(validationResults.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

검증 방법 :

Set<Problem> validate() {
    Set<Problem> result = new HashSet<Problem>();
    if(!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        result.add(Problem.DISALLOWED_DOG_NAME);
    }
    if(this.legs < 4) {
        result.add(Problem.LITERALLY_INVALID_DOG);
    }
    // etc.
    return result;
}

왜? 많은 이유가 있으며, 다른 응답에서 가장 많은 이유가 지적되었습니다. 간단하게 말하면 : 다른 사람들이 읽고 이해하는 것이 훨씬 간단 합니다. 둘째, 사용자 스택 추적을 표시하여 자신이 dog잘못 설정 했음을 설명하고 싶습니까?

경우 (가) 두 번째 예에서는 커밋하는 동안, 여전히 오류가 발생 하여 검증이를 검증에도 불구하고 dog, 제로 문제 는 예외가 옳은 일이다 던지고 . Like : 데이터베이스 연결이 없습니다. 데이터베이스 항목이 다른 사람에 의해 수정되었습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.