예외를 보수적으로 사용해야하는 이유는 무엇입니까?


80

나는 종종 사람들이 예외는 드물게 사용되어야한다고 말하는 것을 보거나 듣지만 그 이유를 설명하지 않습니다. 그것이 사실 일지 모르지만, 이론적 근거는 일반적으로 "이유가있는 예외라고 불린다" 는 단순한 글입니다. 이것은 저에게는 존경할만한 프로그래머 / 엔지니어가 절대로 받아 들여서는 안되는 일종의 설명 인 것 같습니다.

예외를 사용하여 해결할 수있는 다양한 문제가 있습니다. 제어 흐름에 사용하는 것이 현명하지 않은 이유는 무엇입니까? 그것들이 어떻게 사용되는지에 대해 예외적으로 보수적 인 뒤에있는 철학은 무엇입니까? 의미론? 공연? 복잡성? 미학? 협약?

이전에 성능에 대한 일부 분석을 보았지만 일부 시스템과 관련이 있고 다른 시스템과 관련이없는 수준입니다.

다시 말하지만, 그들이 특별한 상황에서 구원 받아야한다는 데 반드시 동의하지 않는 것은 아니지만, 합의의 근거가 무엇인지 궁금합니다 (만약 그런 것이 존재한다면).



32
중복이 아닙니다. 링크 된 예는 예외 처리가 전혀 유용한 지 여부입니다. 이것은 예외를 사용하는시기와 다른 오류보고 메커니즘을 사용하는시기를 구분하는 것입니다.
Adrian McCarthy

1
그리고 이것에 대해 stackoverflow.com/questions/1385172/… ?
stijn

1
합의 된 근거가 없습니다. 다른 사람들은 예외를 던지는 "적절성"에 대해 다른 의견을 가지고 있으며, 이러한 의견은 일반적으로 그들이 개발 한 언어에 의해 영향을받습니다.이 질문에 C ++로 태그를 지정했지만 Java로 태그를 지정하면 다른 의견을 얻을 수있을 것 같습니다.
Charles Salvia

5
아니요, 위키가 아니어야합니다. 여기에 대한 답변은 전문 지식이 필요하며 담당자에게 보상을 제공해야합니다.
bobobobo

답변:


92

마찰의 주요 지점은 의미론입니다. 많은 개발자가 예외를 남용하고 모든 기회에 예외를 던집니다. 아이디어는 다소 예외적 인 상황에 예외를 사용하는 것입니다. 예를 들어 잘못된 사용자 입력은 이러한 상황이 발생할 것으로 예상하고 준비가되어 있기 때문에 예외로 간주되지 않습니다. 그러나 파일을 만들려고하는데 디스크에 충분한 공간이 없다면 이것은 확실한 예외입니다.

또 다른 문제는 예외가 자주 발생하고 삼켜진다는 것입니다. 개발자는이 기술을 사용하여 단순히 프로그램을 "무음 화"하고 완전히 무너질 때까지 가능한 한 오래 실행되도록합니다. 이것은 매우 잘못되었습니다. 예외를 처리하지 않거나 일부 리소스를 해제하여 적절하게 대응하지 않으면 예외 발생을 기록하지 않거나 최소한 사용자에게 알리지 않으면 의미에 대해 예외를 사용하지 않는 것입니다.

귀하의 질문에 직접 답변합니다. 예외 상황은 드물고 비용이 많이 들기 때문에 예외는 거의 사용하지 않아야합니다.

드물게, 버튼을 누를 때마다 또는 잘못된 사용자 입력이있을 때마다 프로그램이 충돌 할 것으로 예상하지 않기 때문입니다. 예를 들어 데이터베이스에 갑자기 액세스 할 수없고 디스크에 충분한 공간이 없을 수 있으며 의존하는 일부 타사 서비스가 오프라인 상태입니다.이 모든 것이 발생할 수 있지만 아주 드물게 예외적 인 경우가 될 수 있습니다.

예외가 발생하면 정상적인 프로그램 흐름이 중단되므로 비용이 많이 듭니다. 런타임은 예외를 처리 할 수있는 적절한 예외 처리기를 찾을 때까지 스택을 해제합니다. 또한 핸들러가 수신 할 예외 객체에 전달되는 모든 과정에서 호출 정보를 수집합니다. 모두 비용이 있습니다.

그렇다고 예외 (웃음)를 사용하는 데 예외가 없다고 말하는 것은 아닙니다. 여러 계층을 통해 반환 코드를 전달하는 대신 예외를 throw하면 때때로 코드 구조를 단순화 할 수 있습니다. 간단한 규칙으로, 어떤 메서드가 자주 호출 될 것으로 예상하고 "예외적 인"상황을 절반 정도 발견하면 다른 솔루션을 찾는 것이 좋습니다. 그러나이 "예외적 인"상황이 드문 경우에만 나타날 수있는 반면 대부분의 경우 정상적인 작동 흐름을 기대한다면 예외를 throw하는 것이 좋습니다.

@Comments : 예외는 코드를 더 간단하고 쉽게 만들 수 있다면 예외가 적은 상황에서 확실히 사용될 수 있습니다. 이 옵션은 열려 있지만 실제로는 매우 드뭅니다.

제어 흐름에 사용하는 것이 현명하지 않은 이유는 무엇입니까?

예외는 정상적인 "제어 흐름"을 방해하기 때문입니다. 예외가 발생하면 프로그램의 정상적인 실행이 중단되어 잠재적으로 개체가 일관되지 않은 상태로 남아 있고 일부 열린 리소스는 해제되지 않습니다. 물론 C #에는 using 본문에서 예외가 발생하더라도 개체가 삭제되도록하는 using 문이 있습니다. 그러나 잠시 언어에서 추상화합시다. 프레임 워크가 사용자를 위해 개체를 처리하지 않는다고 가정합니다. 수동으로 수행합니다. 리소스와 메모리를 요청하고 해제하는 방법에 대한 시스템이 있습니다. 어떤 상황에서 개체와 리소스를 해제 할 책임이있는 시스템 전체에 대한 계약이 있습니다. 외부 라이브러리를 다루는 방법에 대한 규칙이 있습니다. 프로그램이 정상적인 작동 흐름을 따르면 잘 작동합니다. 그러나 갑자기 실행 도중에 예외가 발생합니다. 자원의 절반이 해제되지 않은 채로 남아 있습니다. 절반은 아직 요청되지 않았습니다. 작업이 트랜잭션 용으로 의도 된 경우 이제 중단됩니다. 리소스를 해제하는 코드 부분이 단순히 실행되지 않기 때문에 리소스 처리 규칙이 작동하지 않습니다. 다른 사람이 이러한 리소스를 사용하기를 원하면이 특정 상황을 예측할 수 없기 때문에 일관성없는 상태에 있고 충돌 할 수도 있습니다.

M () 메서드가 N () 메서드를 호출하여 일부 작업을 수행하고 일부 리소스를 정렬 한 다음 M ()으로 다시 반환하여이를 사용하고 폐기하기를 원한다고 가정 해 보겠습니다. 좋아. 이제 N ()에서 뭔가 잘못되고 M ()에서 예상하지 못한 예외가 발생하므로 예외가 어떤 메서드 C ()에서 잡힐 때까지 맨 위로 거품이 발생합니다. N () 및 일부 리소스 해제 여부 및 방법.

예외를 던지면 프로그램을 예측, 이해 및 처리하기 어려운 예측할 수없는 새로운 중간 상태로 만드는 방법을 만듭니다. GOTO를 사용하는 것과 다소 유사합니다. 한 위치에서 다른 위치로 실행을 무작위로 점프 할 수있는 프로그램을 설계하는 것은 매우 어렵습니다. 유지 관리 및 디버깅도 어렵습니다. 프로그램이 복잡해지면 문제를 해결하기 위해 더 적게 발생하는시기와 위치에 대한 개요를 잃게됩니다.


5
예외를 발생시키는 함수와 오류 코드를 반환하는 함수를 비교한다면, 내가 뭔가를 놓치지 않는 한 스택 해제는 동일 할 것입니다.
Catskul

9
@Catskul : 반드시 그런 것은 아닙니다. 함수 반환은 직접 호출자에게 제어를 직접 반환합니다. throw 된 예외는 현재 호출 스택 전체에서 해당 예외 유형 (또는 기본 클래스 또는 "...")에 대한 첫 번째 catch 핸들러에 제어를 반환합니다.
jon-hanson

5
또한 디버거는 기본적으로 예외로 인해 중단되는 경우가 많습니다. 비정상적이지 않은 조건에 대해 예외를 사용하는 경우 디버거가 많이 중단됩니다.
Qwertie

5
@jon : 잡히지 않았고 오류 처리에 도달하기 위해 더 많은 범위를 이스케이프해야하는 경우에만 발생합니다. 반환 값을 사용하는 동일한 경우 (또한 여러 범위를 통해 전달되어야 함) 동일한 양의 스택 해제가 발생합니다.
Catskul

3
-1 죄송합니다. 예외 기능이 "의미"되는 것에 대해 말하는 것은 무의미합니다. 메모리 부족 등과 같은 예외적 인 상황을 처리하는 데 사용할 수있는 메커니즘 일뿐입니다. 그렇다고 다른 용도로도 사용 해서는 안되는 것은 아닙니다 . 제가보고 싶은 것은 다른 용도로 사용 해서는 안되는 이유에 대한 설명입니다 . 귀하의 답변은 그것에 대해 약간 이야기하지만 예외가 "의미"되는 것에 대한 많은 이야기와 섞여 있습니다.
j_random_hacker

61

"예외적 인 상황에서 예외 발생"이 glib 대답이지만 실제로 그러한 상황이 무엇인지 정의 할 수 있습니다. 전제 조건은 만족하지만 사후 조건은 만족할 수없는 경우 입니다. 이를 통해 오류 처리를 희생하지 않고 더 엄격하고, 더 엄격하고, 더 유용한 사후 조건을 작성할 수 있습니다. 그렇지 않으면 예외없이 가능한 모든 오류 상태를 허용하도록 사후 조건을 변경해야합니다.

  • 함수 호출 하기 전에 전제 조건 이 참이어야합니다 .
  • 사후 조건 은 함수 가 반환 한 후 보장 하는 것 입니다.
  • 예외 안전성 은 예외가 함수 또는 데이터 구조의 내부 일관성에 어떻게 영향을 미치는지 나타내며, 종종 외부에서 전달되는 동작을 처리합니다 (예 : functor, 템플릿 매개 변수의 ctor 등).

생성자

C ++로 작성 될 수있는 모든 클래스의 모든 생성자에 대해 말할 수있는 것은 거의 없지만 몇 가지가 있습니다. 그중 가장 중요한 점은 생성 된 객체 (즉 생성자가 반환하여 성공한 객체)가 파괴된다는 것입니다. 언어가 참이라고 가정하고 자동으로 소멸자를 호출하므로이 사후 조건을 수정할 수 없습니다. (기술적으로는 언어가 어떤 것에 대해서도 보장 하지 않는 정의되지 않은 동작의 가능성을 받아 들일 수 있지만 다른 곳에서 더 잘 다룰 수 있습니다.)

