예외를 규칙적인 제어 흐름으로 사용하지 않는 이유는 무엇입니까?


193

Googled에 대한 모든 표준 답변을 피하기 위해 모든 사람이 마음대로 공격 할 수있는 예를 제공합니다.

C #과 Java (및 너무 많은 다른 것)에는 내가 좋아하지 않는 '오버플로'동작이 많이 있습니다 (예 type.MaxValue + type.SmallestValue == type.MinValue: int.MaxValue + 1 == int.MinValue:).

그러나 내 악의적 인 성격을 보았을 때이 행동을 확장 하여이 부상에 모욕을 더할 것 DateTime입니다. (나는 DateTime.NET에서 봉인 된 것을 알고 있지만이 예제를 위해 DateTime이 봉인되지 않았다는 점을 제외하고는 C #과 유사한 의사 언어를 사용하고 있습니다.)

재정의 된 Add방법 :

/// <summary>
/// Increments this date with a timespan, but loops when
/// the maximum value for datetime is exceeded.
/// </summary>
/// <param name="ts">The timespan to (try to) add</param>
/// <returns>The Date, incremented with the given timespan. 
/// If DateTime.MaxValue is exceeded, the sum wil 'overflow' and 
/// continue from DateTime.MinValue. 
/// </returns>
public DateTime override Add(TimeSpan ts) 
{
    try
    {                
        return base.Add(ts);
    }
    catch (ArgumentOutOfRangeException nb)
    {
        // calculate how much the MaxValue is exceeded
        // regular program flow
        TimeSpan saldo = ts - (base.MaxValue - this);
        return DateTime.MinValue.Add(saldo)                         
    }
    catch(Exception anyOther) 
    {
        // 'real' exception handling.
    }
}

물론 if는 이것을 쉽게 해결할 수는 있지만 예외를 사용할 수없는 이유를 알 수 없다는 사실은 여전히 ​​유지됩니다 (논리적으로, 성능이 특정 경우 예외를 피해야하는 문제가 있음을 알 수 있습니다) ).

나는 많은 경우에 그것들이 if 구조보다 명확하고 방법이 만드는 계약을 깨뜨리지 않는다고 생각합니다.

IMHO“정기 프로그램 흐름에는 절대로 사용하지 마십시오”라는 반응은 그 반응의 강도가 정당화 될 수 있기 때문에 제대로 구축되지 않은 것 같습니다.

아니면 내가 착각합니까?

나는 모든 종류의 특별한 경우를 다루는 다른 게시물을 읽었지만, 내 요점은 당신이 둘 다라면 아무런 문제가 없다는 것입니다.

  1. 명확한
  2. 방법의 계약을 존중하십시오

날 쏴


2
+1 같은 느낌입니다. 성능 외에도 제어 흐름 예외를 피하는 유일한 이유는 호출자 코드가 반환 값으로 훨씬 더 읽기 쉬운 경우입니다.

4
무언가가 발생하면 -1을 반환하고 다른 것이 있으면 -2를 반환합니다.
kender

1
진실을 말함으로써 부정적인 평판을 얻는 것은 슬픈 일입니다 : 당신의 모범이 if 서술문으로 쓰여질 수 없었습니다. (이것이 정확 / 완전하다고 말하는 것은 아닙니다.)
Ingo

8
나는 예외를 던지는 것이 때때로 유일한 선택 일 수 있다고 주장한다. 예를 들어 데이터베이스를 쿼리하여 생성자 내에서 내부 상태를 초기화하는 비즈니스 구성 요소가 있습니다. 데이터베이스에 적절한 데이터가없는 경우가 있습니다. 생성자 내에서 예외를 발생시키는 것은 객체의 생성을 효과적으로 취소하는 유일한 방법입니다. 이것은 클래스의 계약 (필자의 경우 Javadoc)에 명확하게 명시되어 있으므로 클라이언트 코드가 구성 요소를 만들 때 해당 예외를 잡아서 계속 할 수 있다는 문제는 없습니다.
Stefan Haberl

1
가설을 세웠으므로, 확실한 증거 / 이유도 인용해야합니다. 우선, 코드가 훨씬 짧고 자체 문서화 된 진술 보다 우월한 이유 하나를 설명하십시오 if. 당신은 이것을 매우 어렵다는 것을 알게 될 것입니다. 다시 말해, 당신의 전제에 결함이 있으며, 그로부터 얻은 결론은 잘못되었습니다.
Konrad Rudolph

답변:


171

정상적인 작동 과정에서 초당 5 개의 예외를 발생시키는 프로그램을 디버그하려고 시도한 적이 있습니까?

나는 가지고있다.

이 프로그램은 상당히 복잡했으며 (분산 계산 서버) 프로그램의 한 쪽을 약간 수정하면 완전히 다른 곳에서 무언가를 쉽게 깨뜨릴 수 있습니다.

프로그램을 시작하고 예외가 발생할 때까지 기다릴 수 있기를 원하지만 정상적인 작업 과정에서 시작하는 동안 약 200 개의 예외가있었습니다.

내 요점 : 정상적인 상황에서 예외를 사용하는 경우 비정상적인 상황 (예 : 예외 )을 어떻게 찾 습니까?

물론, 특히 성능 측면에서 예외를 너무 많이 사용하지 않는 다른 강력한 이유가 있습니다.


13
예 : .net 프로그램을 디버깅 할 때 Visual Studio에서 시작하고 VS에 모든 예외를 위반하도록 요청합니다. 예상되는 동작으로 예외에 의존하면 더 이상 할 수 없으며 (초당 5 회 중단되므로) 코드의 문제가있는 부분을 찾는 것이 훨씬 더 복잡합니다.
Brann

15
+1은 실제 예외 바늘을 찾기 위해 예외 건초 더미를 만들고 싶지 않습니다.
Grant Wagner

16
이 답변을 전혀 얻지 마십시오. 사람들이 여기 오해한다고 생각합니다. 디버깅과는 전혀 관련이 없지만 디자인과는 관련이 있습니다. 이것은 내가 두려워하는 순수한 형태의 순환 추론입니다. 그리고 당신의 요점은 실제로 이전에 언급 된 것과 같은 질문 이외의 것입니다
Peter

