문제

계약에 따라 프로그래밍할 때 함수나 메서드는 해당 책임을 수행하기 전에 먼저 전제 조건이 충족되었는지 확인합니다.이러한 점검을 수행하는 가장 눈에 띄는 두 가지 방법은 다음과 같습니다. assert 그리고 exception.

  1. Assert는 디버그 모드에서만 실패합니다.모든 개별 계약 전제 조건을 (단위) 테스트하여 실제로 실패하는지 확인하는 것이 중요합니다.
  2. 디버그 및 릴리스 모드에서는 예외가 실패합니다.이는 테스트된 디버그 동작이 릴리스 동작과 동일하다는 이점이 있지만 런타임 성능 저하가 발생합니다.

어느 쪽이 바람직하다고 생각하시나요?

관련 질문 보기 여기

도움이 되었습니까?

해결책

릴리스 빌드에서 어설션을 비활성화하는 것은 "릴리스 빌드에서는 어떤 문제도 발생하지 않을 것입니다"라고 말하는 것과 같습니다. 이는 종종 그렇지 않습니다.따라서 릴리스 빌드에서는 Assert를 비활성화하면 안 됩니다.하지만 오류가 발생할 때마다 릴리스 빌드가 충돌하는 것을 원하지 않습니까?

그러니 예외를 활용하고 잘 활용하세요.훌륭하고 견고한 예외 계층 구조를 사용하고 디버거에서 예외를 던지는 것을 잡아서 잡을 수 있는지 확인하고 릴리스 모드에서는 직접적인 충돌이 아닌 오류를 보상할 수 있습니다.가는 것이 더 안전한 방법입니다.

다른 팁

경험상 법칙은 자신의 오류를 잡으려고 할 때 어설션을 사용해야 하고, 다른 사람의 오류를 잡으려고 할 때 예외를 사용해야 한다는 것입니다.즉, 공개 API 기능에 대한 전제 조건을 확인하고 시스템 외부의 데이터를 얻을 때마다 예외를 사용해야 합니다.시스템 내부의 기능이나 데이터에 대해 어설션을 사용해야 합니다.

내가 따르는 원칙은 이렇다.코딩을 통해 상황을 현실적으로 피할 수 있는 경우 어설션을 사용하세요.그렇지 않으면 예외를 사용하십시오.

주장은 계약이 준수되고 있는지 확인하기 위한 것입니다.계약은 공정해야 고객이 계약을 준수할 수 있는 위치에 있어야 합니다.예를 들어 유효한 URL인지 아닌지에 대한 규칙이 알려져 있고 일관되기 때문에 URL이 유효해야 한다고 계약서에 명시할 수 있습니다.

클라이언트와 서버 모두의 통제 범위를 벗어나는 상황은 예외입니다.예외는 무엇인가 잘못되었으며 이를 방지하기 위해 수행할 수 있는 조치가 없음을 의미합니다.예를 들어, 네트워크 연결은 애플리케이션 제어 범위를 벗어나므로 네트워크 오류를 방지하기 위해 수행할 수 있는 조치는 없습니다.

Assertion / Exception 구별이 실제로는 최선의 방법이 아니라는 점을 덧붙이고 싶습니다.당신이 정말로 생각하고 싶은 것은 계약과 그것이 어떻게 시행될 수 있는지입니다.위의 URL 예에서 가장 좋은 방법은 URL을 캡슐화하고 Null이거나 유효한 URL인 클래스를 갖는 것입니다.이는 문자열을 계약을 시행하는 URL로 변환하는 것이며 유효하지 않은 경우 예외가 발생합니다.URL 매개변수가 있는 메서드는 문자열 매개변수와 URL을 지정하는 어설션이 있는 메서드보다 훨씬 더 명확합니다.

Asserts는 개발자가 잘못한 일을 포착하기 위한 것입니다(자신뿐만 아니라 팀의 다른 개발자도 마찬가지입니다).사용자 실수로 인해 이러한 상황이 발생할 수 있다는 것이 합리적인 경우에는 예외가 되어야 합니다.

마찬가지로 결과에 대해서도 생각해 보십시오.어설션은 일반적으로 앱을 종료합니다.해당 조건이 복구될 수 있다는 현실적인 기대가 있는 경우 예외를 사용해야 합니다.