생성자가 성공할 수 없을 때 예외를 던지는 유일한 대안은 유효한 "null"또는 좀비 상태를 허용하도록 클래스의 기본 정의 ( "클래스 불변")를 수정하여 생성자가 좀비를 생성하여 "성공"할 수 있도록하는 것입니다. .

좀비 예

이 좀비 수정의 예는 std :: ifstream 이며 사용하기 전에 항상 상태를 확인해야합니다. 예를 들어 std :: string 은 그렇지 않기 때문에 생성 후 즉시 사용할 수 있다는 것이 항상 보장됩니다. 이 예제와 같은 코드를 작성해야하는데 좀비 상태를 확인하는 것을 잊었다면 조용히 잘못된 결과를 얻거나 프로그램의 다른 부분을 손상시킬 수 있다고 상상해보십시오.

string s = "abc";
if (s.memory_allocation_succeeded()) {
  do_something_with(s); // etc.
}

해당 메서드의 이름을 지정하는 것조차도 상황 문자열에 대한 클래스의 불변 및 인터페이스를 수정해야하는 방법의 좋은 예입니다 . 스스로를 예측하거나 처리 할 수 ​​없습니다.

입력 예 검증

일반적인 예를 들어 보겠습니다 : 사용자 입력 확인. 실패한 입력을 허용하고 싶다고해서 파싱 ​​함수 가 사후 조건에 포함해야 한다는 의미는 아닙니다 . 하지만 핸들러가 파서가 실패했는지 확인해야한다는 뜻입니다.

// boost::lexical_cast<int>() is the parsing function here
void show_square() {
  using namespace std;
  assert(cin); // precondition for show_square()
  cout << "Enter a number: ";
  string line;
  if (!getline(cin, line)) { // EOF on cin
    // error handling omitted, that EOF will not be reached is considered
    // part of the precondition for this function for the sake of example
    //
    // note: the below Python version throws an EOFError from raw_input
    //  in this case, and handling this situation is the only difference
    //  between the two
  }
  int n;
  try {
    n = boost::lexical_cast<int>(line);
    // lexical_cast returns an int
    // if line == "abc", it obviously cannot meet that postcondition
  }
  catch (boost::bad_lexical_cast&) {
    cout << "I can't do that, Dave.\n";
    return;
  }
  cout << n * n << '\n';
}

불행히도 이것은 C ++의 범위 지정에 RAII / SBRM을 중단해야하는 방법에 대한 두 가지 예를 보여줍니다. 그 문제가없고 내가 C ++이 갖고 싶었던 것을 보여주는 파이썬의 예-try-else :

# int() is the parsing "function" here
def show_square():
  line = raw_input("Enter a number: ") # same precondition as above
  # however, here raw_input will throw an exception instead of us
  # using assert
  try:
    n = int(line)
  except ValueError:
    print "I can't do that, Dave."
  else:
    print n * n

전제 조건

전제 조건을 엄격하게 검사 할 필요는 없습니다. 하나를 위반하는 것은 항상 논리 실패를 나타내며 호출자의 책임입니다. 그러나이를 확인하면 예외를 던지는 것이 적절합니다. (어떤 경우에는 쓰레기를 반환하거나 프로그램을 중단시키는 것이 더 적절합니다. 다른 상황에서는 이러한 작업이 끔찍하게 잘못 될 수 있습니다. 정의되지 않은 동작 을 가장 잘 처리하는 방법 은 또 다른 주제입니다.)

특히, stdlib 예외 계층 구조 의 std :: logic_errorstd :: runtime_error 분기를 대조하십시오 . 전자는 종종 사전 조건 위반에 사용되는 반면 후자는 사후 조건 위반에 더 적합합니다.


5
괜찮은 대답이지만 기본적으로 규칙을 말하고 근거를 제공하지 않습니다. 그렇다면 당신의 합리적입니까 : 관습과 스타일?
Catskul

6
+1. 이것이 예외가 사용되도록 설계된 방법이며 이러한 방식으로 사용하면 실제로 코드의 가독성과 재사용 가능성 (IMHO) 이 향상됩니다 .
Daniel Pryden

15
Catskul : 첫 번째는 이론적 근거 나 관습이 아닌 예외적 인 상황의 정의입니다. 사후 조건을 보장 할 수없는 경우 반품수 없습니다 . 예외없이 모든 오류 상태를 포함 하도록 사후 조건을 매우 광범위 하게 만들어 실질적으로 쓸모가 없게해야합니다. 나는 그들을 그렇게 보지 않기 때문에 "왜 그들이 희귀해야 하는가"라는 문자적인 질문에 대답하지 않은 것 같습니다. 오류가 발생할 수 있도록 허용하면서 사후 조건을 유용하게 강화할 수있는 도구입니다 (예외 상황에서 :).

9
+1. 사전 / 사후 조건은 내가 들어 본 최고의 설명입니다.
KitsuneYMG

6
많은 사람들이 "하지만 그것은 단지 관습 / 의미론으로 귀결된다"고 말합니다. 그래 맞아. 그러나 관례와 의미론은 복잡성, 유용성 및 유지 관리에 깊은 영향을 미치기 때문에 중요합니다. 결국, 사전 조건과 사후 조건을 공식적으로 정의할지 여부에 대한 결정도 관습과 의미의 문제가 아닌가? 그러나 이들을 사용하면 코드를 훨씬 쉽게 사용하고 유지 관리 할 수 ​​있습니다.
Darryl

40

  1. 커널 (시스템) 신호 인터페이스를 관리하기위한 값 비싼 커널 호출 (또는 기타 시스템 API 호출)
  2. 분석
    하기 어려움goto진술의 많은 문제가 예외에 적용됩니다. 여러 루틴 및 소스 파일에서 잠재적으로 많은 양의 코드를 건너 뛰는 경우가 많습니다. 이것은 중간 소스 코드를 읽을 때 항상 분명하지는 않습니다. (Java에 있습니다.)
  3. 중간 코드에서 항상 예상되는 것은 아닙니다.
    점프되는 코드는 예외 종료 가능성을 염두에두고 작성되었거나 작성되지 않았을 수 있습니다. 원래 그렇게 작성 되었다면이를 염두에두고 유지되지 않았을 수 있습니다. 생각하세요 : 메모리 누수, 파일 설명자 누수, 소켓 누수, 누가 압니까?
  4. 유지 관리의 복잡성
    처리 예외를 뛰어 넘는 코드를 유지하는 것이 더 어렵습니다.

24
+1, 일반적으로 좋은 이유. 그러나 스택 풀기 및 소멸자 호출 (적어도) 비용은 기능적으로 동등한 오류 처리 메커니즘 (예 : C에서와 같이 오류 코드를 테스트하고 반환)에 의해 지불됩니다.
j_random_hacker

j_random에 동의하더라도 답으로 표시하겠습니다. 스택 해제 및 리소스 해제 / 할당은 기능적으로 동일한 모든 메커니즘에서 동일합니다. 동의하는 경우이 내용을 볼 때 목록에서 잘라내어 답변을 좀 더 좋게 만드세요.
Catskul 2009.11.23

14
Julien : j_random의 의견은 비교 비용 절감이 없다는 점을 지적 int f() { char* s = malloc(...); if (some_func() == error) { free(s); return error; } ... }합니다. 수동으로 수행하든 예외를 통해 스택을 풀려면 비용을 지불해야합니다. 당신은 오류 처리에 대한 예외를 사용하여 비교할 수 없습니다 전혀 .

14
1. 비싸다 : 조기 최적화는 모든 악의 근원입니다. 2. 분석하기 어렵다 : 오류 반환 코드의 중첩 된 레이어와 비교할 때? 나는 정중하게 동의하지 않습니다. 3. 중간 코드에서 항상 예상되는 것은 아닙니다. 중첩 된 레이어와 비교할 때 항상 오류를 합리적으로 처리하고 번역하지 않습니다. 초보자는 둘 다 실패합니다. 4. 유지 보수 문제 : 어떻게? 오류 코드에 의해 중재되는 종속성이 유지 관리하기 더 쉽습니까? ...하지만 이것은 어느 학교의 개발자들이 서로의 주장을 받아들이는 데 어려움을 겪는 영역 중 하나 인 것 같습니다. 다른 것과 마찬가지로 오류 처리 설계는 절충안입니다.
Pontus Gagge 2010

1
나는 이것이 복잡한 문제이며 일반성으로 정확하게 대답하기 어렵다는 데 동의합니다. 그러나 원래의 질문 은 전체적인 모범 사례에 대한 설명이 아니라 단순히 예외 방지 조언이 제공된 이유 를 물었다는 점을 명심하십시오 .
DigitalRoss

22

예외를 던지는 것은 어느 정도 goto 문과 유사합니다. 흐름 제어를 위해 그렇게하면 이해할 수없는 스파게티 코드로 끝납니다. 더 나쁜 것은 어떤 경우에는 점프가 정확히 어디로 가는지조차 알지 못합니다 (즉, 주어진 컨텍스트에서 예외를 포착하지 않는 경우). 이것은 유지 보수성을 향상시키는 "최소한의 놀라움"원칙을 노골적으로 위반합니다.


5
흐름 제어 이외의 목적으로 예외를 사용하는 방법은 무엇입니까? 예외적 인 상황에서도 여전히 흐름 제어 메커니즘이며 코드의 명확성에 영향을줍니다 (이해할 수없는 것으로 가정). 나는 당신이 예외를 사용할 수 있다고 생각하지만 ban catch, 그 경우 당신은 거의 std::terminate()자신을 부르는 것이 좋습니다. 전반적으로이 주장은 "예외를 거의 사용하지 않는다"라기보다는 "예외를 사용하지 않는다"라고 말하는 것처럼 보입니다.
Steve Jessop

5
함수 내부의 return 문은 함수에서 빠져 나옵니다. 예외는 얼마나 많은 레이어가 있는지 아는 사람을 데려다줍니다. 쉽게 찾을 수있는 방법은 없습니다.

2
스티브 : 당신의 요점을 이해합니다. 그러나 그것은 제가 의미하는 바가 아닙니다. 예기치 않은 상황에 대한 예외는 괜찮습니다. 어떤 사람들은 그것들을 "조기 귀환"으로 남용하거나 어떤 종류의 switch 문으로도 남용합니다.
Erich Kitzmueller

2
이 질문은 "예기치 않은"이 충분히 설명 적이 지 않다고 가정한다고 생각합니다. 어느 정도 동의합니다. 어떤 조건으로 인해 함수가 원하는대로 완료되지 않으면 프로그램이 해당 상황을 올바르게 처리하거나 처리하지 않습니다. 처리하는 경우 "예상"이며 코드를 이해할 수 있어야합니다. 제대로 처리하지 않으면 문제가있는 것입니다. 예외로 인해 프로그램이 종료되지 않는 한, 코드의 일정 수준에서 "예상"해야합니다. 그러나 실제로는 내 대답에서 말했듯이 이론적으로 만족스럽지 않지만 좋은 규칙입니다.
Steve Jessop