11
@Peter : 예외를 깨지 않고 디버깅하는 것은 어렵고, 설계 상 예외가 많으면 모든 예외를 포착하는 것이 고통 스럽습니다. 나는 디버깅을 어렵게 만드는 디자인이 거의 부분적으로 망가 졌다고 생각한다 (즉, 디자인은 디버깅, IMO와 관련이있다)
Brann

7
디버깅하려는 대부분의 상황이 발생하는 예외와 일치하지 않는다는 사실을 무시하더라도 귀하의 질문에 대한 답변은 "유형별"입니다. 예를 들어, 디버거에게 AssertionError 또는 StandardError 또는 나쁜 일이 일어나고 있습니다. 당신이 그것에 문제가 있다면, 당신은 어떻게 로깅을합니까-레벨과 클래스별로 정확하게 기록하지 않습니까? 그것도 나쁜 생각이라고 생각합니까?
Ken

158

예외는 기본적으로 goto후자의 모든 결과를 가진 로컬이 아닌 진술입니다. 흐름 제어에 예외를 사용하면 놀랍게도 되는 원칙을 위반하고 프로그램을 읽기 어렵게 만듭니다 (프로그램은 프로그래머를 위해 먼저 작성되어야 함을 기억하십시오).

게다가 이것은 컴파일러 벤더들이 기대하는 것이 아닙니다. 예외가 거의 발생하지 않기를 기대하며 일반적으로 throw코드가 상당히 비효율적입니다. 예외를 던지는 것은 .NET에서 가장 비싼 작업 중 하나입니다.

그러나 일부 언어 (특히 Python)는 예외를 흐름 제어 구문으로 사용합니다. 예를 들어, 반복자 StopIteration가 더 이상 항목이 없으면 예외를 발생시킵니다. 표준 언어 구문 (예 for:)도 이에 의존합니다.


11
야, 예외는 놀랍지 않다! 그리고 당신은 "나쁜 생각이야"라고 말하고 "파이썬에서는 좋은 생각입니다"라고 말할 때 스스로 모순됩니다.
hasen

6
나는 여전히 전혀 확신하지 못한다. 질문은 남아 있습니다 : 처음에는 왜 ID를 사용하지 않습니까? 그러나 이것이 답이기 때문에
Peter

4
+1 사실 파이썬과 C #의 차이점을 지적하게되어 기쁩니다. 나는 그것이 모순이라고 생각하지 않습니다. 파이썬은 훨씬 더 역동적이며 이런 식으로 예외를 사용할 것이라는 기대는 언어로 구워졌습니다. 또한 파이썬 EAFP 문화의 일부입니다. 어떤 접근 방식이 개념적으로 더 순수하거나 더 일관성이 있는지는 모르지만 다른 사람들이 기대 하는 바를 수행하는 코드 작성 아이디어를 좋아합니다 . 이는 다른 언어로 다른 스타일을 의미합니다.
Mark E. Haase

13
과는 달리 goto, 물론, 올바르게 상호 작용 통화 스택 및 어휘 범위 지정과 함께, 및 예외 스택을 두지 않거나 엉망 스코프.
Lukas Eder

4
실제로 대부분의 VM 공급 업체는 예외를 예상하고 효율적으로 처리합니다. @LukasEder가 지적했듯이 예외는 구조화 된 점에서 완전히 다릅니다.
Marcin

30

내 경험 법칙은 다음과 같습니다.

  • 오류에서 복구하기 위해 무엇이든 할 수 있다면 예외를 잡아라.
  • 오류가 매우 일반적인 경우 (예 : 사용자가 잘못된 비밀번호로 로그인을 시도한 경우) returnvalues를 사용하십시오.
  • 오류를 복구하기 위해 아무 것도 할 수없는 경우, 잡히지 말고 그대로 두십시오 (또는 응용 프로그램을 약간 정상적으로 종료하기 위해 주 캐처에서 잡으십시오)

예외로 볼 때 문제는 순전히 구문 관점에서입니다 (성능 오버 헤드가 최소화되어 있다고 확신합니다). 나는 모든 곳에서 try-blocks를 좋아하지 않는다.

이 예제를 보자 :

try
{
   DoSomeMethod();  //Can throw Exception1
   DoSomeOtherMethod();  //Can throw Exception1 and Exception2
}
catch(Exception1)
{
   //Okay something messed up, but is it SomeMethod or SomeOtherMethod?
}

.. 또 다른 예는 팩토리를 사용하여 핸들에 무언가를 할당해야하는 경우에 해당 팩토리에서 예외가 발생할 수 있습니다.

Class1 myInstance;
try
{
   myInstance = Class1Factory.Build();
}
catch(SomeException)
{
   // Couldn't instantiate class, do something else..
}
myInstance.BestMethodEver();   // Will throw a compile-time error, saying that myInstance is uninitalized, which it potentially is.. :(

개인적으로, 드문 오류 조건 (메모리 부족 등)에 대한 예외를 유지하고 대신 반환 값 (값 클래스, 구조체 또는 열거 형)을 사용하여 오류 검사를 수행해야한다고 생각합니다.

나는 당신의 질문이 올바른 것을 이해하기를 바랍니다 :)


4
다시 : 두 번째 예-빌드 후 try 블록 안에 BestMethodEver에 대한 호출을 넣는 것이 어떻습니까? Build ()에서 예외가 발생하면 실행되지 않으며 컴파일러가 만족합니다.
Blorgbeard는