반면에 문제가 발생할 수 있는 경우 오직 프로그래머 오류로 인해 발생했다면 가능한 한 빨리 이에 대해 알고 싶기 때문에 Assert를 사용하세요.예외가 포착되어 처리될 수 있으며 이에 대해 결코 알 수 없습니다.그리고 그렇습니다. 릴리스 코드에서 어설션을 비활성화해야 합니다. 왜냐하면 가능성이 조금이라도 있는 경우 앱이 복구되기를 원하기 때문입니다.프로그램 상태가 심각하게 손상되더라도 사용자는 작업 내용을 저장할 수 있습니다.

"어설션이 디버그 모드에서만 실패합니다."라는 말은 정확히 사실이 아닙니다.

~ 안에 객체 지향 소프트웨어 구성, 2판 Bertrand Meyer의 저자는 릴리스 모드에서 전제 조건을 확인할 수 있는 문을 열어 둡니다.이 경우 어설션이 실패하면 어떻게 됩니까?어설션 위반 예외가 발생했습니다!이 경우 상황에서 복구할 수 없습니다.하지만 유용한 작업을 수행할 수 있으며 자동으로 오류 보고서를 생성하고 경우에 따라 응용 프로그램을 다시 시작하는 것입니다.

그 이유는 일반적으로 전제 조건이 불변 및 사후 조건보다 테스트 비용이 저렴하고 경우에 따라 릴리스 빌드의 정확성과 "안전성"이 속도보다 더 중요하기 때문입니다.즉.많은 응용 프로그램의 경우 속도는 문제가 되지 않지만 견고성 (동작이 올바르지 않을 때 안전한 방식으로 동작하는 프로그램의 능력, 즉계약을 파기한 경우) 입니다.

항상 전제 조건 확인을 활성화해야 합니까?때에 따라 다르지.그것은 당신에게 달려 있습니다.보편적인 대답은 없습니다.은행을 위한 소프트웨어를 만드는 경우 $1,000 대신 $1,000,000를 이체하는 것보다 경고 메시지로 실행을 중단하는 것이 더 나을 수 있습니다.하지만 게임을 프로그래밍한다면 어떨까요?어쩌면 얻을 수 있는 모든 속도가 필요할 수도 있고, 전제 조건이 포착되지 않은 버그로 인해(활성화되지 않았기 때문에) 누군가가 10점 대신 1000점을 얻는다면 운이 좋지 않을 것입니다.

두 경우 모두 테스트 중에 해당 버그를 이상적으로 포착해야 하며 어설션을 활성화한 상태에서 테스트의 중요한 부분을 수행해야 합니다.여기서 논의되는 내용은 불완전한 테스트로 인해 이전에 감지되지 않은 시나리오에서 프로덕션 코드의 전제 조건이 실패하는 드문 경우에 대한 최선의 정책이 무엇인지입니다.

요약, 당신은 어설 션을 가질 수 있으며 여전히 자동으로 예외를 얻을 수 있습니다, 활성화된 상태로 두면 최소한 에펠에서는 가능합니다.C++에서 동일한 작업을 수행하려면 직접 입력해야 한다고 생각합니다.

또한보십시오: 언제 프로덕션 코드에 어설션을 유지해야 합니까?

엄청난 일이 있었어요 comp.lang.c++.moderated의 릴리스 빌드에서 어설션 활성화/비활성화와 관련하여 몇 주가 지나면 이에 대한 의견이 얼마나 다양한지 확인할 수 있습니다.:)

반대 콥프로, 릴리스 빌드에서 어설션을 비활성화할 수 있는지 확실하지 않다면 어설션이 아니어야 한다고 생각합니다.어설션은 프로그램 불변성이 손상되는 것을 방지하기 위한 것입니다.이러한 경우 코드 클라이언트에 관한 한 다음 두 가지 가능한 결과 중 하나가 발생합니다.

  1. 일종의 OS 유형 오류로 인해 중단되어 중단 호출이 발생합니다.(어설션 없음)
  2. 중단을 직접 호출하여 죽습니다.(assert 포함)

