Programing

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

lottogame 2020. 10. 11. 09:01
반응형

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


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

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

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

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


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

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

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

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

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

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

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

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

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

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

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


"예외적 인 상황에서 예외 발생"이 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 분기를 대조하십시오 . 전자는 종종 사전 조건 위반에 사용되는 반면 후자는 사후 조건 위반에 더 적합합니다.



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

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


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

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

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

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

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

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


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

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


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

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

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

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

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

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

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;
    }
}

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

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


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

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

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


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

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


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

관련 부분 :

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


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

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

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


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

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

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

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

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

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


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

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

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

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

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


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

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

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

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


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

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;
}

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

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


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

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


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

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


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

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

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

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


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

  • 파일을 열려고하면 파일이 없으면 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이 많이 사용되는 경우 개발자는 특정 예외 대신 일반 예외를 포착하고 예외가 예상하는 오류로 인한 것이지만 다른 원인으로 인한 것일 수 있다고 가정합니다. 결국 이것은 예외가 예외가 아닌 애플리케이션 흐름의 일부가 되었기 때문에 실제 예외가 무시되는 결과를 낳습니다.

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


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

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


"드물게 사용한다"는 말이 올바른 문장이 아닌 것 같아요. 나는 "예외적 인 상황에서만 던지기"를 선호합니다.

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

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

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

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

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

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


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

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

I believe that a good programming language does not support exceptions as we know them in C++ and Java. You should opt for programming languages that can provide alternative flow for all sorts of return values from functions. The programmer should be responsible for anticipating all forms of outputs of a routine and handle them in a seperate code file if I could have my way.


I use exceptions if:

  • an error occured that cannot be recovered from locally AND
  • if the error is not recovered from the program should terminate.

If the error can be recovered from (the user entered "apple" instead of a number) then recover (ask for the input again, change to default value, etc.).

If the error cannot be recovered from locally but the application can continue (the user tried to open a file but the file does not exist) then an error code is appropriate.

If the error cannot be recovered from locally and the application cannot continue without recovering (you are out of memory/disk space/etc.), then an exception is the right way to go.


Who said they should be used conservatively ? Just never use exceptions for flow control and thats it. And who cares about the cost of exception when it already thrown ?


My two cents:

I like to use exceptions, because it allows me to program as if no errors will happen. So my code remains readable, not scattered with all kinds of error-handling. Of course, the error handling (exception handling) is moved to the end (the catch block) or is considered the responsability of the calling level.

A great example for me, is either file handling, or database handling. Presume everything is ok, and close your file at the end or if some exception occurs. Or rollback your transaction when an exception occurred.

The problem with exceptions, is that it quickly gets very verbose. While it was meant to allow your code to remain very readable, and just focus on the normal flow of things, but if used consistently almost every function call needs to be wrapped in a try/catch block, and it starts to defeat the purpose.

For a ParseInt as mentioned before, i like the idea of exceptions. Just return the value. If the parameter was not parseable, throw an exception. It makes your code cleaner on the one hand. At the calling level, you need to do something like

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

The code is clean. When i get ParseInt like this scattered all over, i make wrapper functions that handle the exceptions and return me default values. E.g.

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

   return result;
}

So to conclude: what i missed througout the discussion was the fact that exceptions allow me to make my code easier/more readable because i can ignore the error conditions more. Problems:

  • the exceptions still need to be handled somewhere. Extra problem: c++ does not have the syntax that allows it to specify which exceptions a function might throw (like java does). So the calling level is not aware which exceptions might need to be handled.
  • sometimes code can get very verbose, if every function needs to be wrapped in a try/catch block. But sometimes this still makes sense.

So that makes it hard to find a good balance sometimes.


I'm sorry but the answer is "they are called exceptions for a reason." That explanation is a "rule of thumb". You can't give a complete set of circumstances under which exceptions should or should not be used because what a fatal exception (English definition) is for one problem domain is normal operating procedure for a different problem domain. Rules of thumb are not designed to be followed blindly. Instead they are designed to guide your investigation of a solution. "They are called exceptions for a reason" tells you that you should determine ahead of time what is a normal error the caller can handle and what is an unusual circumstance the caller cannot handle without special coding (catch blocks).

Just about every rule of programming is really a guideline saying "Don't do this unless you have a really good reason": "Never use goto", "Avoid global variables", "Regular expressions pre-increment your number of problems by one", etc. Exceptions are no exception....

참고URL : https://stackoverflow.com/questions/1744070/why-should-exceptions-be-used-conservatively

반응형