2
그렇다. 아마 이것이 결국에는 끝이 될 것이지만, myInstance 타입 자체가 예외를 던질 수있는 좀 더 복잡한 예제를 생각 해보자. 많은 중첩 된 try / catch 블록으로 끝납니다 :(
cwap

catch 블록에서 예외 변환 (추상 레벨에 적합한 예외 유형으로)을 수행해야합니다. 참고 : "멀티 캐치"는 Java 7에 들어가기로되어 있습니다.
jasonnerothin 2009

참고 : C ++에서는 다른 예외를 잡으려고 시도한 후 여러 catch를 넣을 수 있습니다.
RobH

2
shrinkwrap 소프트웨어의 경우 모든 예외를 잡아야합니다. 최소한 프로그램을 종료해야한다는 대화 상자를 표시하면 버그 보고서로 이해할 수없는 내용이 있습니다.
David Thornley

25

많은 답변에 대한 첫 번째 반응 :

프로그래머를 위해 쓰고 있으며 가장 놀랍게도하는 원리

물론이야! 그러나 if는 항상 더 명확하지 않습니다.

그것은 놀랍지 않아야합니다. 예 : 나누기 (1 / x) 캐치 (divisionByZero)는 나에게 (Conrad 및 다른 사람들에게) 나보다 더 분명합니다. 이러한 종류의 프로그래밍이 예상되지 않는다는 사실은 순전히 전통적이며 실제로는 여전히 관련이 있습니다. 어쩌면 내 예에서 if가 더 명확 할 것입니다.

그러나 그 문제에 대한 DivisionByZero와 FileNotFound는 if보다 명확합니다.

물론 성능이 떨어지고 초당 zillion 시간이 필요한 경우 당연히 피해야하지만 과도한 디자인을 피해야 할 이유를 읽지 못했습니다.

놀랍게도라는 원칙이 진행되는 한 여기에 순환 추론의 위험이 있습니다. 공동체 전체가 나쁜 디자인을 사용한다고 가정하면,이 디자인은 예상 될 것입니다! 그러므로 원칙은 성배가 될 수 없으며 신중하게 고려해야합니다.

정상적인 상황에 대한 예외, 비정상적인 (즉, 예외적 인) 상황을 어떻게 찾습니까?

많은 반응에서 sth. 이처럼 여물통이 빛납니다. 그냥 잡아라 귀하의 방법은 명확하고 잘 문서화되어 있으며 계약을 존중해야합니다. 인정해야 할 질문이 없습니다.

모든 예외에 대한 디버깅 : 동일합니다. 예외를 사용하지 않는 디자인이 일반적이기 때문에 때때로 수행됩니다. 내 질문은 : 왜 처음에 공통입니까?


1
1) x전화하기 전에 항상 확인 1/x합니까? 2) 모든 분할 작업을 try-catch 블록으로 감싸서 잡을 수 DivideByZeroException있습니까? 3) 캐치 블록에 어떤 로직을 복구 할 수 DivideByZeroException있습니까?
라이트 만

1
DivisionByZero와 FileNotFound를 제외하고는 예외로 취급해야하는 예외적 인 사례이므로 나쁜 예입니다.
0x6C38 1

여기에있는 "예외없는"사람들보다 파일을 찾을 수 없다는 점에서 "매우 예외적 인"것은 없습니다. FileNotFound 예외가 정상적으로 처리 openConfigFile();되면 포착 된 FileNotFound가 올 수 있습니다 { createDefaultConfigFile(); setFirstAppRun(); }. 추락하지 말고 최종 사용자의 경험을 나쁘게하지 말고 더 좋게 만들어 봅시다. "하지만 이것이 처음으로 실행 된 것이 아니라면 매번 그것을 얻는다면 어떨까요?" 적어도 응용 프로그램은 매번 실행되며 모든 시작시 충돌하지 않습니다! 일대일에서 10 : "이것은 끔찍하다": 모든 시작 = 3 또는 4마다 "처음 실행", 모든 시작 = 10 충돌 .
Loduwijk

귀하의 예는 예외입니다. 아니요, x호출하기 전에 항상 확인하는 것은 아닙니다 1/x. 일반적으로 괜찮 기 때문입니다. 예외적 인 경우가 좋지 않은 경우입니다. 여기서는 지구를 산산조각 내고 있지 않지만, 예를 들어 random이 주어진 기본 정수의 x경우 4294967296 중 1 개만 나누기가 실패합니다. 예외적이며 예외를 처리하는 것이 좋습니다. 그러나 예외를 사용하여 switch명령문 과 동등한 기능을 구현할 수는 있지만 매우 어리 석습니다.
Thor84no

16

예외 전에 C에서 거기 setjmplongjmp그는 스택 프레임의 유사한 풀림을 달성하기 위해 사용될 수있다.

그런 다음 동일한 구성에 "예외"라는 이름이 지정되었습니다. 그리고 대부분의 답변은 예외적 인 조건에서 예외가 사용된다고 주장 하면서이 이름의 의미에 의존하여 사용법에 대해 논쟁합니다. 그것은 원래의 의도가 아니 었습니다 longjmp. 많은 스택 프레임에서 제어 흐름을 중단해야하는 상황이있었습니다.

동일한 스택 프레임 내에서도 예외를 사용할 수 있다는 점에서 예외가 약간 더 일반적입니다. 이것은 goto내가 틀렸다고 믿는 것과의 유사점을 높 입니다. Gotos는 (되도록하고 단단히 결합 된 쌍인 setjmp하고 longjmp). 느슨하게 연결된 발행 / 구독은 훨씬 더 깔끔합니다. 따라서 동일한 스택 프레임 내에서 사용하는 것은 gotos 를 사용하는 것과 거의 동일하지 않습니다 .

세 번째 혼란의 원인은 확인 된 예외인지 확인되지 않은 예외와 관련이 있습니다. 물론, 검사되지 않은 예외는 제어 흐름 및 기타 많은 것들에 사용하기에 특히 끔찍한 것 같습니다.

그러나 점검 한 예외는 빅토리아 행 아웃을 모두 극복하고 조금만 살면 제어 흐름에 좋습니다.

내가 가장 좋아하는 사용법은 원하는 throw new Success()코드를 찾을 때까지 한 번에 하나씩 시도하는 긴 코드 조각 시퀀스입니다 . 각각의 논리 조각마다 break임의의 중첩이있을 수 있으므로 모든 종류의 조건 테스트도 가능합니다. if-else패턴은 취성이다. else다른 방법으로 구문을 편집 하거나 엉망으로 만들면 털이 많은 버그가 있습니다.

를 사용 하면 코드 흐름이 throw new Success() 선형화 됩니다. 로컬로 정의 된 Success클래스 (물론 확인 됨)를 사용 하므로이를 잊어 버린 경우 코드가 컴파일되지 않습니다. 그리고 다른 방법을 찾지 못했습니다 Success.

때로는 내 코드가 다른 것을 차례로 확인하고 모든 것이 정상인 경우에만 성공합니다. 이 경우을 사용하는 비슷한 선형화가 throw new Failure()있습니다.

