예외 계약을 작성하고 시행하려면 어떻게해야합니까?


33

isSuccessful에서 오류 코드와 함께 부울 이나 열거 형 을 반환하는 대신 C ++에서 예외를 사용할 수 있도록 설득하려고 합니다. 그러나 나는 그의 비판에 반대 할 수 없다.

이 라이브러리를 고려하십시오.

class OpenFileException() : public std::runtime_error {
}

void B();
void C();

/** Does blah and blah. */
void B() {
    // The developer of B() either forgot to handle C()'s exception or
    // chooses not to handle it and let it go up the stack.
    C();
};

/** Does blah blah.
 *
 * @raise OpenFileException When we failed to open the file. */
void C() {
    throw new OpenFileException();
};
  1. 개발자가 B()함수를 호출한다고 가정하십시오 . 그는 문서를 확인하고 예외를 반환하지 않는 것을 확인하므로 아무것도 잡으려고 시도하지 않습니다. 이 코드는 프로덕션 환경에서 프로그램을 중단시킬 수 있습니다.

  2. 개발자가 C()함수를 호출한다고 가정하십시오 . 그는 문서를 확인하지 않으므로 예외를 포착하지 않습니다. 호출이 안전하지 않아 프로덕션 환경에서 프로그램이 중단 될 수 있습니다.

그러나 우리가 이런 식으로 오류를 확인하면 :

void old_C(myenum &return_code);

해당 함수를 사용하는 개발자는 해당 인수를 제공하지 않으면 컴파일러에서 경고를 받고 "Aha, 이것이 확인해야하는 오류 코드를 반환합니다."라고 말합니다.

어떤 종류의 계약이 발생하도록 예외를 안전하게 사용하려면 어떻게해야합니까?


4
@Sjoerd 주요 장점은 개발자가 컴파일러에 의해 리턴 코드를 저장하는 함수에 변수를 제공해야한다는 것입니다. 그는 오류가있을 수 있음을 알고이를 처리해야합니다. 컴파일 시간 검사가 전혀없는 예외보다 무한히 좋습니다.
DBedrenko

4
"그는 그것을 처리해야한다"-이것이 일어나는지 점검하는 것은 없다.
Sjoerd

4
@Sjoerd 필요하지 않습니다. 오류가 발생할 수 있으며이를 확인해야 함을 개발자에게 알리는 것으로 충분합니다. 검토자가 오류를 확인했는지 여부에 상관없이 리뷰어도 알 수 있습니다.
DBedrenko

5
Either/ Resultmonad를 사용하여 형식이 안전한
컴포저 블

5
@gardenhead 너무 많은 개발자가 제대로 사용하지 않기 때문에 추악한 코드로 끝나고 기능 자체를 비난하기 시작합니다. Java에서 확인 된 예외 기능은 아름답지만 일부 사람들은 그것을 얻지 못하기 때문에 나쁜 랩을 얻습니다.
AxiomaticNexus

답변:


47

이것은 예외에 대한 정당한 비판입니다. 코드 반환과 같은 간단한 오류 처리보다 눈에 잘 띄지 않는 경우가 많습니다. "계약"을 시행하는 쉬운 방법은 없습니다. 요점의 일부는 예외를 더 높은 수준에서 잡을 수있게하는 것입니다 (모든 수준에서 모든 예외를 잡아야한다면 오류 코드를 반환하는 것과 어떻게 다른가?). 그리고 이것은 코드를 적절하게 처리하지 않는 다른 코드로 코드를 호출 할 수 있음을 의미합니다.

예외는 단점이 있습니다. 비용 편익을 기반으로 사례를 만들어야합니다.
: 나는 도움이 두 기사를 발견 예외의 필요성예외를 제외하고 모두의 잘못입니다. 또한 이 블로그 게시물에서는 C ++에 중점을두고 예외에 대한 많은 전문가의 의견을 제공합니다 . 전문가 의견은 예외에 찬성하는 것처럼 보이지만 명확한 합의와는 거리가 멀다.