2
물론 래퍼는 던지는 함수를 오류 반환 함수로 또는 그 반대로 변환 할 수 있습니다. 따라서 호출자가 "예기치 않은"아이디어에 동의하지 않는다고해서 반드시 코드 이해도를 깰 필요는 없습니다. "예기치 않은"것으로 생각되는 많은 조건을 유발하는 성능을 희생 할 수 있지만 정상적이고 복구 가능하다고 생각하고 따라서 errno 등으로 변환합니다.
Steve Jessop

16

예외는 프로그램 상태에 대해 추론하기 어렵게 만듭니다. 예를 들어 C ++에서는 함수가 필요하지 않은 경우에해야하는 것보다 예외적으로 강력하게 안전한지 확인하기 위해 추가 생각을해야합니다.

그 이유는 예외없이 함수 호출이 반환되거나 프로그램을 먼저 종료 할 수 있기 때문입니다. 예외를 제외하고 함수 호출은 반환되거나 프로그램을 종료하거나 어딘가의 catch 블록으로 점프 할 수 있습니다. 따라서 앞의 코드를 보는 것만으로는 더 이상 제어 흐름을 따를 수 없습니다. 호출 된 함수가 throw 될 수 있는지 알아야합니다. 제어가 어디로 가는지, 아니면 현재 범위를 벗어나는 것만 신경 쓰는지에 따라 던져 질 수있는 것과 잡힌 위치를 알아야 할 수도 있습니다.

이런 이유로 사람들은 "상황이 정말 예외적 인 경우가 아니면 예외를 사용하지 마십시오"라고 말합니다. 아래로 내려 가면 "정말 예외적"이란 "오류 반환 값으로 처리 할 때의 이점이 비용보다 더 큰 상황이 발생했습니다"를 의미합니다. 예, 이것은 "정말 예외적"에 대한 본능이 있으면 좋은 경험 법칙이됩니다. 사람들이 흐름 제어에 대해 이야기 할 때, 이는 (블록을 ​​잡기위한 참조없이) 로컬에서 추론 할 수있는 능력이 반환 값의 이점이라는 것을 의미합니다.

Java는 C ++보다 "정말 예외적"이라는 광범위한 정의를 가지고 있습니다. C ++ 프로그래머는 Java 프로그래머보다 함수의 반환 값을보고 싶어 할 가능성이 더 높으므로 Java에서 "정말 예외적"은 "이 함수의 결과로 널이 아닌 객체를 반환 할 수 없습니다"를 의미 할 수 있습니다. C ++에서는 "내 호출자가 계속할 수 있을지 의심 스럽다"는 의미 일 가능성이 높습니다. 따라서 Java 스트림은 파일을 읽을 수없는 경우 발생하지만 C ++ 스트림 (기본적으로)은 오류를 나타내는 값을 반환합니다. 그러나 모든 경우에 호출자가 작성해야하는 코드가 무엇인지의 문제입니다. 따라서 실제로 코딩 스타일의 문제입니다. 코드가 어떻게 표시되어야하는지, "예외 안전"정도에 대해 얼마나 많은 "오류 검사"코드를 작성하고 싶은지 합의에 도달해야합니다.

모든 언어에 대한 광범위한 합의는 오류의 복구 가능성 측면에서 이것이 가장 잘 수행되는 것으로 보입니다 (복구 할 수없는 오류는 예외가있는 코드를 생성하지 않지만 여전히 자체 검사 및 반환이 필요하기 때문에) 오류 반환을 사용하는 코드의 오류). 사람들은 "을 의미하는"이 기능 I 호출이 예외가 발생합니다 "기대 그래서 내가 "그냥 계속할 수 없습니다 "를, 그것을계속할 수 없습니다. "라는 말은 예외에 내재 된 것이 아니라 관례 일뿐입니다.하지만 다른 좋은 프로그래밍 관행과 마찬가지로 다른 방식으로 시도했지만 결과를 즐기지 못한 현명한 사람들이 옹호하는 관습입니다. 너무 많은 예외를 던지는 경험 그래서 개인적으로 나는 상황에 대한 어떤 것이 예외를 특히 매력적으로 만들지 않는 한 "정말 예외적 인"관점에서 생각합니다.

Btw, 코드 상태에 대한 추론 외에도 성능에 영향을 미칩니다. 예외는 일반적으로 성능에 관심이있는 언어에서 일반적으로 저렴합니다. 여러 수준의 "오, 결과는 오류입니다. 그러면 오류가 발생하여 나 자신을 종료하는 것이 가장 좋습니다."의 여러 수준보다 빠를 수 있습니다. 예전에는 예외를 던지고 포착하고 다음 작업을 계속하는 것이 당신이하는 일이 쓸모 없게 될 정도로 느려질 것이라는 진정한 두려움이있었습니다. 따라서이 경우 "정말 예외적"이라는 것은 "상황이 너무 나빠서 끔찍한 성능이 더 이상 중요하지 않다"는 의미입니다. 더 이상 그렇지 않습니다 (단단한 루프의 예외가 여전히 눈에 띄지 만). "정말 예외적"의 정의가 유연해야하는 이유를 알 수 있습니다.


2
"예를 들어 C ++에서는 필요하지 않은 경우에해야하는 것보다 함수가 예외적으로 강력하게 안전한지 확인하기 위해 추가 생각을해야합니다." -기본 C ++ 구문 (예 :) new과 표준 라이브러리가 모두 예외를 던진다는 점을 감안할 때 , 코드에서 예외를 사용하지 않으면 예외 안전 코드를 작성하지 않아도되는 방법을 알 수 없습니다.
Pavel Minaev

1
Google에 문의해야합니다. 하지만 그렇습니다. 함수가 작동하지 않으면 호출자가 몇 가지 작업을 수행해야하며 예외를 발생시키는 추가 조건을 추가한다고해서 "더 많은 예외 발생"이 발생하지는 않습니다. 그러나 메모리 부족은 어쨌든 특별한 경우입니다. 처리하지 않는 프로그램을 사용하고 대신 종료하는 것이 일반적입니다. "예외를 사용하지 말고 예외를 보장하지 마십시오. 새로운 throw가 발생하면 종료하고 스택을 풀지 않는 컴파일러를 사용할 것"이라는 코딩 표준을 가질 수 있습니다. 높은 개념은 아니지만 날아갈 것입니다.
Steve Jessop

스택을 해제하지 않는 (또는 예외를 비활성화하지 않는) 컴파일러를 사용하면 기술적으로 더 이상 C ++가 아닙니다. :)
Pavel Minaev

잡히지 않은 예외에서 스택을 풀지 않습니다.
Steve Jessop

캐치 블록을 찾기 위해 스택을 풀지 않는 한 잡히지 않았는지 어떻게 알 수 있습니까?
jmucchiello

11

정말 합의가 없습니다. 예외를 던지는 "적절성"은 종종 언어 자체의 표준 라이브러리 내의 기존 관행에 의해 제안되기 때문에 전체 문제는 다소 주관적입니다. C ++ 표준 라이브러리는 잘못된 사용자 입력 (예 :)과 같은 예상 오류에 대해서도 거의 항상 예외를 선호 하는 Java 표준 라이브러리보다 훨씬 덜 자주 예외를 발생시킵니다 Scanner.nextInt. 이것은 예외를 던지는 것이 적절한시기에 대한 개발자 의견에 큰 영향을 미친다고 생각합니다.

C ++ 프로그래머로서 저는 개인적으로 매우 "예외적 인"상황 (예 : 메모리 부족, 디스크 공간 부족, 종말 발생 등)에 대해 예외를 예약하는 것을 선호합니다. 그러나 이것이 절대적으로 올바른 방법이라고 주장하지는 않습니다. 소지품.


2
나는 일종의 합의가 있다고 생각하지만 아마도 합의는 건전한 추론보다는 관습에 더 기반을두고있을 것입니다. 예외적 인 상황에서만 예외를 사용해야하는 타당한 이유가있을 수 있지만 대부분의 개발자는 그 이유를 실제로 인식하지 못합니다.
Qwertie

4
동의합니다. "예외는 예외적 인 상황을위한 것입니다."라는 말을 자주 듣지만, "예외적 인 상황"이 무엇인지 제대로 정의하는 것을 귀찮게하는 사람은 없습니다. 이는 대부분 관습이며 확실히 언어에 따라 다릅니다. 지옥, 파이썬에서 반복자는 시퀀스의 끝을 알리기 위해 예외를 사용하며 완벽하게 정상적인 것으로 간주됩니다!
Pavel Minaev

2
+1. 엄격하고 빠른 규칙은 없습니다. 규칙 만 있으면됩니다.하지만 프로그래머가 서로의 코드를 더 쉽게 이해할 수 있도록 해주기 때문에 여러분의 언어를 사용하는 다른 사람들의 규칙을 따르는 것이 유용합니다.
j_random_hacker

1
"세상의 종말이 일어났다"- 아무것도 ;-) 않습니다 UB, 정당화하는 경우, 예외를 신경 쓰지
스티브 Jessop

3
Catskul : 거의 모든 프로그래밍이 관습입니다. 엄밀히 말하면 예외도 필요하지도 않고 전혀 필요하지도 않습니다. NP- 완전성, big-O / theta / little-o 또는 Universal Turing Machines를 포함하지 않는다면 아마도 관습 일 것입니다. :-)
Ken

7

예외는 거의 사용되지 않아야한다고 생각합니다. 그러나.

모든 팀과 프로젝트가 예외를 사용할 준비가되어있는 것은 아닙니다. 예외를 사용하려면 프로그래머의 높은 자격, 특수 기술 및 큰 레거시 비 예외 안전 코드가 부족해야합니다. 거대한 오래된 코드베이스가 있다면 거의 항상 예외로부터 안전하지 않습니다. 나는 당신이 그것을 다시 쓰고 싶지 않을 것이라고 확신합니다.

예외를 광범위하게 사용하려면 다음을 수행하십시오.

  • 사람들에게 예외 안전이 무엇인지 가르 칠 준비를하십시오.
  • 원시 메모리 관리를 사용해서는 안됩니다.
  • RAII를 광범위하게 사용

반면에 강력한 팀이있는 새 프로젝트에서 예외를 사용하면 코드가 더 깨끗하고 유지 관리가 쉽고 더 빨라질 수 있습니다.

  • 오류를 놓치지 않거나 무시하지 않습니다.
  • 하위 수준에서 잘못된 코드로 무엇을해야하는지 실제로 알지 못한 채 반환 코드 검사를 작성할 필요가 없습니다.
  • 예외 안전 코드를 작성해야하는 경우 더 구조화됩니다.

1
특히 "모든 팀이 예외를 사용할 준비가되어있는 것은 아닙니다"라는 사실에 대한 귀하의 언급이 마음에 들었습니다. 예외는 확실히 뭔가있는 것 같다 쉽게 할 수 있지만, 이렇게하는 것이 매우 어려운 권리 , 그 위험하게 어떤 부품이.
j_random_hacker