별도의 기능을 사용하면 자연스러운 수준의 구획화가 어려워집니다. 따라서 return솔루션이 최적이 아닙니다. 인지적인 이유로 한 페이지에 한두 페이지의 코드를 사용하는 것을 선호합니다. 나는 극도로 분할 된 코드를 믿지 않습니다.

핫스팟이 없으면 JVM 또는 컴파일러의 기능이 관련성이 떨어집니다. 컴파일러가 로컬에서 발생하고 잡은 예외를 감지하지 않고 goto기계 코드 수준에서 매우 효율적인 것으로 처리 해야하는 근본적인 이유가 있다고 생각할 수 없습니다 .

제어 흐름 (예 : 예외적 인 경우가 아닌 일반적인 경우)을 위해 여러 기능을 사용하는 한 여러 복원, 조건 테스트, 복원 만하는 것이 아니라 3 개의 스택 프레임을 통과하는 것보다 효율성이 떨어지는 방법을 알 수 없습니다. 스택 포인터

나는 개인적으로 스택 프레임에서 패턴을 사용하지 않으며 우아하게 디자인 정교함이 필요한 방법을 알 수 있습니다. 그러나 드물게 사용하면 괜찮을 것입니다.

마지막으로, 놀라운 처녀 프로그래머에 대해서는 설득력있는 이유가 아닙니다. 연습에 부드럽게 소개하면, 사랑하는 법을 배우게됩니다. C 프로그래머가 놀랍고 놀라게하는 데 사용 된 C ++을 기억합니다.


3
이 패턴을 사용하면 대부분의 거친 함수에는 끝에 두 개의 작은 캐치가 있습니다. 하나는 성공을위한 것이고 다른 하나는 실패를위한 것이므로 함수는 올바른 서블릿 응답 준비 또는 반환 값 준비와 같은 것들을 마무리합니다. 마무리 작업을 한 곳에서 수행하는 것이 좋습니다. return-pattern 대안은 그러한 모든 기능에 대한 두 가지 기능을 필요로한다. 서블릿 응답 또는 기타 그러한 조치를 준비하는 외부 및 계산을 수행하는 내부. 추신 : 영어 교수는 아마 마지막
necromancer

11

표준 답변은 예외가 규칙적이지 않으며 예외적 인 경우에 사용해야한다는 것입니다.

나에게 중요한 한 가지 이유는 try-catch내가 유지 관리하거나 디버깅하는 소프트웨어에서 제어 구조를 읽을 때 원래 코더가 if-else구조 대신 예외 처리를 사용한 이유를 찾으려고하기 때문입니다. 그리고 나는 좋은 대답을 찾을 것으로 기대합니다.

컴퓨터뿐만 아니라 다른 코더에도 코드를 작성해야합니다. 기계가 신경 쓰지 않기 때문에 버릴 수없는 예외 처리기와 관련된 의미가 있습니다.


내가 생각하는 저조한 대답. 예외가 삼키는 것을 발견했을 때 컴퓨터가 크게 느려지지 않을 수도 있지만 다른 사람의 코드를 작업하고 있고 그것을 발견 할 때 중요한 일을 놓친 경우 운동하는 동안 내 트랙에서 죽는 것을 막습니다. 이 안티 패턴의 사용에 대한 정당성이 없거나 알 수없는 경우.
Tim Abell

9

성능은 어떻습니까? .NET 웹 앱을로드 테스트하는 동안 일반적으로 발생하는 예외를 수정하고 그 수를 500 명으로 늘릴 때까지 웹 서버 당 100 명의 시뮬레이션 사용자를 처리했습니다.


8

Josh Bloch는 Effective Java에서이 주제를 광범위하게 다루고 있습니다. 그의 제안은 밝고 .Net에도 적용되어야합니다 (세부 사항 제외).

특히 예외적 인 상황에서는 예외를 사용해야합니다. 그 이유는 주로 사용성 관련입니다. 주어진 방법을 최대로 사용할 수 있으려면 입력 및 출력 조건을 최대로 제한해야합니다.

예를 들어, 두 번째 방법은 첫 번째 방법보다 사용하기 쉽습니다.

/**
 * Adds two positive numbers.
 *
 * @param addend1 greater than zero
 * @param addend2 greater than zero
 * @throws AdditionException if addend1 or addend2 is less than or equal to zero
 */
int addPositiveNumbers(int addend1, int addend2) throws AdditionException{
  if( addend1 <= 0 ){
     throw new AdditionException("addend1 is <= 0");
  }
  else if( addend2 <= 0 ){
     throw new AdditionException("addend2 is <= 0");
  }
  return addend1 + addend2;
}

/**
 * Adds two positive numbers.
 *
 * @param addend1 greater than zero
 * @param addend2 greater than zero
 */
public int addPositiveNumbers(int addend1, int addend2) {
  if( addend1 <= 0 ){
     throw new IllegalArgumentException("addend1 is <= 0");
  }
  else if( addend2 <= 0 ){
     throw new IllegalArgumentException("addend2 is <= 0");
  }
  return addend1 + addend2;
}

두 경우 모두 호출자가 API를 적절하게 사용하고 있는지 확인해야합니다. 그러나 두 번째 경우에는 (암시 적으로) 필요합니다. 사용자가 javadoc을 읽지 않았지만 다음과 같은 소프트 예외가 계속 발생합니다.

  1. 문서화 할 필요가 없습니다.
  2. 단위 테스트 전략이 얼마나 공격적인 지에 따라 테스트 할 필요가 없습니다.
  3. 발신자가 세 가지 사용 사례를 처리하도록 요구하지 않습니다.

지상 수준의 포인트는 예외가해야한다는 것입니다 하지 당신은 당신의 API하지만 발신자의 API뿐만 아니라 복잡했습니다 주로하기 때문에, 반환 코드로 사용.

올바른 일을하는 것은 물론 비용이 든다. 비용은 모든 사람이 문서를 읽고 따라야한다는 것을 이해해야한다는 것입니다. 잘만되면 그것이 사실이다.


7

흐름 제어에 예외를 사용할 수 있다고 생각합니다. 그러나이 기술의 단점이 있습니다. 예외를 만들려면 스택 추적을 만들어야하므로 비용이 많이 듭니다. 따라서 예외적 인 상황을 알리는 것보다 예외를 더 자주 사용하려면 스택 추적을 구축해도 성능에 부정적인 영향을 미치지 않아야합니다.