팀 리더를 설득시키는 것은 올바른 선택이 아닐 수도 있습니다. 특히 레거시 코드에서는 그렇지 않습니다. 위의 두 번째 링크에서 언급했듯이 :

예외 안전하지 않은 코드를 통해 예외를 전파 할 수 없습니다. 따라서 예외 사용은 프로젝트의 모든 코드가 예외 안전해야 함을 의미합니다.

주로 예외가 아닌 프로젝트에 예외를 사용하는 약간의 코드를 추가해도 개선 되지는 않을 것입니다. 잘 작성된 코드에서 예외를 사용하지 않는 것은 치명적인 문제와는 거리가 멀다. 응용 프로그램 및 요청한 전문가에 따라 전혀 문제가되지 않을 수 있습니다. 당신은 당신의 전투를 선택해야합니다.

이것은 아마도 새로운 프로젝트가 시작될 때까지는 노력하지 않을 논쟁 일 것입니다. 그리고 새로운 프로젝트가 있더라도 레거시 코드에서 사용되거나 사용됩니까?


1
조언과 링크에 감사드립니다. 이것은 기존 프로젝트가 아닌 새로운 프로젝트입니다. 예외적으로 사용이 안전하지 않은 큰 단점이있을 때 예외가 왜 널리 퍼져 있는지 궁금합니다. 예외 n 레벨을 더 높게 잡은 다음 나중에 코드를 수정하고 이동하면 예외가 여전히 잡히고 처리되는지 확인하는 정적 검사가 없습니다 (심지어 검토 자에게 n 레벨의 모든 기능을 검사하도록 요청하는 것이 너무 많습니다) ).
DBedrenko

3
@Dee는 예외에 찬성하여 일부 주장에 대해서는 위의 링크를 참조하십시오 (전문가의 의견으로 추가 링크를 추가했습니다).

6
이 답변은 제자리에 있습니다!
Doc Brown

1
기존 코드베이스에 예외를 추가하려고 시도하는 것은 정말 나쁜 생각이라는 경험을 통해 알 수 있습니다. 나는 그런 코드베이스로 일해 왔으며 실제로 당신의 얼굴에 무엇이 터질지 알지 못했기 때문에 다루기가 어려웠습니다. 예외는 미리 계획 한 프로젝트에서만 사용해야합니다.
Michael Kohne

26

이 주제에 대해 말 그대로 전체 책이 있으므로 모든 대답은 요약이 될 것입니다. 귀하의 질문에 따라 가치가 있다고 생각되는 몇 가지 중요한 사항은 다음과 같습니다. 전체 목록이 아닙니다.


모든 곳에서 예외가 발생하지 않도록 의도되었습니다.

응용 프로그램 유형 (웹 서버, 로컬 서비스, 명령 줄 유틸리티 등)에 따라 기본 루프에 일반 예외 처리기가있는 한 일반적으로 필요한 모든 예외 처리기가 있습니다.

내 코드에는 주 루프 외부에 캐치 문이 거의 없습니다. 그리고 그것은 현대 C ++에서 일반적인 접근 방식 인 것 같습니다.


예외 및 리턴 코드는 상호 배타적이지 않습니다.

이것을 전혀 또는 전혀 접근하지 않아야합니다. 예외적 인 상황에서는 예외를 사용해야합니다. "구성 파일을 찾을 수 없음", "디스크가 꽉 참"또는 로컬로 처리 할 수없는 기타 항목.

사용자가 제공 한 파일 이름이 유효한지 확인하는 것과 같은 일반적인 실패는 예외의 사용 사례가 아닙니다. 이 경우 대신 반환 값을 사용하십시오.

위의 예에서 볼 수 있듯이 "파일을 찾을 수 없음"은 사용 사례에 따라 "설치의 일부"대 "사용자가 오타를 만들 수 있습니다"라는 예외 또는 반환 코드 일 수 있습니다.

따라서 절대적인 규칙은 없습니다. 대략적인 지침은 다음과 같습니다. 로컬에서 처리 할 수 ​​있으면 반환 값으로 설정하십시오. 로컬로 처리 할 수 ​​없으면 예외를 처리하십시오.