1
+1은 "예외 안전 코드를 작성해야 할 때 더욱 구조화됩니다." 예외 기반 코드는 더 많은 구조를 갖도록 강요 받고 있으며, 객체와 불변성을 무시할 수 없을 때 실제로 추론하는 것이 훨씬 더 쉽다는 것을 알게되었습니다. 사실, 강력한 예외 안전은 거의 가역적 인 코드를 작성하는 것이므로 불확실한 상태를 쉽게 피할 수 있습니다.
Tom

7

2009 년 11 월 20 일 수정 :

관리 코드 성능 향상에 대한 MSDN 기사를 방금 읽고 있었는데이 부분에서이 질문이 떠 올랐습니다.

예외 발생의 성능 비용은 상당합니다. 구조적 예외 처리가 오류 조건을 처리하는 데 권장되는 방법이지만 오류 조건이 발생하는 예외적 인 상황에서만 예외를 사용해야합니다. 일반 제어 흐름에 예외를 사용하지 마십시오.

물론 이것은 .NET만을위한 것이며, 저와 같은 고성능 애플리케이션을 개발하는 사람들을위한 것이기도합니다. 그래서 그것은 분명히 보편적 인 진실이 아닙니다. 그래도 .NET 개발자가 많기 때문에 주목할 가치가 있다고 느꼈습니다.

편집 :

좋아, 우선, 한 가지를 똑바로하자. 나는 성능 문제에 대해 누구와도 싸울 의도가 없다. 일반적으로, 저는 조기 최적화가 죄라고 믿는 사람들의 의견에 동의하는 경향이 있습니다. 그러나 두 가지 사항 만 말씀 드리겠습니다.

  1. 포스터는 예외를 아껴서 사용해야한다는 기존의 통념 뒤에 객관적인 근거를 요구하고 있습니다. 우리가 원하는 모든 가독성과 적절한 디자인에 대해 논의 할 수 있습니다. 그러나 이것은 양쪽에서 논쟁 할 준비가 된 사람들과 주관적인 문제입니다. 나는 포스터가 이것을 알고 있다고 생각한다. 사실은 프로그램 흐름을 제어하기 위해 예외를 사용하는 것은 종종 일을하는 비효율적 인 방법이라는 것입니다. 아니요, 항상 그런 것은 아니지만 자주 . 그렇기 때문에 붉은 고기를 먹거나 와인을 아껴 마시는 것이 좋은 조언처럼 예외를 아껴 사용하는 것이 합리적인 조언입니다.

  2. 정당한 이유없이 최적화하는 것과 효율적인 코드를 작성하는 것에는 차이가 있습니다. 이에 따른 결과는 최적화되지는 않더라도 강력한 것을 작성하는 것과 비효율적 인 것 사이에 차이가 있다는 것입니다. 때때로 사람들이 예외 처리와 같은 것에 대해 논쟁 할 때 그들은 근본적으로 다른 것에 대해 논의하고 있기 때문에 서로 과거에 대해 이야기하고 있다고 생각합니다.

내 요점을 설명하기 위해 다음 C # 코드 예제를 고려하십시오.

예 1 : 잘못된 사용자 입력 감지

이것은 제가 예외 남용 이라고 부르는 예입니다 .

int value = -1;
string input = GetInput();
bool inputChecksOut = false;

while (!inputChecksOut) {
    try {
        value = int.Parse(input);
        inputChecksOut = true;

    } catch (FormatException) {
        input = GetInput();
    }
}

이 코드는 저에게 말도 안됩니다. 물론 작동합니다 . 아무도 그것에 대해 논쟁하지 않습니다. 그러나 그것은 해야 같은 수 :

int value = -1;
string input = GetInput();

while (!int.TryParse(input, out value)) {
    input = GetInput();
}

예 2 : 파일 존재 확인

이 시나리오는 실제로 매우 일반적이라고 생각합니다. 파일 I / O를 다루기 때문에 많은 사람들에게 확실히 훨씬 더 "허용되는" 것처럼 보입니다 .

string text = null;
string path = GetInput();
bool inputChecksOut = false;

while (!inputChecksOut) {
    try {
        using (FileStream fs = new FileStream(path, FileMode.Open)) {
            using (StreamReader sr = new StreamReader(fs)) {
                text = sr.ReadToEnd();
            }
        }

        inputChecksOut = true;

    } catch (FileNotFoundException) {
        path = GetInput();
    }
}

이건 충분히 합리적 이죠? 파일을 열려고합니다. 만약 거기에 없다면, 우리는 그 예외를 잡아서 다른 파일을 열려고합니다. 그게 무슨 문제입니까?

정말 없어요. 그러나 예외를 발생 시키지 않는 다음 대안을 고려하십시오 .

string text = null;
string path = GetInput();

while (!File.Exists(path)) path = GetInput();

using (FileStream fs = new FileStream(path, FileMode.Open)) {
    using (StreamReader sr = new StreamReader(fs)) {
        text = sr.ReadToEnd();
    }
}

물론,이 두 접근법의 성능이 실제로 동일하다면 이것은 순전히 교리 적 문제 일 것입니다. 자, 한번 살펴 보겠습니다. 첫 번째 코드 예제에서는 10000 개의 임의 문자열 목록을 만들었는데, 그중 어느 것도 적절한 정수를 나타내지 않았고 맨 끝에 유효한 정수 문자열을 추가했습니다. 위의 두 가지 방법을 모두 사용한 결과는 다음과 같습니다.

사용 try/ catch블록 : 25.455
사용 int.TryParse: 1.637 밀리 초

두 번째 예제에서는 기본적으로 동일한 작업을 수행했습니다. 10000 개의 임의 문자열 목록을 만들고 유효한 경로가 아닌 마지막에 유효한 경로를 추가했습니다. 결과는 다음과 같습니다.

사용 try/ catch블록 : 29.989
사용 File.Exists: 22.820 밀리 초

많은 사람들이 "예, 10,000 개의 예외를 던지고 잡는 것은 매우 비현실적입니다. 이것은 결과를 과장합니다."라고 대답합니다. 물론 그렇습니다. 하나의 예외를 던지고 잘못된 입력을 직접 처리하는 것의 차이는 사용자에게 눈에 띄지 않습니다. 이 두 가지 경우 예외를 사용 하는 것은 읽을 수있는 다른 접근 방식보다 1,000 배에서 10,000 배 이상 느립니다 .

그래서 GetNine()아래 방법 의 예를 포함 시켰습니다 . 참을 수 없을 정도로 느리 거나 용납 할 수 없을 정도로 느린 것이 아닙니다 . 그것은 것보다 느리게 있다는입니다 해야 할 ... 어떤 좋은 이유에 대해 .

다시 말하지만, 이것들은 단지 두 가지 예일뿐입니다. 중 물론 예외를 사용하여 성능 저하가없는이 심한 (; 결국, 그 구현에 의존 않습니다 파벨의 오른쪽) 때가있을 것입니다. 내가 말하는 것은 : 사실을 직시합시다. 위와 같은 경우 예외를 던지고 잡는 것은 다음과 유사합니다 GetNine(). 그것은 쉽게 더 잘 할 수있는 일을하는 비효율적 인 방법 일뿐 입니다.


당신은 이것이 모든 사람들이 이유를 모른 채 악 대차에 뛰어 드는 상황 중 하나 인 것처럼 근거를 요구하고 있습니다. 하지만 사실 답은 분명하고 이미 알고 계신 것 같습니다. 예외 처리에는 엄청난 성능이 있습니다.

좋습니다. 특히 비즈니스 시나리오에는 문제가 없지만 상대적으로 말해서 예외를 던지거나 잡으면 많은 경우에 필요한 것보다 더 많은 오버 헤드가 발생합니다. 알고 있습니다. 대부분의 경우 예외를 사용하여 프로그램 흐름을 제어하는 ​​경우 느린 코드를 작성하는 것입니다.

이 코드가 왜 나쁜가요?

private int GetNine() {
    for (int i = 0; i < 10; i++) {
        if (i == 9) return i;
    }
}

이 기능을 프로파일 링하면 일반적인 비즈니스 응용 프로그램에서 상당히 빠르게 수행된다는 것을 알 수있을 것입니다. 그것은 훨씬 더 잘 할 수있는 것을 성취하는 끔찍하게 비효율적 인 방법이라는 사실을 바꾸지 않습니다.

이것이 사람들이 예외 "남용"에 대해 이야기 할 때 의미하는 것입니다.


2
"예외 처리에는 엄청난 성능이 있습니다." -구현 세부 사항이며 모든 언어, 특히 C ++의 경우 모든 구현에 적용되지는 않습니다.
Pavel Minaev

"구현 세부 사항입니다"나는이 주장을 얼마나 자주 들었는지 기억이 나지 않으며 단지 냄새가납니다. 기억하십시오 : 모든 컴퓨터 관련 사항은 구현 세부 사항의 합계에 관한 것입니다. 또한 : 망치 대신 스크류 드라이버를 사용하는 것은 현명하지 않습니다. 이것이 사람들이하는 일이며 "구현 세부 사항"에 대해 많이 이야기합니다.
Juergen

2
그럼에도 불구하고 Python은 일반적인 흐름 제어에 예외를 기꺼이 사용합니다. 왜냐하면 구현에 대한 성능 저하가 그다지 나쁘지 않기 때문입니다. 망치가 드라이버처럼
Pavel Minaev

1
@j_random_hacker : 당신 말이 맞아요 GetNine. 내 요점은 그것을 사용할 이유가 없다는 것입니다. 더 "올바른"접근 방식이 훨씬 더 많은 노력을 필요로하는 반면, 무언가를 수행하는 "빠르고 쉬운"방법이 아닙니다. 내 경험상 "올바른"접근 방식이 실제로 노력 이 들거나 거의 같은 경우가 종종 있습니다 . 어쨌든, 나에게 성능은 왼쪽과 오른쪽 예외를 사용하지 않는 유일한 객관적인 이유입니다. 성능 손실이 "엄청나게 과대 평가"되었는지 여부는 사실 주관적입니다.
Dan Tao

1
@j_random_hacker : 당신의 제쳐두고는 매우 흥미롭고 구현에 따라 성능 저하에 대한 Pavel의 요점을 실제로 이끌어냅니다. 내가 매우 의심하는 한 가지는 예외를 사용하는 것이 실제로 대안을 능가 하는 시나리오를 찾을 수 있다는 것입니다 (적어도 내 예에서와 같이 대안이 존재하는 경우).
Dan Tao

6

예외에 대한 모든 경험 규칙은 주관적인 용어로 내려갑니다. 언제 사용하고 사용하지 않는지에 대해 어렵고 빠른 정의를 기 대해서는 안됩니다. "예외 상황에서만". 멋진 순환 정의 : 예외는 예외적 인 상황을위한 것입니다.

예외를 사용하는시기는 "이 코드가 한 클래스인지 두 클래스인지 어떻게 알 수 있습니까?"와 동일한 버킷에 속합니다. 부분적으로는 스타일 문제이고 부분적으로는 선호도입니다. 예외는 도구입니다. 그것들은 사용되고 남용 될 수 있으며, 둘 사이의 경계를 찾는 것은 프로그래밍의 기술과 기술의 일부입니다.

많은 의견과 트레이드 오프가 있습니다. 당신에게 말하는 것을 찾아서 따르십시오.


6