예외를 만드는 비용을 줄이는 가장 좋은 방법은 다음과 같이 fillInStackTrace () 메서드를 재정의하는 것입니다.

public Throwable fillInStackTrace() { return this; }

이러한 예외에는 스택 추적이 채워지지 않습니다.


stacktrace는 또한 호출자가 스택의 모든 Throwable을 "알고"의존해야합니다. 이것은 나쁜 것입니다. 추상화 수준 (서비스의 ServiceException, Dao 메소드의 DaoException 등)에 적합한 예외를 처리하십시오. 필요한 경우 번역하십시오.
jasonnerothin 2009

5

나는 당신이 인용 한 코드에서 프로그램 흐름을 어떻게 제어하고 있는지 알지 못합니다. ArgumentOutOfRange 예외 외에 다른 예외는 표시되지 않습니다. (따라서 두 번째 캐치 절은 절대로 맞지 않습니다). if 문을 모방하기 위해 비용이 많이 드는 던지기를 사용하는 것입니다.

또한 흐름 제어를 수행하기 위해 다른 곳에서 예외를 포착하기 위해 순전히 예외를 던지는 더 불길한 작업을 수행하지 않습니다. 실제로 예외적 인 경우를 처리하고 있습니다.


5

블로그 게시물에 설명 된 모범 사례는 다음과 같습니다 .

  • 소프트웨어에서 예기치 않은 상황을 알리려면 예외를 던지 십시오.
  • 입력 유효성 검사에 반환 값을 사용하십시오 .
  • 라이브러리에서 발생하는 예외를 처리하는 방법을 알고 있다면 가능한 가장 낮은 수준에서 잡아라 .
  • 예기치 않은 예외가 발생하면 현재 작업을 완전히 삭제하십시오. 당신이 그들을 다루는 방법을 아는 척하지 마십시오 .

4

코드를 읽기가 어렵 기 때문에 디버깅에 문제가있을 수 있으며 오랜 시간 후에 버그를 수정하면 새로운 버그가 발생하며, 리소스 및 시간 측면에서 더 비싸며, 코드를 디버깅하고 디버거는 모든 예외가 발생하면 정지합니다.)


4

언급 된 이유 외에도 플로우 제어에 예외를 사용하지 않는 한 가지 이유는 디버깅 프로세스를 크게 복잡하게 만들 수 있기 때문입니다.

예를 들어, VS에서 버그를 추적하려고 할 때 일반적으로 "모든 예외에서 중단"을 설정합니다. 흐름 제어에 예외를 사용하는 경우 정기적으로 디버거를 중단하고 실제 문제가 발생할 때까지 예외가 아닌 예외를 무시해야합니다. 이것은 누군가를 화나게 할 것입니다!


1
나는 이미 하나의 higer를 가지고 있습니다 : 모든 예외에 대한 디버깅 : 동일, 예외를 사용하지 않는 디자인이 일반적이기 때문에 방금 완료되었습니다. 내 질문은 : 왜 처음에 공통입니까?
Peter

당신의 대답은 기본적으로 "Visual Studio에는이 하나의 기능이 있기 때문에 좋지 않습니다 ..."입니까? 나는 지금 약 20 년 동안 프로그래밍을 해왔으며 "모든 예외에 대한 휴식"옵션이 있다는 것을 알지 못했습니다. 여전히 "이 기능 때문에!" 약한 이유처럼 들립니다. 예외를 소스로 추적하십시오. 바라건대 당신은 이것을 쉽게 만드는 언어를 사용하고 있습니다-그렇지 않으면 문제는 일반적인 예외 자체가 아닌 언어 기능에 관한 것입니다.
Loduwijk

3

일부 계산을 수행하는 방법이 있다고 가정합니다. 유효성을 검사 한 다음 0보다 큰 숫자를 반환하려면 많은 입력 매개 변수가 있습니다.

유효성 검사 오류를 알리기 위해 반환 값을 사용하면 간단합니다. method가 0보다 작은 수를 반환하면 오류가 발생합니다. 어떤 매개 변수가 유효성을 검사하지 않는지 어떻게 알 수 있습니까?

나는 C 일에서 많은 함수가 다음과 같은 오류 코드를 반환했다는 것을 기억합니다.

-1 - x lesser then MinX
-2 - x greater then MaxX
-3 - y lesser then MinY

기타

읽기가 불가능하고 예외를 던지고 잡는가?


그래서 그들은 열거 형을 발명했습니다 :) 그러나 마법의 숫자는 완전히 다른 주제입니다. en.wikipedia.org/wiki/…
Isak Savo

좋은 예입니다. 나는 같은 것을 쓰려고했다. @IsakSavo : 메소드가 의미있는 값이나 객체를 반환 할 것으로 예상되면 열거 형은이 상황에서 도움이되지 않습니다. 예를 들어 getAccountBalance ()는 AccountBalanceResultEnum 객체가 아닌 Money 객체를 반환해야합니다. 많은 C 프로그램은 하나의 센티넬 값 (0 또는 널)이 오류를 나타내는 유사한 패턴을 가지고 있으며, 오류가 발생한 이유를 판별하기 위해 별도의 오류 코드를 얻기 위해 다른 함수를 호출해야합니다. (MySQL C API는 이와 같습니다.)
Mark E. Haase

3

제어 흐름에 예외를 사용할 수있는 것처럼 망치의 ​​발톱을 사용하여 나사를 돌릴 수 있습니다. 그렇다고 기능 이 의도 된 용도 라는 의미는 아닙니다 . 이 if명세서는 의도 된 용도 흐름을 제어 하는 조건을 나타 냅니다.

해당 용도로 설계된 기능을 사용하지 않도록 선택하면서 의도하지 않은 방식으로 기능을 사용하는 경우 관련 비용이 발생합니다. 이 경우 실제 부가가치가 없어 선명도와 성능이 저하됩니다. 예외를 사용하면 널리 받아 들여지는 if진술 을 통해 무엇을 얻을 수 있습니까?

또 다른 방법은 말했다 : 당신은 그냥 있기 때문에 할 수 있습니다 당신은 의미하지 않는다 해야한다 .


1
예외가 필요하지 않습니다. 우리가 if정상적으로 사용하거나 예외를 사용할 경우 의도하지 않았기 때문에 (원형 인수) 예외 가 있습니까?
Val