사용자에게는 차이가 없지만 어설션은 코드가 실패하지 않는 대부분의 실행에 존재하는 코드에 불필요한 성능 비용을 추가할 수 있습니다.

질문에 대한 대답은 실제로 API 클라이언트가 누구인지에 따라 훨씬 더 달라집니다.API를 제공하는 라이브러리를 작성하는 경우 고객이 API를 잘못 사용했음을 알리기 위한 일종의 메커니즘이 필요합니다.두 가지 버전의 라이브러리를 제공하지 않는 한(하나는 어설션이 있고 하나는 없음) 어설션은 적절한 선택이 될 가능성이 거의 없습니다.

그러나 개인적으로 이 경우에도 예외를 적용할 수 있을지 확신할 수 없습니다.적절한 형태의 복구가 발생할 수 있는 경우에는 예외가 더 적합합니다.예를 들어, 메모리를 할당하려고 할 수 있습니다.'std::bad_alloc' 예외가 발생하면 메모리를 확보하고 다시 시도하는 것이 가능할 수 있습니다.

나는 이 문제의 상태에 대한 나의 견해를 여기에 설명했습니다. 객체의 내부 상태를 어떻게 검증합니까? .일반적으로 자신의 주장을 주장하고 다른 사람의 위반에 대해 던지십시오.릴리스 빌드에서 어설션을 비활성화하려면 다음을 수행할 수 있습니다.

  • 비용이 많이 드는 검사에 대한 어설션 비활성화(예: 범위가 주문되었는지 확인)
  • 사소한 검사를 활성화합니다(예: 널 포인터 또는 부울 값 검사).

물론 릴리스 빌드에서는 실패한 어설션과 포착되지 않은 예외를 디버그 빌드(std::abort만 호출할 수 있음)와는 다른 방식으로 처리해야 합니다.오류 로그를 어딘가(파일 등)에 기록하고 고객에게 내부 오류가 발생했음을 알립니다.고객이 귀하에게 로그 파일을 보낼 수 있습니다.

디자인 타임 오류와 런타임 오류의 차이점에 대해 질문하고 있습니다.

주장은 '안녕하세요 프로그래머, 이건 깨졌습니다'라는 알림으로, 버그가 발생했을 때 눈치 채지 못했을 버그를 상기시키기 위해 존재합니다.

예외는 '안녕하세요 사용자님, 문제가 발생했습니다' 알림입니다(분명히 사용자에게 알리지 않도록 이를 포착하도록 코딩할 수 있음). 그러나 이러한 알림은 Joe 사용자가 앱을 사용할 때 런타임에 발생하도록 설계되었습니다.

따라서 모든 버그를 해결할 수 있다고 생각되면 예외만 사용하세요.할 수 없다고 생각한다면.....예외를 사용하십시오.물론 디버그 어설션을 사용하여 예외 수를 줄일 수도 있습니다.

많은 전제조건이 사용자 제공 데이터라는 점을 잊지 마십시오. 따라서 사용자에게 데이터가 좋지 않음을 알리는 좋은 방법이 필요합니다.그렇게 하려면 호출 스택의 오류 데이터를 상호 작용하는 비트로 반환해야 하는 경우가 많습니다.그러면 Assert는 유용하지 않습니다. 앱이 n 계층인 경우에는 두 배로 유용합니다.

마지막으로, 나는 둘 다 사용하지 않을 것입니다. 오류 코드는 정기적으로 발생할 것으로 생각되는 오류에 비해 훨씬 우수합니다.:)

나는 두 번째를 선호합니다.테스트가 제대로 실행되었을 수도 있지만 머피 예상치 못한 일이 일어날 것이라고 말합니다.따라서 실제 잘못된 메서드 호출에서 예외가 발생하는 대신 10스택 프레임 더 깊이 있는 NullPointerException(또는 이에 상응하는)을 추적하게 됩니다.

이전 답변이 정확합니다.공개 API 함수에 예외를 사용하십시오.이 규칙을 변경하려는 유일한 경우는 검사에 계산 비용이 많이 드는 경우입니다.그렇다면 당신은 ~할 수 있다 주장에 넣으십시오.