예외가 거의 사용되지 않아야하는 것은 아닙니다. 단지 예외적 인 상황에서만 던져 져야한다는 것입니다. 예를 들어 사용자가 잘못된 암호를 입력하더라도 예외는 아닙니다.

이유는 간단합니다. 예외는 갑자기 함수를 종료하고 스택을 catch블록으로 전파합니다 . 이 프로세스는 계산 비용이 매우 많이 듭니다. C ++는 "정상적인"함수 호출에 대한 오버 헤드가 거의 없도록 예외 시스템을 구축합니다. 따라서 예외가 발생하면 어디로 가야할지 찾기 위해 많은 작업을해야합니다. 또한 모든 코드 줄에서 예외가 발생할 수 있기 때문입니다. f예외를 자주 발생시키는 함수 가 있다면 이제 모든 호출에 try/ catch블록 을 사용하도록주의해야합니다 f. 그것은 매우 나쁜 인터페이스 / 구현 결합입니다.


8
당신은 본질적으로 "이유 때문에 예외라고 불린다"는 근거를 반복했습니다. 나는 이것을 더 실질적인 것으로 구체화 할 사람들을 찾고 있습니다.
Catskul

4
"예외적 인 상황"이란 무엇을 의미합니까? 실제로 내 프로그램은 잘못된 사용자 암호보다 실제 IOException을 더 자주 처리해야합니다. 그게 내가 해야 한다고 생각하는 건가요? BadUserPassword에게 예외를 만들거나 다음 stdlib 사람이 있음을 IOException이에게 예외를 만들었다? "매우 계산적으로 비싸다"는 것이 실제 이유가 될 수 없습니다. 제어 메커니즘을 통해 잘못된 암호를 처리하는 것이 성능 병목 현상을 일으키는 프로그램을 본 적이 없기 때문입니다.
Ken

5

C ++ 예외 에 대한 기사 에서이 문제를 언급했습니다 .

관련 부분 :

거의 항상 예외를 사용하여 "정상적인"흐름에 영향을주는 것은 나쁜 생각입니다. 3.1 절에서 이미 논의했듯이 예외는 보이지 않는 코드 경로를 생성합니다. 이러한 코드 경로는 오류 처리 시나리오에서만 실행되는 경우 허용됩니다. 그러나 다른 목적으로 예외를 사용하면 "정상적인"코드 실행이 보이는 부분과 보이지 않는 부분으로 나뉘어 코드를 읽고 이해하고 확장하기가 매우 어렵습니다.


1
보시다시피 저는 C에서 C ++로 왔고 "오류 처리 시나리오"는 정상이라고 생각합니다. 자주 실행되지는 않지만 오류가없는 코드 경로에 대해 생각하는 것보다 더 많은 시간을 보냅니다. 다시 말하지만, 저는 일반적으로 그 정서에 동의하지만 "정상"을 정의하는 데 더 가까워지지 않으며, 예외를 사용하는 것이 나쁜 선택이라는 "정상"의 정의에서 추론 할 수있는 방법은 없습니다.
Steve Jessop

또한 C에서 C ++로 왔지만 CI에서도 "정상적인"흐름 종료 오류 처리를 구분했습니다. 예를 들어 fopen ()을 호출하면 성공 사례를 처리하는 "정상"분기와 가능한 실패 원인을 처리하는 분기가 있으며 이것이 오류 처리입니다.
Nemanja Trifunovic

2
맞습니다.하지만 오류 코드에 대해 추론 할 때 이상하게도 영리하지 않습니다. 따라서 "보이지 않는 코드 경로"가 "정상적인"코드에 대해 읽고 이해하고 확장하기가 매우 어렵다면 "오류"라는 단어를 적용하면 "논의의 여지가있는"코드가되는 이유를 전혀 알 수 없습니다. 오류 상황은 내 경험상 항상 발생하며 이것이 "정상"으로 만듭니다. 오류 상황에서 예외를 파악할 수 있다면 오류가 아닌 상황에서 예외를 파악할 수 있습니다. 당신이 할 수 없다면, 당신은 할 수 없습니다. 그리고 당신이 한 모든 것은 당신의 오류 코드를 뚫을 수 없게 만들었지 만 "오직 오류"이기 때문에 그 사실을 무시하는 것입니다.
Steve Jessop

1
예 : 어떤 일을 수행해야하는 기능이 있지만 다양한 접근 방식을 시도 할 것이며 어떤 접근 방식이 성공할지 모르고 각각 추가 하위 접근 방식을 시도 할 수 있습니다. 무언가가 작동 할 때까지 계속됩니다. 그런 다음 실패는 "정상"이고 성공은 "예외적"이지만 분명히 오류는 아니라고 주장합니다. 성공시 예외를 던지는 것은 대부분의 프로그램에서 끔찍한 오류에 대한 예외를 던지는 것보다 이해하기 어렵지 않습니다. 단 한 비트의 코드 만 다음에 수행 할 작업을 알 필요가 있는지 확인하고 바로 바로 이동할 수 있습니다.
Steve Jessop

1
@onebyone : 귀하의 두 번째 댓글은 실제로 다른 곳에서 본 적이없는 좋은 지적입니다. 이에 대한 대답은 오류 조건에 대한 예외를 사용하는 (자신의 답변에서 효과적으로 말했듯이) 많은 언어의 "표준 관행"에 흡수되어 원래의 이유가 있더라도 준수하는 유용한 지침이되었다는 것입니다. 그렇게하는 것은 잘못된 것입니다.
j_random_hacker

5

오류 처리에 대한 나의 접근 방식은 오류의 세 가지 기본 유형이 있다는 것입니다.

  • 오류 사이트에서 처리 할 수있는 이상한 상황. 사용자가 명령 줄 프롬프트에서 잘못된 입력을 입력 한 경우 일 수 있습니다. 올바른 동작은 단순히 사용자에게 불평하고이 경우 반복하는 것입니다. 또 다른 상황은 0으로 나누기 일 수 있습니다. 이러한 상황은 실제로 오류 상황이 아니며 일반적으로 잘못된 입력으로 인해 발생합니다.
  • 이전과 같은 상황이지만 오류 사이트에서 처리 할 수없는 상황입니다. 예를 들어 파일 이름을 가져 와서 해당 이름으로 파일을 구문 분석하는 함수가있는 경우 파일을 열지 못할 수 있습니다. 이 경우 오류를 처리 할 수 ​​없습니다. 이것은 예외가 빛나는 때입니다. C 접근 방식을 사용하는 대신 (잘못된 값을 플래그로 반환하고 전역 오류 변수를 설정하여 문제를 표시) 코드에서 예외를 throw 할 수 있습니다. 그러면 호출 코드가 예외를 처리 할 수 ​​있습니다. 예를 들어 사용자에게 다른 파일 이름을 묻는 메시지가 표시됩니다.
  • 일어나서는 안되는 상황. 클래스 불변이 위반되거나 함수가 유효하지 않은 매개 변수 등을 수신하는 경우입니다. 이것은 코드 내의 논리 오류를 나타냅니다. 실패 수준에 따라 예외가 적절하거나 즉시 강제 종료하는 것이 바람직 할 수 있습니다 assert. 일반적으로 이러한 상황은 코드의 어딘가에서 문제가 발생했음을 나타내며 다른 어떤 것도 올바른 것으로 신뢰할 수 없습니다. 메모리 손상이 만연 할 수 있습니다. 당신의 배는 가라 앉고 있습니다.

바꿔 말하면 예외는 처리 할 수있는 문제가 있지만 눈치 채는 곳에서 처리 할 수없는 경우입니다. 처리 할 수없는 문제는 단순히 프로그램을 종료해야합니다. 즉시 처리 할 수있는 문제는 간단히 처리해야합니다.


2
당신은 잘못된 질문에 대답하고 있습니다. 우리는 오류 시나리오를 처리하기 위해 예외를 사용해야하는 이유를 알고 싶지 않습니다. 오류 처리가 아닌 시나리오에 예외를 사용해야하는 이유를 알고 싶습니다.
j_random_hacker

5

여기에서 몇 가지 답변을 읽었습니다. 나는이 모든 혼란이 무엇에 관한 것인지 여전히 놀랍습니다. 이 모든 예외 == spagetty 코드에 강력히 동의하지 않습니다. 혼란스러워하는 것은 C ++ 예외 처리를 좋아하지 않는 사람들이 있다는 것을 의미합니다. C ++ 예외 처리에 대해 어떻게 배웠는지 확신 할 수 없지만 몇 분 안에 그 의미를 이해했습니다. 1996 년경 OS / 2 용 Borland C ++ 컴파일러를 사용하고있었습니다. 예외를 언제 사용할지 결정하는 데 문제가 없었습니다. 저는 보통 오류가있는 do-undo 작업을 C ++ 클래스로 래핑합니다. 이러한 실행 취소 작업에는 다음이 포함됩니다.

  • 시스템 핸들 생성 / 파괴 (파일, 메모리 맵, WIN32 GUI 핸들, 소켓 등)
  • 핸들러 설정 / 설정 해제
  • 메모리 할당 / 할당 해제
  • 뮤텍스 청구 / 해제
  • 참조 횟수 증가 / 감소
  • 창 표시 / 숨기기

기능적 래퍼가 있습니다. 시스템 호출 (이전 범주에 속하지 않음)을 C ++로 래핑합니다. 예 : 파일 읽기 / 쓰기. 실패하면 오류에 대한 전체 정보가 포함 된 예외가 발생합니다.

그런 다음 실패에 더 많은 정보를 추가하기 위해 예외를 포착 / 다시 던집니다.

전반적인 C ++ 예외 처리는 더 깨끗한 코드로 이어집니다. 코드의 양이 크게 줄어 듭니다. 마지막으로 생성자를 사용하여 오류가있는 리소스를 할당하고 이러한 실패 후에도 손상없는 환경을 유지할 수 있습니다.

이러한 클래스를 복잡한 클래스로 연결할 수 있습니다. 일부 멤버 / 기본 개체의 생성자가 실행되면 동일한 개체 (이전에 실행 된)의 다른 모든 생성자가 성공적으로 실행되었음을 신뢰할 수 있습니다.


3

예외는 기존 구조 (루프, ifs, 함수 등)에 비해 매우 특이한 흐름 제어 방법입니다. 일반 제어 흐름 구조 (루프, ifs, 함수 호출 등)는 모든 정상적인 상황을 처리 할 수 ​​있습니다. 일상적인 발생에 대한 예외에 도달하면 코드가 어떻게 구조화되는지 고려해야합니다.

그러나 일반 구조로는 쉽게 처리 할 수없는 특정 유형의 오류가 있습니다. 리소스 할당 실패와 같은 치명적인 실패는 낮은 수준에서 감지 될 수 있지만 여기서 처리 할 수는 없으므로 간단한 if 문은 부적절합니다. 이러한 유형의 오류는 일반적으로 훨씬 더 높은 수준에서 처리해야합니다 (예 : 파일 저장, 오류 기록, 종료). 반환 값과 같은 전통적인 방법을 통해 이와 같은 오류를보고하는 것은 지루하고 오류가 발생하기 쉽습니다. 또한이 기괴하고 비정상적인 오류를 처리하기 위해 중간 수준 API 계층에 오버 헤드를 주입합니다. 오버 헤드는 이러한 API의 클라이언트를 산만하게하고 제어 할 수없는 문제에 대해 걱정해야합니다. 예외는 큰 오류에 대해 로컬이 아닌 처리를 수행하는 방법을 제공합니다.