1
@val : 예외는 예외적 인 상황입니다 - 우리가 예외를 발생 및 처리 할 수있을만큼이 감지되면, 우리가하기에 충분한 정보가 되지 던져 여전히 그것을 처리합니다. 처리 로직으로 바로 이동하여 값 비싼 불필요한 시도 / 잡기를 건너 뛸 수 있습니다.
Bryan Watts

이 논리에 따라 예외가 없을 수도 있고 항상 예외를 throw하는 대신 시스템 종료를 수행 할 수도 있습니다. 종료하기 전에 무언가를하고 싶다면 래퍼를 만들고 호출하십시오. Java 예제 : public class ExitHelper{ public static void cleanExit() { cleanup(); System.exit(1); } }그런 다음 던지기 대신 호출 ExitHelper.cleanExit();하십시오. 논리가 적절하다면 이것이 선호되는 접근법이며 예외는 없습니다. 기본적으로 "예외의 유일한 이유는 다른 방식으로 충돌하는 것입니다."
Loduwijk

@Aaron : 예외를 모두 처리하고 잡으면이를 피할 수있는 충분한 정보가 있습니다. 그렇다고 모든 예외가 갑자기 치명적이라는 의미는 아닙니다. 내가 제어하지 않는 다른 코드는 그것을 잡을 수 있습니다. 건전한 내 주장은 같은 맥락에서 예외를 던지고 잡는 것이 불필요하다는 것입니다. 모든 예외가 프로세스를 종료해야한다고 말하지도 않았습니다.
Bryan Watts

@BryanWatts는 인정했다. 다른 많은 사람들 복구 할 수없는 것에 대해서만 예외를 사용해야한다고 말했으며 확장에 의해 항상 예외에서 충돌해야합니다. 그렇기 때문에 이러한 것들에 대해 논의하기가 어렵습니다. 2 개의 의견뿐만 아니라 많은 의견이 있습니다. 나는 여전히 당신의 의견에 동의하지 않지만 강력하지는 않습니다. throw / catch가 함께 가장 읽기 쉽고 유지 보수가 가능하며보기 좋은 코드 인 경우가 있습니다. 일반적으로 이것은 이미 다른 예외를 잡기 위해 이미 시도 / 캐치를 가지고 있고 한 개 또는 두 개 이상의 catch를 추가하는 것이 별도의 오류 검사보다 깨끗합니다 if.
Loduwijk

3

다른 사람들이 많이 언급했듯이, 놀랍게도 적은 원칙은 제어 흐름 전용 목적으로 예외를 과도하게 사용하는 것을 금지합니다. 반면에, 어떤 규칙이 100 % 정확하지 않으며, 예외가 "딱 맞는 도구"입니다 이러한 경우 항상있다 - 같은 많은 goto자체, 그런데,의 형태로 선박 breakcontinue자바 같은 언어는 종종 많이 피할 수있는 것은 아니지만 자주 중첩 된 루프에서 뛰어 내리는 완벽한 방법입니다.

다음 블로그 게시물은 로컬아닌 경우에 다소 복잡하지만 흥미로운 사용 사례를 설명합니다 ControlFlowException.

또한 jOOQ (Java 용 SQL 추상화 라이브러리) 내부 에서 이러한 "예외"가 일부 "희귀 한"조건이 충족 될 때 초기에 SQL 렌더링 프로세스를 중단하는 데 사용되는 방법을 설명합니다 .

이러한 조건의 예는 다음과 같습니다.

  • 바인드 값이 너무 많습니다. 일부 데이터베이스는 SQL 문에서 임의의 수의 바인드 값을 지원하지 않습니다 (SQLite : 999, Ingres 10.1.0 : 1024, Sybase ASE 15.5 : 2000, SQL Server 2008 : 2100). 이 경우 jOOQ는 SQL 렌더링 단계를 중단하고 인라인 된 바인드 값으로 SQL 문을 다시 렌더링합니다. 예:

    // Pseudo-code attaching a "handler" that will
    // abort query rendering once the maximum number
    // of bind values was exceeded:
    context.attachBindValueCounter();
    String sql;
    try {
    
      // In most cases, this will succeed:
      sql = query.render();
    }
    catch (ReRenderWithInlinedVariables e) {
      sql = query.renderWithInlinedBindValues();
    }

    매번 계산하기 위해 쿼리 AST에서 바인드 값을 명시 적으로 추출한 경우이 문제가 발생하지 않는 쿼리의 99.9 %에 대해 귀중한 CPU주기가 낭비됩니다.

  • 일부 로직은 "부분적으로"만 실행하려는 API를 통해 간접적으로 만 사용할 수 있습니다. 이 UpdatableRecord.store()메소드는 의 내부 플래그 에 따라 INSERTor UPDATE문을 생성합니다 Record. "외부"에서 우리는 어떤 종류의 로직 store()(예 : 낙관적 잠금, 이벤트 리스너 처리 등) 이 포함되어 있는지 알지 못 하므로 배치 레코드에 여러 레코드를 저장할 때 해당 로직을 반복하고 싶지 않습니다. 여기서는 store()SQL 문만 생성하고 실제로 실행하지는 않습니다. 예:

    // Pseudo-code attaching a "handler" that will
    // prevent query execution and throw exceptions
    // instead:
    context.attachQueryCollector();
    
    // Collect the SQL for every store operation
    for (int i = 0; i < records.length; i++) {
      try {
        records[i].store();
      }
    
      // The attached handler will result in this
      // exception being thrown rather than actually
      // storing records to the database
      catch (QueryCollectorException e) {
    
        // The exception is thrown after the rendered
        // SQL statement is available
        queries.add(e.query());                
      }
    }

    store()선택적으로 SQL을 실행 하지 않도록 사용자 정의 할 수있는 "재사용 가능한"API로 로직을 외부화 한 경우 유지 관리하기 어렵고 재사용하기 어려운 API를 작성하려고합니다.

결론

본질적으로, 이러한 비 로컬 gotos 의 사용법은 [Mason Wheeler] [5]가 그의 답변에서 말한 내용과 일치합니다.

"이 상황에서 처리 할 수있는 컨텍스트가 충분하지 않기 때문에이 시점에서 제대로 처리 할 수없는 상황이 발생했습니다. 그러나 저를 호출 한 루틴 (또는 호출 스택의 상위 항목)은 처리 방법을 알아야합니다. "