해당 전제조건을 위반할 가능성이 있다고 생각되면 이를 예외로 유지하거나 전제조건을 리팩토링하세요.

둘 다 사용해야 합니다.Assert는 개발자의 편의를 위한 것입니다.예외는 런타임 중에 놓쳤거나 예상하지 못한 사항을 포착합니다.

나는 좋아하게 되었다 glib의 오류 보고 기능 평범한 오래된 주장 대신.이는 Assert 문처럼 동작하지만 프로그램을 중단하는 대신 값을 반환하고 프로그램이 계속되도록 합니다.놀랍게도 잘 작동하며, 보너스로 함수가 "예상된 결과"를 반환하지 않을 때 프로그램의 나머지 부분에 무슨 일이 일어나는지 확인할 수 있습니다.충돌이 발생하면 오류 검사가 다른 곳에서 느슨하다는 것을 알 수 있습니다.

지난 프로젝트에서는 이러한 스타일의 함수를 사용하여 전제 조건 확인을 구현했으며, 그 중 하나가 실패하면 로그 파일에 스택 추적을 인쇄하지만 계속 실행했습니다.내 디버그 빌드를 실행할 때 다른 사람들이 문제에 직면할 때 디버깅 시간을 엄청나게 절약했습니다.

#ifdef DEBUG
#define RETURN_IF_FAIL(expr)      do {                      \
 if (!(expr))                                           \
 {                                                      \
     fprintf(stderr,                                        \
        "file %s: line %d (%s): precondition `%s' failed.", \
        __FILE__,                                           \
        __LINE__,                                           \
        __PRETTY_FUNCTION__,                                \
        #expr);                                             \
     ::print_stack_trace(2);                                \
     return;                                                \
 };               } while(0)
#define RETURN_VAL_IF_FAIL(expr, val)  do {                         \
 if (!(expr))                                                   \
 {                                                              \
    fprintf(stderr,                                             \
        "file %s: line %d (%s): precondition `%s' failed.",     \
        __FILE__,                                               \
        __LINE__,                                               \
        __PRETTY_FUNCTION__,                                    \
        #expr);                                                 \
     ::print_stack_trace(2);                                    \
     return val;                                                \
 };               } while(0)
#else
#define RETURN_IF_FAIL(expr)
#define RETURN_VAL_IF_FAIL(expr, val)
#endif

인수에 대한 런타임 검사가 필요한 경우 다음을 수행합니다.

char *doSomething(char *ptr)
{
    RETURN_VAL_IF_FAIL(ptr != NULL, NULL);  // same as assert(ptr != NULL), but returns NULL if it fails.
                                            // Goes away when debug off.

    if( ptr != NULL )
    {
       ...
    }

    return ptr;
}

나는 여기에 다른 몇 가지 답변을 내 자신의 견해로 종합해 보았습니다.

프로덕션에서 이를 비활성화하려는 경우 어설션을 사용하여 오류를 그대로 두십시오.프로덕션에서는 비활성화하고 개발에서는 비활성화하는 유일한 실제 이유는 프로그램 속도를 높이기 위한 것입니다.대부분의 경우 이러한 속도 향상은 중요하지 않지만 때로는 코드가 시간이 중요하거나 테스트에 계산 비용이 많이 듭니다.코드가 업무상 중요한 경우 속도 저하에도 불구하고 예외가 가장 좋습니다.

실제로 복구 가능성이 있는 경우 어설션은 복구되도록 설계되지 않았으므로 예외를 사용하세요.예를 들어, 코드는 프로그래밍 오류로부터 복구하도록 설계되는 경우가 거의 없지만 네트워크 오류나 잠긴 파일과 같은 요인으로부터 복구하도록 설계됩니다.오류는 단순히 프로그래머의 통제 범위를 벗어났다는 이유만으로 예외로 처리되어서는 안 됩니다.오히려 이러한 오류는 코딩 실수에 비해 예측 가능성이 높기 때문에 복구하기가 더 쉽습니다.