클라이언트가 ParseInt문자열로 호출 하고 문자열에 정수가 포함되어 있지 않은 경우 즉시 호출자는 오류에 대해 관심을 갖고 처리 방법을 알고 있습니다. 따라서 ParseInt를 설계하여 이와 같은 오류 코드를 반환합니다.

반면에 ParseInt메모리가 끔찍하게 조각화되어 버퍼를 할당 할 수 없기 때문에 실패하면 호출자는 이에 대해 무엇을해야할지 알 수 없습니다. 이 비정상적인 오류를 이러한 근본적인 오류를 처리하는 일부 계층까지 버블 링해야합니다. 이는 그들 자신의 API에서 오류 전달 메커니즘을 수용해야하기 때문에 그 사이에있는 모든 사람에게 세금을 부과합니다. 예외로 인해 해당 레이어를 건너 뛸 수 있습니다 (필요한 정리 작업이 계속 발생하도록 보장).

저수준 코드를 작성할 때 기존 메서드를 사용할시기와 예외를 throw 할시기를 결정하기가 어려울 수 있습니다. 낮은 수준의 코드는 결정을 내려야합니다 (던지기 여부). 그러나 무엇이 예상되고 무엇이 예외적인지를 진정으로 아는 것은 더 높은 수준의 코드입니다.


1
그럼에도 불구하고, 자바, parseInt실제로 않는 예외를 throw합니다. 따라서 예외 발생의 적절성에 대한 의견은 선택한 개발 언어의 표준 라이브러리 내의 기존 관행에 크게 의존한다고 말하고 싶습니다.
Charles Salvia

Java의 런타임은 어쨌든 정보를 추적해야하므로 예외를 쉽게 생성 할 수 있습니다. C ++ 코드는 해당 정보를 손에 넣지 않는 경향이 있으므로이를 생성하면 성능이 저하됩니다. 손에 들고 있지 않으면 더 빠르고 / 작고 / 더 캐시 친화적 인 경향이 있습니다. 또한 Java 코드는 배열 등과 같은 항목에 대한 동적 검사를 수행하며 C ++에 내재되지 않은 기능이므로 개발자가 Java 추가 예외 클래스입니다. 이미 try / catch 블록으로 이러한 많은 일을 처리하고 있으므로 모든 것에 대해 예외를 사용하지 않는 이유는 무엇입니까?
Ape-inago