두 가지 사용법 모두 ControlFlowExceptions대안에 비해 구현하기가 쉬웠으므로 관련 내부에서 리팩토링하지 않고도 광범위한 로직을 재사용 할 수 있습니다.

그러나 이것이 미래의 관리자에게 약간 놀람이라는 느낌이 남아 있습니다. 코드는 다소 정교하게 느껴지고이 경우에는 올바른 선택 이었지만 로컬 제어 흐름에는 예외를 사용하지 않는 것이 좋습니다 if - else.


2

일반적으로 낮은 수준에서 예외를 처리하는 데는 아무런 문제가 없습니다. 예외는 조작을 수행 할 수없는 이유에 대한 자세한 정보를 제공하는 유효한 메시지입니다. 그리고 처리 할 수 ​​있다면해야합니다.

일반적으로 확인 할 수있는 실패 확률이 높다는 것을 알고 있다면 ... 확인해야합니다 ... 예 : if (obj! = null) obj.method ()

귀하의 경우, C # 라이브러리에 익숙하지 않아서 날짜 시간에 타임 스탬프가 범위를 벗어 났는지 확인할 수있는 쉬운 방법이 있는지 알고 있습니다. 그렇다면 if (.isvalid (ts))를 호출하십시오. 그렇지 않으면 코드가 기본적으로 정상입니다.

따라서 기본적으로 어느 쪽이 더 깨끗한 코드를 생성하든간에 예상 예외를 방지하는 작업이 예외를 처리하는 것보다 복잡한 경우가 있습니다. 모든 곳에서 복잡한 경비원을 만드는 대신 예외를 처리 할 권한이 있습니다.


추가 사항 : 예외가 실패 캡처 정보 ( "Param getWhatParamMessedMeUp ()"와 같은 게터)를 제공하는 경우 API 사용자가 다음에 수행 할 작업에 대한 올바른 결정을 내리는 데 도움이 될 수 있습니다. 그렇지 않으면 오류 상태에 이름을 지정하는 것입니다.
jasonnerothin 2009

2

제어 흐름에 예외 처리기를 사용하는 경우 너무 일반적이며 게으른 것입니다. 다른 사람이 언급했듯이 처리기에서 처리를 처리하면 어떤 일이 발생했는지 알고 있지만 정확히 무엇입니까? 본질적으로 제어 흐름에 사용하는 경우 else 문에 예외를 사용하고 있습니다.

어떤 상태가 발생할 수 있는지 모르는 경우, 예를 들어 타사 라이브러리를 사용해야 할 때 또는 예기치 않은 상태에 예외 처리기를 사용하여 UI에서 모든 것을 잡아야 멋진 오류를 표시 할 수 있습니다 메시지를 표시하고 예외를 기록하십시오.

그러나 무엇이 잘못 될 수 있는지 알고 if 문이나 그것을 확인하기 위해 무언가를 넣지 않으면 게으른 것입니다. 예외 처리기가 발생했을 수있는 모든 것을 포괄하도록 허용하는 것은 게으 르며, 나중에 잘못된 가정에 따라 예외 처리기의 상황을 수정하려고하기 때문에 나중에 다시 괴롭힐 것입니다.

정확히 무슨 일이 일어 났는지 판단하기 위해 예외 처리기에 논리를 넣으면 그 논리를 try 블록 안에 넣지 않는 것은 매우 어리석은 일입니다.

예외 처리기는 최후의 수단으로, 무언가 잘못되는 것을 막기위한 아이디어 / 방법이 부족하거나 통제 할 수없는 것입니다. 마찬가지로 서버가 다운되고 시간이 초과되어 해당 예외가 발생하는 것을 막을 수 없습니다.

마지막으로, 모든 검사를 미리 완료하면 알고 있거나 기대할 수있는 사항이 표시되고 명시 적으로 나타납니다. 코드는 명확해야합니다. 무엇을 읽고 싶습니까?


1
전혀 사실이 아님 : "본질적으로 제어 흐름에 사용하는 경우 else 문에 예외를 사용하고 있습니다. 물론 특정!
Peter

2

Common Lisp의 상태 시스템을 살펴 보는 데 관심이있을 수 있습니다.이 예외는 올바르게 수행 된 일종의 일반화입니다. 스택을 풀거나 제어 할 수 없기 때문에 "레스토랑"도 얻을 수있어 매우 편리합니다.

이것은 다른 언어로 된 모범 사례와 관련이 없지만 생각하는 방향으로 (거의) 생각한 디자인으로 수행 할 수있는 작업을 보여줍니다.

물론 요요처럼 스택을 위아래로 튕기는 경우 여전히 성능 고려 사항이 있지만, 대부분의 catch / throw 예외 시스템이 구현하는 접근 방식보다 "오, 헛소리, 보석 금지"보다 훨씬 일반적인 아이디어입니다.


2

흐름 제어에 예외를 사용하는 데 문제가 있다고 생각하지 않습니다. 예외는 연속과 다소 유사하며 정적으로 입력 된 언어에서는 예외가 연속보다 강력하므로 예외가 필요하지만 언어에없는 경우 예외를 사용하여 예외를 구현할 수 있습니다.

실제로, 연속이 필요하고 언어에 언어가없는 경우 잘못된 언어를 선택했기 때문에 다른 언어를 사용해야합니다. 그러나 선택의 여지가없는 경우도 있습니다. 클라이언트 측 웹 프로그래밍이 가장 좋은 예입니다. JavaScript를 해결할 방법이 없습니다.

예 : Microsoft Volta 는 웹 응용 프로그램을 간단한 .NET으로 작성하고 프레임 워크가 어느 비트를 어디에서 실행해야하는지 알아 내도록하는 프로젝트입니다. 그 결과 Volta는 클라이언트에서 코드를 실행할 수 있도록 CIL을 JavaScript로 컴파일 할 수 있어야합니다. 그러나 문제가 있습니다. .NET에는 멀티 스레딩이 있고 JavaScript에는 없습니다. 따라서 Volta는 JavaScript 예외를 사용하여 JavaScript에서 연속을 구현 한 다음 이러한 연속을 사용하여 .NET 스레드를 구현합니다. 이렇게하면 스레드를 사용하는 Volta 응용 프로그램을 Silverlight없이 수정되지 않은 브라우저에서 실행하도록 컴파일 할 수 있습니다.