예외 정적 검사는 유용하지 않습니다.

예외는 어쨌든 로컬에서 처리되지 않으므로 일반적으로 어떤 예외를 처리 할 수 ​​있는지는 중요하지 않습니다. 유일하게 유용한 정보는 예외가 발생할 수 있는지 여부입니다.

Java에는 정적 검사가 있지만 일반적으로 실패한 실험으로 간주되며 대부분의 언어 (특히 C #)에는 이러한 유형의 정적 검사가 없습니다. C #에없는 이유에 대해 잘 읽어보십시오 .

이러한 이유로 C ++은를 throw(exceptionA, exceptionB)선호 하지 않습니다 noexcept(true). 디폴트는 함수가 던질 수 있기 때문에, 문서화가 명시 적으로 약속하지 않는 한 프로그래머는이를 예상해야합니다.


예외 안전 코드 작성은 예외 핸들러 작성과 관련이 없습니다.

차라리 예외 안전 코드 작성은 예외 처리기 작성을 피하는 방법에 관한 것입니다.

모범 사례의 대부분은 예외 처리기의 수를 줄이려고합니다. 코드를 한 번 작성하고 자동으로 코드를 호출하면 (예 : RAII를 통해) 동일한 코드를 복사하여 붙여 넣는 것보다 버그가 줄어 듭니다.


3
" 일반적인 예외 처리기가있는 한 ... 일반적으로 이미 괜찮습니다. "이것은 대부분의 소스와 모순되므로 예외 안전 코드를 작성하는 것은 매우 어려운 일입니다.

12
@ dan1111 "예외 안전 코드 작성"은 예외 처리기 작성과 관련이 없습니다. 예외 안전 코드 작성은 RAII를 사용하여 리소스를 캡슐화하고 "모든 로컬 작업을 먼저 수행 한 다음 스왑 / 이동 사용"및 기타 모범 사례를 사용하는 것입니다. 당신이 그들에게 익숙하지 않을 때 도전합니다. 그러나 "예외 처리기 작성"은 이러한 모범 사례의 일부가 아닙니다.
Sjoerd

4
@AxiomaticNexus 어떤 수준에서, 실패한 기능 사용을 "나쁜 개발자"라고 설명 할 수 있습니다. 가장 좋은 아이디어는 올바른 작업을 간단하게하기 때문에 나쁜 사용줄이는 아이디어 입니다. 정적으로 확인 된 예외는 해당 범주에 속하지 않습니다. 그들은 종종 코드가 작성 될 때 코드에 신경 쓰지 않아도되는 곳에서 가치에 비례하지 않는 작업을 만듭니다. 사용자는 컴파일러가 방해하지 않도록 잘못된 방법으로 침묵 시키려고합니다. 제대로하더라도 많은 혼란을 초래합니다.
jpmc26

4
@AxiomaticNexus 균형이 맞지 않는 이유는 이것이 전체 코드베이스의 거의 모든 함수에 쉽게 전파 될 수 있기 때문입니다 . 내 클래스의 절반에있는 모든 함수가 데이터베이스에 액세스 할 때 모든 함수가 명시 적으로 선언하지 않아도 SQLException이 발생할 수 있습니다. 그것들을 호출하는 모든 컨트롤러 메소드도 마찬가지입니다. 그 정도의 소음은 작업량에 관계없이 실제로 음의 값입니다. Sjoerd는 머리에 못을 박았 습니다 . 어쨌든 코드에 대해 아무것도 할 수 없기 때문에 대부분의 코드는 예외에 대해 걱정할 필요가 없습니다 .
jpmc26

3
@AxiomaticNexus 예, 최고의 개발자가 그렇게 항상 완벽하게 모든 단일 가능한 사용자 입력을 처리 할 자신의 코드를 완벽하게하고, 당신이 있기 때문에 , 지금까지, 지금까지 결코 자극에 그 예외를 볼 수 없습니다. 그리고 그렇게하면 인생에서 실패한 것입니다. 진심으로,이 쓰레기를주지 마십시오. 그 누구도 완벽하지 않고 최고도 아닙니다. 그리고 "정상적으로"가 "중단 오류 페이지 / 메시지 중지 및 반품"인 경우에도 오류가 발생하더라도 앱이 제대로 작동하지 않아야합니다. 그리고 무엇을 추측하십시오 : 99 %의 IOExceptions 와 동일한 작업 입니다. 확인 된 분할은 임의적이며 쓸모가 없습니다.
jpmc26

8

C ++ 프로그래머는 예외 사양을 찾지 않습니다. 그들은 예외 보증을 찾습니다.

코드 조각에서 예외가 발생했다고 가정 해보십시오. 프로그래머가 여전히 유효한 가정은 무엇입니까? 코드가 작성되는 방식에서 예외 발생 후 코드는 무엇을 보장합니까?

아니면 특정 코드 조각이 절대로 던져지지 않도록 보장 할 수 있습니까 (즉, 종료되는 OS 프로세스가 부족하지 않습니까)?

"롤백"이라는 단어는 예외에 대한 토론에서 자주 발생합니다. 유효한 상태 (명시 적으로 문서화 됨)로 롤백 할 수있는 것은 예외 보증의 예입니다. 예외 보증이 없으면 프로그램은 그 이후에 실행되는 코드가 의도 한대로 작동한다고 보장하지 않기 때문에 즉석에서 종료되어야합니다. 즉, 메모리가 손상된 경우 추가 작업은 기술적으로 정의되지 않은 동작입니다.

다양한 C ++ 프로그래밍 기술은 예외 보장을 촉진합니다. RAII (범위 기반 리소스 관리)는 정리 코드를 실행하고 일반적인 경우와 예외적 인 경우 모두 리소스가 해제되도록하는 메커니즘을 제공합니다. 객체를 수정하기 전에 데이터를 복사하면 작업이 실패한 경우 해당 객체의 상태를 복원 할 수 있습니다. 등등.

에 대한 답변 이 StackOverflow의 질문은 C ++ 프로그래머가 자신의 코드에 일어날과 실패에도 불구하고 프로그램 상태의 유효성을 보호하려고 할 수있는 가능한 고장 모드의 모든 이해로 이동 많은 노력을 엿볼 수 있습니다. C ++ 코드의 라인 별 분석은 습관이됩니다.

C ++로 개발할 때 (제작 용도로) 세부 사항을 감당할 여유가 없습니다. 또한 이진 블로 브 (비 오픈 소스)는 C ++ 프로그래머의 골칫거리입니다. 이진 블로 브를 호출해야하는데 블로 브가 실패하면 리버스 엔지니어링이 C ++ 프로그래머가 다음에하는 일입니다.

참조 : http://en.cppreference.com/w/cpp/language/exceptions#Exception_safety- 예외 안전 아래를 참조하십시오.

C ++에서 예외 스펙 구현에 실패했습니다. 다른 언어로 된 이후의 분석에 따르면 예외 사양은 단순히 실용적이지 않습니다.

실패한 이유 : 엄격하게 시행하려면 유형 시스템의 일부 여야합니다. 그러나 그렇지 않습니다. 컴파일러는 예외 사양을 확인하지 않습니다.

C ++이 그것을 선택한 이유와 다른 언어 (Java)의 경험으로 예외 사양이 불분명하다는 이유는 무엇입니까? 함수의 구현을 수정하면 (예를 들어, 새로운 종류의 예외), 엄격한 예외 사양 시행은 해당 사양도 업데이트해야 함을 의미합니다. 이것은 전파됩니다-간단한 변경 사항에 대해 수십 또는 수백 개의 함수에 대한 예외 사양을 업데이트해야 할 수도 있습니다. 추상 기본 클래스 (C ++ 인터페이스에 해당)는 상황이 악화됩니다. 인터페이스에 예외 사양이 적용되는 경우 인터페이스 구현에서는 다른 유형의 예외를 발생시키는 함수를 호출 할 수 없습니다.

참조 : http://www.gotw.ca/publications/mill22.htm

C ++ 17부터는 [[nodiscard]]속성이 함수 반환 값에 사용될 수 있습니다 ( https://stackoverflow.com/questions/39327028/can-ac-function-be-declared-such-that-the-return-value 참조) 무시할 수 없습니다 ).

따라서 코드를 변경하고 새로운 종류의 실패 조건 (예 : 새로운 유형의 예외)이 발생하면 주요 변경입니까? 호출자가 코드를 업데이트하도록 강요했거나 최소한 변경에 대해 경고 했어야합니까?

C ++ 프로그래머가 예외 사양 대신 예외 보장을 찾는 인수를 받아들이면 새로운 종류의 실패 조건이 예외를 위반하지 않으면 이전에 약속 한 코드를 보장한다고해서 큰 변화가 아닙니다.


"함수의 구현을 수정하면 (예를 들어, 새로운 종류의 예외를 발생시킬 수있는 다른 함수를 호출해야 함) 엄격한 예외 사양 적용은 해당 사양도 업데이트해야 함을 의미합니다."-양호 해야합니다. 대안은 무엇입니까? 새로 도입 된 예외를 무시 하시겠습니까?
AxiomaticNexus

@AxiomaticNexus 또한 예외 보증으로 답변됩니다. 사실 나는 사양이 완전 할 수는 없다고 주장한다. 보증은 우리가 찾는 것입니다. 어떤 보증이 여전히 유효한지 추측하기 위해 어떤 보증이 손상되었는지 알아 내기 위해 예외 유형을 비교하는 경우가 종종 있습니다. 예외 사양에는 확인해야 할 일부 유형이 나열되어 있지만 실제 시스템에서는 목록을 완료 할 수 없습니다. 대부분의 경우는 예외 유형은 하드 체크하게 다른 예외, 그것을 포장, 예외 유형을 "먹는"의 바로 가기를 가지고 사람들을 강제로
rwong

@ AxiomaticNexus 나는 예외 사용을 주장하거나 반대하지 않습니다. 일반적인 예외 사용은 기본 예외 클래스와 일부 파생 예외 클래스를 갖도록 권장합니다. 한편, C ++ STL 또는 다른 라이브러리에서 발생 된 예외 유형과 같은 다른 예외 유형도 가능하다는 점을 기억해야합니다. 이 경우 예외 사양을 적용 할 수 있다고 주장 할 수 있습니다. 표준 예외 ( std::exception)와 자체 기본 예외가 발생하도록 지정하십시오. 그러나 전체 프로젝트에 적용하면 모든 단일 기능에 동일한 사양, 즉 잡음이 있음을 의미합니다.
rwong

예외에 관해서는 C ++과 Java / C #이 다른 언어라고 지적합니다. 첫째, C ++은 임의의 코드 실행 또는 데이터 덮어 쓰기를 허용한다는 의미에서 형식에 안전하지 않으며, 둘째로 C ++은 훌륭한 스택 추적을 인쇄하지 않습니다. 이러한 차이로 인해 C ++ 프로그래머는 오류 및 예외 처리 측면에서 다른 선택을해야했습니다. 또한 특정 프로젝트는 C ++이 자신에게 적합한 언어가 아니라고 합법적으로 주장 할 수 있음을 의미합니다. (예를 들어, 개인적으로 TIFF 이미지 디코딩은 C 또는 C ++로 구현 될 수 없다고 생각합니다.)
rwong

1
@ rwong : C ++ 모델을 따르는 언어에서 예외 처리의 큰 문제 는 예외 객체 catch유형 을 기반으로 한다는 것 입니다 . 많은 경우 중요한 예외의 원인이 아니라 오히려 암시하는 것입니다. 시스템 상태 불행하게도 예외 유형은 일반적으로 객체가 부분적으로 업데이트 된 (따라서 유효하지 않은) 상태를 유지하는 방식으로 코드가 코드를 중단 적으로 종료했는지 여부에 대해서는 아무 말도하지 않습니다.
supercat

4

C () 함수를 호출하는 개발자를 고려하십시오. 그는 문서를 확인하지 않으므로 예외를 포착하지 않습니다. 전화가 안전하지 않습니다

완전히 안전합니다. 그는 모든 곳에서 예외를 잡을 필요가 없으며 실제로 유용한 일을 할 수있는 곳에 시도 / 캐치를 던질 수 있습니다. 그가 스레드에서 누출을 허용하는 경우에만 안전하지는 않지만 일반적으로 스레드가 발생하는 것을 방지하는 것은 어렵지 않습니다.


그가 예외를 기대하지 않기 때문에 안전하지 않으므로 C()결과적으로 호출을 건너 뛴 후 코드를 보호하지 않습니다. 일부 불일치가 계속 유지되도록 해당 코드가 필요한 경우,이 보증은에서 나오는 첫 번째 예외에서 of이 C()됩니다. 완벽하게 예외 안전 소프트웨어와 같은 것은 없으며, 예외가 예상되지 않은 부분은 없습니다.
cmaster

1
@cmaster 그분이 예외가 나오기를 기대하지 않는C() 것은 큰 실수입니다. C ++의 기본값은 모든 함수가 던질 수 있다는 것 noexcept(true)입니다. 컴파일러에 달리 알리려면 명시 적이 필요합니다 . 이 약속이 없으면 프로그래머는 함수가 발생할 수 있다고 가정해야합니다.
Sjoerd

1
@cmaster 그것은 자신의 바보 같은 결함이며 C ()의 저자와는 아무런 관련이 없습니다. 예외 안전은 문제입니다. 그는 그것에 대해 배우고 따라야합니다. 그가 모르는 함수를 호출하면 예외가 아니며, 던질 때 발생하는 일을 잘 처리해야합니다. 예외가 필요하지 않은 경우 구현을 찾아야합니다.
DeadMG

@Sjoerd 및 DeadMG : 표준에서는 모든 함수에서 예외를 발생시킬 수 있지만 프로젝트에서 코드에서 예외를 허용해야한다는 의미는 아닙니다. OP의 팀 리더가하는 것처럼 코드에서 예외를 금지하면 예외 안전 프로그래밍을 수행 할 필요가 없습니다. 또한 예외가없는 코드 기반으로 예외를 허용하도록 팀 리더를 설득하려고하면 예외가 발생할 경우 많은 C()기능이 작동합니다. 이것은 실제로 매우 현명한 일이며 많은 문제를 일으킬 운명입니다.
cmaster

@cmaster 이것은 새로운 프로젝트에 관한 것입니다. 허용 된 답변에 대한 첫 번째 코멘트를보십시오. 따라서 기존 기능이 없으므로 기존 기능이 작동하지 않습니다.
Sjoerd

3

중요한 시스템을 구축하는 경우 팀장의 조언을 따르는 것을 고려하고 예외를 사용하지 마십시오. 이것은 록히드 마틴의 공동 타격 전투기 항공 차량 C ++ 코딩 표준의 AV 규칙 208입니다 . 반면에 MISRA C ++ 지침에는 MISRA 호환 소프트웨어 시스템을 구축하는 경우 예외를 사용할 수있는시기와 사용할 수없는시기에 관한 매우 구체적인 규칙이 있습니다.

중요한 시스템을 구축하는 경우 정적 분석 도구를 실행할 가능성이 있습니다. 메소드의 리턴 값을 확인하지 않으면 많은 정적 분석 도구가 경고를 표시하므로 누락 된 오류 처리의 경우를 쉽게 알 수 있습니다. 내가 아는 한, 적절한 예외 처리를 감지하는 비슷한 도구 지원은 강력하지 않습니다.

궁극적으로, 정적 분석과 함께 계약 및 방어 프로그래밍에 의한 설계 는 예외보다 중요한 소프트웨어 시스템에 더 안전 하다고 주장합니다 .

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