1
@Charles-질문에 C ++ 태그가 지정되어 있으므로 그 관점에서 대답했습니다. Java (또는 C #) 프로그래머가 예외는 예외적 인 조건에만 해당한다고 말하는 것을 들어 본 적이 없습니다. C ++ 프로그래머는 이것을 정기적으로 말하고 질문은 이유에 관한 것입니다.
Adrian McCarthy

1
사실, C # 예외도 그렇게 말합니다. 그 이유는 예외가 .NET에서 발생하는 데 매우 비싸기 때문입니다 (예 : Java에서보다 더 높음).
Pavel Minaev

1
@Adrian : 사실, 언어가 서로 영향을 미치기 때문에 예외 처리 철학은 다소 교차 언어 대화에 해당하지만 질문은 C ++로 태그가 지정됩니다. 어쨌든 Boost.lexical_cast는 예외를 발생시킵니다. 그러나 유효하지 않은 문자열은 정말 "예외"입니까? 아마도 그렇지 않을 수도 있지만 Boost 개발자는 멋진 캐스트 구문을 갖는 것이 예외를 사용할 가치가 있다고 생각했습니다. 이것은 전체가 얼마나 주관적인지를 보여주기위한 것입니다.
Charles Salvia

3

C ++에는 몇 가지 이유가 있습니다.

첫째, 예외가 어디에서 오는지 (거의 모든 것에서 던져 질 수 있기 때문에) 종종보기가 어렵고 catch 블록은 COME FROM 문에 속합니다. GO TO에서 어디에서 왔는지 (무작위 함수 호출이 아닌 문)와 어디로 가는지 (라벨)를 알고 있기 때문에 GO TO보다 더 나쁩니다. 기본적으로 C의 setjmp () 및 longjmp ()의 잠재적으로 리소스 안전 버전이며 아무도 사용하고 싶지 않습니다.

둘째, C ++에는 가비지 컬렉션이 내장되어 있지 않으므로 리소스를 소유 한 C ++ 클래스는 소멸자에서이를 제거합니다. 따라서 C ++ 예외 처리에서 시스템은 범위 내에서 모든 소멸자를 실행해야합니다. GC가 있고 Java와 같은 실제 생성자가없는 언어에서는 예외를 던지는 것이 훨씬 덜 부담 스럽습니다.

셋째, Bjarne Stroustrup, 표준위원회 및 다양한 컴파일러 작성자를 포함한 C ++ 커뮤니티는 예외가 예외적이어야한다고 가정하고 있습니다. 일반적으로 언어 문화에 반하는 것은 가치가 없습니다. 구현은 예외가 드물다는 가정을 기반으로합니다. 더 나은 책은 예외를 예외적으로 취급합니다. 좋은 소스 코드는 예외를 거의 사용하지 않습니다. 좋은 C ++ 개발자는 예외를 예외적으로 취급합니다. 그것에 반대하려면 타당한 이유를 원하고 내가 보는 모든 이유는 예외적으로 유지하는 편에 있습니다.


1
"C ++에는 가비지 컬렉션이 내장되어 있지 않으므로 리소스를 소유 한 C ++ 클래스는 소멸자에서 제거합니다. 따라서 C ++ 예외 처리에서 시스템은 범위 내에서 모든 소멸자를 실행해야합니다. GC가 있고 실제 생성자가없는 언어에서는 , 자바처럼 예외를 던지는 일은 훨씬 덜 부담 스럽습니다. " -소멸자는 정상적으로 범위를 벗어날 때 실행되어야하며, Java finally블록은 구현 오버 헤드 측면에서 C ++ 소멸자와 다르지 않습니다.
Pavel Minaev

나는이 대답을 좋아하지만 Pavel처럼 두 번째 요점이 합법적이라고 생각하지 않습니다. 다른 유형의 오류 처리 또는 프로그램 계속을 포함하여 어떤 이유로 범위가 종료되면 어쨌든 소멸자가 호출됩니다.
Catskul

흐름을 따라가는 이유로 언어 문화를 언급 한 +1. 그 대답은 어떤 사람들에게는 불만족 스럽지만 진정한 이유입니다 (그리고 가장 정확한 이유라고 생각합니다).
j_random_hacker

@Pavel : 위의 잘못된 설명 (현재 삭제됨)을 무시하고 Java finally블록이 실행된다는 보장은 없습니다. 물론 그렇습니다 . finalize()실행이 보장되지 않는 Java 메서드 와 혼동되었습니다 .
j_random_hacker

이 답변을 돌아 보면 소멸자의 문제는 잠재적으로 각 함수 반환과 함께 간격을 두는 것이 아니라 모두 즉시 호출되어야한다는 것입니다. 그것은 여전히 ​​좋은 이유는 아니지만 약간의 타당성이 있다고 생각합니다.
David Thornley

2

다음은 예외를 제어 흐름으로 사용하는 나쁜 예입니다.

int getTotalIncome(int incomeType) {
   int totalIncome= 0;
   try {
      totalIncome= calculateIncomeAsTypeA();
   } catch (IncorrectIncomeTypeException& e) {
      totalIncome= calculateIncomeAsTypeB();
   }

   return totalIncome;
}

매우 나쁘지만 다음과 같이 작성해야합니다.

int getTotalIncome(int incomeType) {
   int totalIncome= 0;
   if (incomeType == A) {
      totalIncome= calculateIncomeAsTypeA();
   } else if (incomeType == B) {
      totalIncome= calculateIncomeAsTypeB();
   }
   return totalIncome;
}

이 두 번째 예제는 분명히 약간의 리팩토링이 필요하지만 (디자인 패턴 전략 사용과 같은) 예외가 제어 흐름을위한 것이 아님을 잘 보여줍니다.

예외에도 일부 성능 불이익이 있지만 성능 문제는 "조기 최적화는 모든 악의 근원"이라는 규칙을 따라야합니다.


확인하는 대신 더 많은 다형성이 좋을 때처럼 보입니다 incomeType.
Sarah Vessels

@Sarah 그렇습니다 나는 그것이 좋을 것이라는 것을 압니다. 여기는 설명 목적으로 만 제공됩니다.
Edison Gustavo Muenz

3
왜 나쁜가요? 왜 두 번째 방식으로 작성해야합니까? 질문자는 규칙이 아닌 규칙에 대한 이유를 알고 싶어합니다. -1.
j_random_hacker

2
  1. 유지 관리 성 : 위에서 언급 한 것처럼 모자 한 방울에 예외를 던지는 것은 gotos를 사용하는 것과 비슷합니다.
  2. 상호 운용성 : 예외를 사용하는 경우 C / Python 모듈과 C ++ 라이브러리를 인터페이스 할 수 없습니다.
  3. 성능 저하 : RTTI는 추가 오버 헤드를 부과하는 예외 유형을 실제로 찾는 데 사용됩니다. 따라서 예외는 일반적으로 발생하는 사용 사례를 처리하는 데 적합하지 않습니다 (사용자가 문자열 대신 int 입력 등).

2
1. 그러나 일부 언어에서 예외 모자 한 방울에 던져집니다. 3. 때로는 성능이 중요하지 않습니다. (시간의 80 %?)
UncleBens

2
2. C ++ 프로그램은 대부분의 표준 라이브러리에서 예외를 사용하기 때문에 거의 항상 예외를 사용합니다. 컨테이너 클래스가 발생합니다. 문자열 클래스가 발생합니다. 스트림 클래스가 발생합니다.
David Thornley

다음을 제외하고 어떤 컨테이너 및 문자열 작업이 발생 at()합니까? 즉, 누구나 new던질 수 있으므로 ...
Pavel Minaev

RTTI는 내가 이해하는 한 Try / Throw / Catch에 꼭 필요한 것은 아닙니다. 성능 저하가 항상 언급되고 있다고 믿습니다.하지만 아무도 스케일이나 참조에 대한 링크를 언급하지 않습니다.
Catskul

UncleBens : (1)은 성능이 아닌 유지 관리를위한 것입니다. try / catch / throw를 과도하게 사용하면 코드의 가독성이 떨어집니다. 경험의 법칙 (그리고 나는 이것에 대해 불이 붙을 수도 있지만 내 의견 일뿐입니다), 진입 점과 출구 점이 적을수록 코드를 읽기가 더 쉽습니다. David Thornley : 상호 운용성이 필요할 때는 아닙니다. gcc의 -fno-exceptions 플래그는 C 코드에서 호출되는 라이브러리를 컴파일 할 때 예외를 비활성화합니다.
Sridhar Iyer

2

예외는 안전한 방식으로 현재 컨텍스트 (현재 스택 프레임에서 벗어나지 만 그 이상입니다)에서 벗어나게하는 메커니즘이라고 말하고 싶습니다. 구조화 된 프로그래밍이 가장 가까운 것입니다. 사용하도록 의도 된 방식으로 예외를 사용하려면 지금하고있는 작업을 계속할 수없고 현재 위치에서 처리 할 수없는 상황이 있어야합니다. 따라서 예를 들어 사용자의 비밀번호가 틀린 경우 false를 반환하여 계속할 수 있습니다. 그러나 UI 하위 시스템이 사용자에게 메시지를 표시 할 수 없다고보고하면 단순히 "로그인 실패"를 반환하는 것은 잘못된 것입니다. 현재 수준의 코드는 무엇을해야할지 모릅니다. 따라서 예외 메커니즘을 사용하여 수행 할 작업을 알고있는 위의 사람에게 책임을 위임합니다.


2

매우 실용적인 이유 중 하나는 프로그램을 디버깅 할 때 응용 프로그램을 디버깅하기 위해 종종 First Chance Exceptions (Debug-> Exceptions)를 뒤집기 때문입니다. 예외가 많이 발생하는 경우 "잘못된"부분을 찾기가 매우 어렵습니다.

또한 악명 높은 "캐치 스로우 (catch throw)"와 같은 일부 안티 패턴으로 이어지고 실제 문제를 난독 화합니다. 이에 대한 자세한 내용은 해당 주제에 대해 작성한 블로그 게시물 을 참조하십시오 .


2

가능한 한 예외를 사용하는 것을 선호합니다. 예외로 인해 개발자는 실제 오류 일 수도 있고 아닐 수도있는 일부 조건을 처리해야합니다. 문제의 예외가 치명적인 문제인지 또는 즉시 처리 해야하는 문제인지에 대한 정의입니다 .

이에 대한 반대 주장은 게으른 사람들이 자신의 발을 쏘기 위해 더 많이 입력해야한다는 것입니다.

Google의 코딩 정책은 특히 C ++에서 예외를 사용하지 말라고합니다. 애플리케이션이 예외를 처리 할 준비가되어 있지 않거나 그렇지 않습니다. 그렇지 않은 경우 응용 프로그램이 죽을 때까지 예외가 전파 될 것입니다.

사용했던 일부 라이브러리에서 예외가 발생하고이를 처리 할 준비가되지 않은 것을 찾는 것은 결코 재미가 없습니다.


1

예외를 발생시키는 합법적 인 사례 :

  • 파일을 열려고하면 파일이 없으면 FileNotFoundException이 발생합니다.

불법 사례 :

  • 파일이없는 경우에만 작업을 수행하고 파일을 열고 catch 블록에 코드를 추가합니다.

특정 지점까지 응용 프로그램의 흐름끊고 싶을 때 예외를 사용 합니다. . 이 지점은 해당 예외에 대한 catch (...)가있는 곳입니다. 예를 들어 많은 프로젝트를 처리해야하고 각 프로젝트는 다른 프로젝트와 독립적으로 처리해야하는 경우가 매우 일반적입니다. 따라서 프로젝트를 처리하는 루프에는 try ... catch 블록이 있으며, 프로젝트 처리 중에 일부 예외가 발생하면 해당 프로젝트에 대한 모든 것이 롤백되고 오류가 기록되고 다음 프로젝트가 처리됩니다. 인생은 계속됩니다.

존재하지 않는 파일, 유효하지 않은 표현식 및 유사한 항목에 대해서는 예외를 사용해야한다고 생각합니다. 쉽고 저렴한 대안이 있다면 범위 테스트 / 데이터 유형 테스트 / 파일 존재 / 그 외 무엇이든 예외를 사용해서는 안됩니다. 이런 종류의 논리는 코드를 이해하기 어렵게 만들기 때문에 범위 테스트 / 데이터 유형 테스트 / 파일 존재 / 그외에 쉽고 저렴한 대안이 있다면 예외를 사용해서는 안됩니다.

RecordIterator<MyObject> ri = createRecordIterator();
try {
   MyObject myobject = ri.next();
} catch(NoSuchElement exception) {
   // Object doesn't exist, will create it
}

이것이 더 나을 것입니다.

RecordIterator<MyObject> ri = createRecordIterator();
if (ri.hasNext()) {
   // It exists! 
   MyObject myobject = ri.next();
} else {
   // Object doesn't exist, will create it
}

답변에 추가 된 의견 :

아마도 내 예제가 그다지 좋지 않았을 수도 있습니다. ri.next ()는 두 번째 예제에서 예외를 던져서는 안됩니다. 만약 그렇다면 정말 예외적 인 것이 있고 다른 조치를 취해야합니다. 예제 1이 많이 사용되는 경우 개발자는 특정 예외 대신 일반 예외를 포착하고 예외가 예상되는 오류로 인한 것이지만 다른 원인으로 인한 것일 수 있다고 가정합니다. 결국 이것은 예외가 예외가 아닌 애플리케이션 흐름의 일부가 되었기 때문에 실제 예외가 무시되는 결과로 이어집니다.

이것에 대한 의견은 내 대답 자체보다 더 많은 것을 추가 할 수 있습니다.


1
왜 안돼? 언급 한 두 번째 경우에 예외를 사용하지 않는 이유는 무엇입니까? 질문자는 규칙이 아니라 규칙에 대한 이유 를 알고 싶어합니다 .
j_random_hacker

정교하게 해주셔서 감사합니다. IMHO 두 코드 스 니펫은 거의 동일한 복잡성을 가지고 있습니다. 둘 다 고도로 지역화 된 제어 로직을 사용합니다. 예외의 복잡성이 그 / 그런 다음 / 그렇지 않은 경우 try 블록 내에 여러 개의 문이있을 때 가장 명확하게 예외가 발생하는 경우, 그중 하나가 throw 될 수 있습니다. 동의하십니까?
j_random_hacker

아마도 내 예제가 그다지 좋지 않았을 수도 있습니다. ri.next ()는 두 번째 예제에서 예외를 던져서는 안됩니다. 만약 그렇다면 정말 예외적 인 것이 있고 다른 조치를 취해야합니다. 예제 1이 많이 사용되는 경우 개발자는 특정 예외 대신 일반 예외를 포착하고 예외가 예상되는 오류로 인한 것이지만 다른 원인으로 인한 것일 수 있다고 가정합니다. 결국 이것은 예외가 예외가 아닌 애플리케이션 흐름의 일부가 되었기 때문에 실제 예외가 무시되는 결과로 이어집니다.
Ravi Wallau

그래서 당신은 이렇게 말하고 있습니다 : 시간이 지남에 따라 다른 문장들이 try블록 내부에 추가 될 수 있고 당신은 그 catch블록이 당신이 잡는 것이라고 생각했던 것을 실제로 잡는 것인지 더 이상 확신 할 수 없습니다. 맞습니까? if / then / else 접근 방식을 같은 방식으로 오용하는 것은 더 어렵지만 한 번에 한 가지만 테스트 할 수 있기 때문에 한 번에 한 가지만 테스트 할 수 있으므로 예외적 인 접근 방식은 더 취약한 코드로 이어질 수 있습니다. 그렇다면 귀하의 답변에서 이것을 토론하십시오. 코드 취약성이 진정한 이유라고 생각하므로 기꺼이 +1하겠습니다.
j_random_hacker

0

기본적으로 예외는 구조화되지 않고 이해하기 어려운 흐름 제어의 형태입니다. 이는 정상적인 프로그램 흐름의 일부가 아닌 오류 조건을 처리 할 때 오류 처리 논리가 코드의 정상적인 흐름 제어를 너무 복잡하게 만드는 것을 방지하기 위해 필요합니다.

IMHO 예외는 호출자가 오류 처리 코드를 작성하는 것을 무시하거나 오류가 즉각적인 호출자보다 호출 스택에서 더 잘 처리 될 수있는 경우 정상적인 기본값을 제공하려는 경우 사용해야합니다. 정상적인 기본값은 적절한 진단 오류 메시지와 함께 프로그램을 종료하는 것입니다. 미친 대안은 프로그램이 잘못된 상태에서 절뚝 거리고 나중에 충돌하거나 조용히 잘못된 출력을 생성하여 지점을 진단하기 어렵다는 것입니다. "오류"가 프로그램 흐름의 정상적인 부분이므로 호출자가 확인하는 것을 합리적으로 잊을 수없는 경우 예외를 사용해서는 안됩니다.


0

"드물게 사용한다"는 말이 올바른 문장이 아니라고 생각합니다. 나는 "예외 상황에서만 던지기"를 선호합니다.

많은 사람들이 정상적인 상황에서 예외를 사용해서는 안되는 이유를 설명했습니다. 예외에는 오류 처리 및 순전히 오류 처리에 대한 권리가 있습니다.

다른 점에 초점을 맞출 것입니다.

다른 한 가지는 성능 문제입니다. 컴파일러는 빠른 속도를 얻기 위해 오랫동안 고생했습니다. 현재 정확한 상태는 확실하지 않지만 제어 흐름에 예외를 사용하면 다른 문제가 발생합니다. 프로그램이 느려집니다!

그 이유는 예외는 매우 강력한 goto-statements 일뿐만 아니라 남겨진 모든 프레임에 대해 스택을 풀어야하기 때문입니다. 따라서 암시 적으로 스택의 객체도 해체해야합니다. 따라서이를 인식하지 못한 채 예외를 한 번만 던지면 실제로 모든 메커니즘이 관여하게됩니다. 프로세서는 많은 일을해야합니다.

따라서 당신은 모르게 프로세서를 우아하게 태우게 될 것입니다.

따라서 예외적 인 경우에만 예외를 사용하십시오. 의미 : 실제 오류가 발생한 경우!


1
"그 이유는 예외가 매우 강력한 goto-statement 일뿐만 아니라 떠나는 모든 프레임에 대해 스택을 풀어야하기 때문입니다. 따라서 스택에있는 객체도 묵시적으로 분해해야합니다." -콜 스택에서 여러 스택 프레임을 다운했다면 모든 스택 프레임 (및 그 위에있는 객체)은 결국 결국 파괴되어야합니다. 정상적으로 돌아 왔기 때문에 발생하는지 아니면 던지기 때문에 발생하는지는 중요하지 않습니다. 예외. 결국을 호출 std::terminate()하지 않아도 여전히 파괴해야합니다.
Pavel Minaev

물론 당신이 옳습니다. 그러나 여전히 기억하십시오. 예외를 사용할 때마다 시스템은이 모든 것을 마술처럼 수행 할 수있는 인프라를 제공해야합니다. 나는 단지 고토가 아니라는 것을 약간 분명히하고 싶었습니다. 또한 풀릴 때 시스템은 적절한 잡기 장소를 찾아야하며 추가 시간, 코드 및 RTTI 사용이 필요합니다. 그래서 그것은 점프 그 이상입니다. 대부분의 사람들은 이것을 이해하지 못합니다.
Juergen

표준 호환 C ++를 고수하는 한 RTTI는 피할 수 없습니다. 그렇지 않으면 물론 오버 헤드가 있습니다. 나는 종종 그것을 크게 과장하는 것을 종종 본다.
Pavel Minaev

1
-1. "예외는 오류 처리 및 순전히 오류 처리에 대한 권리가 있습니다."-누가 말합니까? 왜? 성능 만이 유일한 이유는 아닙니다. (A) 세계의 대부분의 코드는 게임 렌더링 엔진이나 행렬 곱셈 함수의 가장 안쪽 루프에 있지 않으므로 예외를 사용하면 성능 차이가 눈에 띄지 않습니다. (B) 어딘가에 주석에서 언급했듯이 모든 스택 해제는 결국 이전 C 스타일의 오류 반환 값 확인 및 필요한 경우 통과 오류가 발생하더라도 궁극적으로 발생해야합니다. 처리 방식이 사용되었습니다.
j_random_hacker

I.이 스레드에서 많은 이유를 인용했습니다. 그냥 읽어보세요. 하나만 설명하고 싶었습니다. 당신이 필요로 한 것이 아니라면 나를 비난하지 마십시오.
Juergen

0

예외의 목적은 소프트웨어 내결함성을 만드는 것입니다. 그러나 함수에서 발생하는 모든 예외에 대한 응답을 제공해야하는 경우 억제가 발생합니다. 예외는 프로그래머가 루틴에서 특정 사항이 잘못 될 수 있으며 클라이언트 프로그래머가 이러한 조건을 인식하고 필요에 따라 처리해야한다는 점을 프로그래머로 하여금 인정하도록하는 공식적인 구조 일뿐입니다.

솔직히 말해서 예외는 프로그래밍 언어에 추가되어 개발자에게 오류 사례 처리 책임을 직계 개발자에서 미래의 개발자로 전환하는 공식적인 요구 사항을 제공하기 위해 추가되었습니다.

좋은 프로그래밍 언어는 C ++ 및 Java에서 알고있는 예외를 지원하지 않는다고 생각합니다. 함수의 모든 종류의 반환 값에 대한 대체 흐름을 제공 할 수있는 프로그래밍 언어를 선택해야합니다. 프로그래머는 루틴의 모든 형태의 출력을 예상하고 내가 할 수 있다면 별도의 코드 파일에서 처리해야합니다.


0

다음과 같은 경우 예외를 사용합니다.

  • 로컬에서 복구 할 수없는 오류가 발생했습니다.
  • 프로그램에서 오류가 복구되지 않으면 종료해야합니다.

오류를 복구 할 수있는 경우 (사용자가 숫자 대신 "apple"입력) 복구 (입력을 다시 요청하고 기본값으로 변경하는 등)하십시오.

로컬에서 오류를 복구 할 수 없지만 응용 프로그램을 계속할 수있는 경우 (사용자가 파일을 열려고했지만 파일이 존재하지 않음) 오류 코드가 적절합니다.

로컬에서 오류를 복구 할 수없고 복구하지 않고 애플리케이션을 계속할 수없는 경우 (메모리 / 디스크 공간 등이 부족한 경우) 예외가 올바른 방법입니다.


1
-1. 질문을주의 깊게 읽으십시오. 대부분의 프로그래머는 예외가 특정 유형의 오류 처리에 적합하다고 생각하거나 결코 적절하지 않다고 생각합니다. 다른 이색적인 흐름 제어 형식에 예외를 사용할 가능성도 고려하지 않습니다. 문제는 그 이유가 무엇입니까?
j_random_hacker

또한주의 깊게 읽어야합니다. 나는 "사용 방법에있어 예외적으로 보수적 인 철학은 무엇인가?"라고 대답했습니다. 그들이 사용되는 방식에 대해 보수적이라는 철학을 가지고 있습니다.
Bill

IMHO 보수주의가 필요한 이유를 설명하지 않았습니다 . 때때로 "적절"한 이유는 무엇입니까? 왜 항상 그렇지 않습니까? (BTW 내가 생각하는 당신의 제안 방법은 나 자신이, 난 그냥이 많은에 도착 생각하지 않는 것을 더 많거나 적은이다, 잘입니다 이유를 질문.)
j_random_hacker

OP는 일곱 가지 질문을 던졌습니다. 나는 하나만 대답하기로 결정했습니다. 투표 할 가치가 있다고 생각하신 다니 유감입니다.
Bill

0

보수적으로 사용해야한다고 누가 말했습니까? 흐름 제어에 예외를 사용하지 마십시오. 그리고 예외가 이미 던져졌을 때 누가 예외의 비용을 걱정합니까?


0

내 2 센트 :

오류가 발생하지 않는 것처럼 프로그래밍 할 수 있기 때문에 예외를 사용하는 것을 좋아합니다. 따라서 내 코드는 모든 종류의 오류 처리로 흩어져 있지 않고 읽을 수 있습니다. 물론 오류 처리 (예외 처리)는 끝 (catch 블록)으로 이동하거나 호출 수준의 책임으로 간주됩니다.

저에게 좋은 예는 파일 처리 또는 데이터베이스 처리입니다. 모든 것이 정상이라고 가정하고 마지막에 또는 일부 예외가 발생하면 파일을 닫으십시오. 또는 예외가 발생했을 때 트랜잭션을 롤백하십시오.

예외의 문제는 빠르게 매우 장황해진다는 것입니다. 코드가 매우 가독성을 유지하고 정상적인 흐름에만 집중할 수 있도록하기위한 것이었지만 일관되게 사용된다면 거의 모든 함수 호출이 try / catch 블록으로 래핑되어야하며 목적을 무너 뜨리기 시작합니다.

앞서 언급했듯이 ParseInt의 경우 예외 개념이 마음에 듭니다. 값을 반환하십시오. 매개 변수를 구문 분석 할 수없는 경우 예외를 발생시킵니다. 한편으로는 코드가 더 깔끔해집니다. 호출 수준에서 다음과 같은 작업을 수행해야합니다.

try 
{
   b = ParseInt(some_read_string);
} 
catch (ParseIntException &e)
{
   // use some default value instead
   b = 0;
}

코드는 깨끗합니다. 이처럼 ParseInt가 흩어져 있으면 예외를 처리하고 기본값을 반환하는 래퍼 함수를 ​​만듭니다. 예

int ParseIntWithDefault(String stringToConvert, int default_value=0)
{
   int result = default_value;
   try
   {
     result = ParseInt(stringToConvert);
   }
   catch (ParseIntException &e) {}

   return result;
}

결론적으로, 토론을 통해 제가 놓친 것은 예외가 오류 조건을 더 무시할 수 있기 때문에 코드를 더 쉽고 / 더 읽기 쉽게 만들 수 있다는 사실이었습니다. 문제점 :

  • 예외는 여전히 어딘가에서 처리되어야합니다. 추가 문제 : C ++에는 함수가 던질 수있는 예외를 지정할 수있는 구문이 없습니다 (Java처럼). 따라서 호출 수준은 처리해야 할 예외를 인식하지 못합니다.
  • 모든 함수를 try / catch 블록으로 래핑해야하는 경우 코드가 매우 장황해질 수 있습니다. 그러나 때때로 이것은 여전히 ​​의미가 있습니다.

그래서 때때로 좋은 균형을 찾기가 어렵습니다.


-1

미안하지만 대답은 "이유가있어서 예외라고 불린다." 그 설명은 "경험의 법칙"입니다. 한 문제 도메인에 대한 치명적인 예외 (영어 정의)가 다른 문제 도메인에 대한 정상적인 운영 절차이기 때문에 예외를 사용하거나 사용해서는 안되는 전체 상황을 제공 할 수 없습니다. 경험의 규칙은 맹목적으로 따르도록 설계되지 않았습니다. 대신 솔루션 조사를 안내하도록 설계되었습니다. "이를 예외라고하는 이유"는 호출자가 처리 할 수있는 정상적인 오류와 호출자가 특수 코딩 (캐치 블록)없이 처리 할 수없는 비정상적인 상황을 미리 결정해야 함을 알려줍니다.

거의 모든 프로그래밍 규칙은 "정말 좋은 이유가없는 한이 작업을 수행하지 마십시오"라는 지침입니다. "goto를 사용하지 마십시오", "전역 변수를 사용하지 마십시오", "정규식은 문제 수를 하나씩 사전 증가시킵니다. "등. 예외도 예외는 아닙니다 ....


... 그리고 질문자는 그것이 경험의 법칙이라는 것을 (아직 다시) 듣는 것보다 그것이 경험의 법칙 인 이유 를 알고 싶어합니다 . -1.
j_random_hacker

나는 내 응답에서 그것을 인정했다. 이유는 명확하지 않습니다. 경험의 규칙은 정의상 모호합니다. 명백한 이유가 있다면 그것은 경험의 법칙이 아니라 규칙 일 것입니다. 위의 다른 모든 답변의 모든 설명에는 경고가 포함되어 있으므로 이유도 설명하지 않습니다.
jmucchiello

명확한 "이유"는 없지만 다른 사람들이 언급하는 부분적인 "이유"가 있습니다. 예를 들어 "그게 다른 사람들이하는 일이기 때문에"(IMHO는 슬프지만 실제적인 이유) 또는 "성능"(IMHO이 이유는 일반적으로 과장되어 있음) , 그러나 그럼에도 불구하고 이유입니다). goto를 피하고 (일반적으로 등가 루프보다 제어 흐름 분석을 더 복잡하게 함) 전역 변수 (잠재적으로 많은 커플 링을 도입하여 나중에 코드 변경을 어렵게 만들고 일반적으로 동일)를 피하는 것과 같은 다른 경험 규칙에 대해서도 마찬가지입니다. 다른 방법을 덜 결합하여 목표를 달성 할 수 있습니다).
j_random_hacker