어설션을 디버그하는 것이 더 쉽다는 점을 다시 주장합니다.적절하게 명명된 예외의 스택 추적은 어설션만큼 읽기 쉽습니다.좋은 코드는 특정 유형의 예외만 포착해야 하므로 예외가 포착되어도 눈에 띄지 않게 되어서는 안 됩니다.하지만 Java는 때때로 모든 예외를 잡아내도록 강요한다고 생각합니다.

또한보십시오 이 질문:

어떤 경우에는 릴리스용으로 빌드할 때 어설션이 비활성화됩니다.당신은 이것을 통제 할 수 없을 수도 있습니다 (그렇지 않으면 어설 팅으로 구축 할 수 있습니다). 따라서 이렇게하는 것이 좋습니다.

입력 값을 "수정"하는 문제는 발신자가 기대하는 것을 얻지 못하며 프로그램의 전적으로 다른 부분에서 문제를 일으키거나 심지어 충돌하여 악몽을 이끌어 낼 수 있다는 것입니다.

나는 보통 IF 진술에서 예외를 던지기 위해 장애가있는 경우 어서 트의 역할을 인수합니다.

assert(value>0);
if(value<=0) throw new ArgumentOutOfRangeException("value");
//do stuff

제가 생각하는 경험상, Assert 표현식을 사용하여 내부 오류와 외부 오류에 대한 예외를 찾는 것입니다.Greg의 다음 토론에서 많은 이점을 얻을 수 있습니다. 여기.

Assert 표현식은 프로그래밍 오류를 찾는 데 사용됩니다.프로그램 논리 자체의 오류 또는 해당 구현의 오류입니다.어설션 조건은 프로그램이 정의된 상태로 유지되는지 확인합니다."정의된 상태"는 기본적으로 프로그램의 가정과 일치하는 상태입니다.프로그램의 "정의된 상태"는 "이상적인 상태"나 "일반적인 상태" 또는 "유용한 상태"일 필요는 없지만 나중에 중요한 사항에 대해 자세히 설명합니다.

어설 션이 프로그램에 어떻게 적합한 지 이해하려면 포인터를 피할 수있는 C ++ 프로그램의 루틴을 고려하십시오.이제 정기적으로 포인터가 해석하기 전에 포인터가 무효인지를 테스트해야합니까, 아니면 포인터가 무효가 아니라고 주장하고 계속해서 피해를 입어야합니까?

나는 대부분의 개발자들이 두 가지를 모두하고, 어설 싱을 추가하고, 또한 주장 된 조건이 실패하면 충돌하지 않기 위해 널 값에 대한 포인터를 점검하고 싶다고 생각합니다.표면적으로 테스트와 점검을 모두 수행하는 것이 가장 현명한 결정처럼 보일 수 있습니다.

주장 된 조건과 달리 프로그램의 오류 처리 (예외)는 프로그램의 오류가 아니라 프로그램이 환경에서 얻는 것을 입력하는 것을 말합니다.이들은 비밀번호를 입력하지 않고 계정에 로그인하려는 사용자와 같이 누군가의 경우 "오류"입니다.오류로 인해 프로그램 작업이 성공적으로 완료되지 않더라도 프로그램 실패는 없습니다.프로그램은 외부 오류로 인해 비밀번호없이 사용자를 로그인하지 못합니다.상황이 다르고 사용자가 올바른 비밀번호로 입력하고 프로그램이 인식하지 못한 경우;결과는 여전히 동일하지만 실패는 이제 프로그램에 속할 것입니다.

오류 처리(예외)의 목적은 두 가지입니다.첫 번째는 프로그램 입력의 오류가 감지되었으며 그 의미를 사용자 (또는 다른 클라이언트)에게 통신하는 것입니다.두 번째 목표는 오류가 감지 된 후 잘 정의 된 상태로 응용 프로그램을 복원하는 것입니다.이 상황에서는 프로그램 자체가 잘못되지 않습니다.물론,이 프로그램은 비 이상 상태 일 수도 있고 심지어 유용하지 않은 상태 일 수도 있지만 프로그래밍 오류는 없습니다.반대로, 오류 복구 상태는 프로그램 디자인에 의해 예상되는 오류 복구 상태이므로 프로그램이 처리 할 수있는 것입니다.

추신:비슷한 질문을 확인하고 싶을 수도 있습니다. 예외 대 주장.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top