2

하나의 미학적 이유 :

시도는 항상 캐치와 함께 제공되는 반면, if에는 다른 것이 필요하지 않습니다.

if (PerformCheckSucceeded())
   DoSomething();

try / catch를 사용하면 훨씬 더 장황해진다.

try
{
   PerformCheckSucceeded();
   DoSomething();
}
catch
{
}

6 줄의 코드가 너무 많습니다.


1

그러나 당신은 항상 당신이 호출하는 방법에서 일어나는 일을 알지 못할 것입니다. 예외가 발생한 위치를 정확히 알 수 없습니다. 예외 개체를 더 자세히 검사하지 않고 ...


1

나는 당신의 모범에 아무런 문제가 없다고 생각합니다. 반대로, 호출 된 함수에 의해 발생 된 예외를 무시하는 것은 죄입니다.

JVM에서 예외를 던지는 것은 비용이 많이 들지 않으며 새로운 xyzException (...)으로 예외를 만드는 것만으로도 예외가 발생합니다. 후자는 스택 워크를 포함하기 때문입니다. 따라서 일부 예외를 미리 만든 경우 비용없이 여러 번 예외가 발생할 수 있습니다. 물론이 방법으로 예외와 함께 데이터를 전달할 수는 없지만 어쨌든 그렇게하는 것이 좋지 않다고 생각합니다.


미안, 이건 명백한 잘못이야, 브랜 조건에 따라 다릅니다. 항상 조건이 사소한 것은 아닙니다. 따라서 if 문은 몇 시간 또는 며칠 이상 걸릴 수 있습니다.
Ingo

JVM에서, 즉 수익보다 비싸지 않습니다. 그림을 이동. 그러나 문제는 if 문에서 무엇을 작성하겠습니까? 이미 호출 된 함수에 이미 존재하는 코드가 아니라면 정상적인 코드에서 예외적 인 사례를 알려주므로 코드 복제입니다.
Ingo

1
Ingo : 예외적 인 상황은 예상치 못한 상황입니다. 즉, 당신은 프로그래머로 생각하지 않은 것입니다. 내 규칙은 "예외를 던지지 않는 코드 작성"입니다. :)
Brann

1
예외 처리기를 작성하지 않고 항상 문제를 해결합니다 (오류 코드를 제어 할 수 없기 때문에 할 수없는 경우 제외). 그리고 내가 작성한 코드가 다른 사람 (예 : 라이브러리)을 대상으로하는 경우를 제외하고는 예외를 던지지 않습니다. 아니 모순을 보여?
Brann

1
난폭하게 예외를 던지지 않기로 동의합니다. 그러나 "예외적 인"것은 정의의 문제입니다. String.parseDouble은 유용한 결과를 제공 할 수없는 경우 (예 : 순서대로) 예외를 발생시킵니다. 다른 무엇을해야합니까? NaN을 반환 하시겠습니까? 비 IEEE 하드웨어는 어떻습니까?
Ingo

1

언어가 값을 반환하지 않고 종료하고 다음 "캐치"블록으로 풀기 위해 언어를 허용 할 수있는 몇 가지 일반적인 메커니즘이 있습니다.

  • 메소드가 스택 프레임을 검사하여 호출 사이트를 판별하고 호출 사이트의 메타 데이터를 사용 try하여 호출 메소드 내의 블록 에 대한 정보 또는 호출 메소드가 호출자의 주소를 저장 한 위치를 찾도록하십시오. 후자의 경우, 발신자의 발신자에 대한 메타 데이터를 검사하여 즉시 발신자와 동일한 방식으로 결정하여 try블록을 찾거나 스택이 비어 있을 때까지 반복 합니다. 이 접근 방식은 예외가없는 경우에 약간의 오버 헤드를 추가하지만 (일부 최적화는 제외) 예외가 발생하면 비용이 많이 듭니다.

  • 메소드가 정상 리턴과 예외를 구별하는 "숨겨진"플래그를 리턴하도록하고 호출자가 해당 플래그와 분기를 "예외"루틴으로 설정하도록 설정하십시오. 이 루틴은 예외가없는 경우에 1-2 개의 명령어를 추가하지만 예외가 발생할 때 비교적 적은 오버 헤드가 발생합니다.

  • 발신자에게 예외 처리 정보 또는 코드를 누적 된 반송 주소와 관련된 고정 주소에 배치하게합니다. 예를 들어 ARM에서는 "BL 서브 루틴"명령어를 사용하는 대신 다음 시퀀스를 사용할 수 있습니다.

        adr lr,next_instr
        b subroutine
        b handle_exception
    next_instr:
    

정상적으로 종료하려면 서브 루틴은 단순히 bx lr또는 pop {pc}; 비정상 종료의 경우, 서브 루틴은 리턴 또는 사용을 수행하기 전에 LR에서 4를 뺍니다 sub lr,#4,pc(ARM 변형, 실행 모드 등에 따라 다름).이 방법은 호출자가 수용하도록 설계되지 않은 경우 매우 잘못 작동합니다.

확인 된 예외를 사용하는 언어 또는 프레임 워크는 위의 # 2 또는 # 3과 같은 메커니즘으로 처리하는 데 도움이되는 반면, 확인되지 않은 예외는 # 1을 사용하여 처리됩니다. Java에서 확인 된 예외를 구현하는 것은 다소 번거롭지 만 호출 사이트에서 "이 방법은 XX를 던지는 것으로 선언되었지만 예상하지는 않습니다. 만약 그렇다면, "확인되지 않은"예외로 다시 던져라 .. 확인 된 예외가 그런 방식으로 처리 된 프레임 워크에서, 일부 상황에서 구문 분석 방법과 같은 것들에 대한 효과적인 흐름 제어 수단이 될 수있다. 실패 가능성은 높지만 실패는 성공과 근본적으로 다른 정보를 반환해야합니다. 그러나 이러한 패턴을 사용하는 모든 프레임 워크는 알지 못합니다. 대신, 더 일반적인 패턴은 모든 예외에 대해 위의 첫 번째 접근 방식 (예외가없는 경우 최소 비용이지만 예외가 발생할 경우 높은 비용)을 사용하는 것입니다.

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