그리고 그 모든 이유에는 내 대답이었던 긴 경고 목록이 있습니다. 프로그래밍에 대한 광범위한 경험을 넘어서는 진정한 이유는 없습니다. 이유를 뛰어 넘는 몇 가지 경험 법칙이 있습니다. 동일한 경험 법칙으로 인해 "전문가"가 그 이유에 동의하지 않을 수 있습니다. 당신은 소금 한 알로 "성능"을 취합니다. 그게 내 목록의 상단이 될 것입니다. 흐름 제어 분석은 저에게 등록조차하지 않습니다. ( "no goto"처럼) 흐름 제어 문제가 과장된 것을 발견하기 때문입니다. 나는 또한 내 대답이 "trite"대답을 사용하는 방법을 설명하려고 시도한다는 것을 지적 할 것입니다.
jmucchiello

모든 경험 규칙에 긴주의 사항 목록이있는 한 귀하에게 동의합니다. 내가 동의하지 않는 부분은 규칙의 원래 이유와 구체적인주의 사항을 확인하는 것이 가치가 있다고 생각한다는 것입니다. (제 말은 우리가 이런 것들을 숙고 할 무한한 시간이 있고 당연히 기한이없는 이상적인 세상에서 말입니다.)) 저는 그것이 OP가 요구 한 것이라고 생각합니다.
j_random_hacker
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.