Debug.Assert 대 예외 발생


92

어설 션을 사용하는 방법과시기에 대한 많은 기사 (및 StackOverflow에 게시 된 몇 가지 다른 유사한 질문)를 읽었으며 이를 잘 이해했습니다. 그러나 여전히 나는 Debug.Assert평범한 예외를 던지는 대신 어떤 종류의 동기가 나를 사용 해야하는지 이해하지 못합니다 . 내 말은 .NET에서 실패한 어설 션에 대한 기본 응답은 "세계를 중지"하고 사용자에게 메시지 상자를 표시하는 것입니다. 이런 종류의 동작은 수정 될 수 있지만 그렇게하는 것이 매우 성 가시고 중복 적이라는 것을 알지만 대신 적절한 예외를 던질 수 있습니다. 이렇게하면 예외가 발생하기 직전에 응용 프로그램 로그에 오류를 쉽게 기록 할 수 있으며 응용 프로그램이 반드시 정지되는 것은 아닙니다.

그렇다면 왜 내가 Debug.Assert평범한 예외 대신 사용해야 할까요? 어설 션을 안되는 곳에 배치하는 것은 모든 종류의 "원치 않는 동작"을 유발할 수 있기 때문에 제 관점에서 예외를 던지는 대신 어설 션을 사용하여 아무것도 얻지 못합니다. 동의하십니까, 아니면 여기에 뭔가 빠졌나요?

참고 : "이론상"(디버그 대 릴리스, 사용 패턴 등)의 차이점이 무엇인지 완전히 이해하지만, 내가보기에 어설 션을 수행하는 대신 예외를 던지는 것이 낫습니다. 프로덕션 릴리스에서 버그가 발견되면 여전히 "어설 션"이 실패하기를 원하므로 (결국 "오버 헤드"가 엄청나게 작습니다) 대신 예외를 던지는 것이 좋습니다.


편집 : 어설 션이 실패하면 응용 프로그램이 일종의 손상되고 예상치 못한 상태에 들어 갔음을 의미합니다. 그렇다면 왜 계속 실행하고 싶습니까? 응용 프로그램이 디버그 또는 릴리스 버전에서 실행되는지는 중요하지 않습니다. 둘 다 똑같아요


1
당신이 말하는 것들 들어 주장 "실패", "버그가 생산 릴리스에서 발견되는 경우, 나는 여전히 그런 것"에 대한 예외는 당신이 무엇을 사용해야있다
톰 Neyland

1
성능이 유일한 이유입니다. 모든 것을 항상 Null 검사하면 속도가 느려질 수 있지만 완전히 눈에 띄지 않을 수 있습니다. 이것은 주로 절대 발생해서는 안되는 경우를위한 것입니다. 예를 들어, 이전 함수에서 이미 null을 확인했음을 알고 있으며 다시 확인하는주기를 낭비하는 포인트가 없습니다. debug.assert는 효과적으로 알려줄 마지막 기회 단위 테스트처럼 작동합니다.

답변:


175

당신의 추론이 타당 하다는 데 동의하지만 , 주장이 예기치 않게 위반되는 경우 던짐으로써 실행을 중단하는 것이 합리적입니다. 개인적으로 주장 대신 예외를 사용하지 않을 것입니다. 그 이유는 다음과 같습니다.

다른 사람들이 말했듯이 주장은 불가능하다고 주장되는 상황이 발생하면 개발자에게이를 알리는 방식으로 불가능한 상황을 문서화 합니다 . 대조적으로, 예외는 예외적이거나 가능성이 없거나 오류가있는 상황에 대한 제어 흐름 메커니즘을 제공하지만 불가능한 상황은 아닙니다. 저에게있어 주요 차이점은 다음과 같습니다.

  • 주어진 throw 문을 실행하는 테스트 케이스를 생성하는 것이 항상 가능해야합니다. 이러한 테스트 케이스를 생성 할 수없는 경우 프로그램에 실행되지 않는 코드 경로가있는 것이므로 데드 코드로 제거해야합니다.

  • 어설 션을 발생시키는 테스트 케이스를 생성하는 것은 절대 불가능합니다. 어설 션이 발생하면 코드가 잘못되었거나 어설 션이 잘못되었습니다. 어느 쪽이든 코드에서 무언가를 변경해야합니다.

그것이 내가 주장을 예외로 바꾸지 않는 이유입니다. 어설 션이 실제로 실행될 수없는 경우 예외대체하면 프로그램에 테스트 할 수없는 코드 경로가 있음을 의미합니다 . 테스트 할 수없는 코드 경로를 싫어합니다.


16
어설 션의 문제는 프로덕션 빌드에 존재하지 않는다는 것입니다. 가정 된 조건이 실패한다는 것은 프로그램이 정의되지 않은 동작 영역에 진입했음을 의미합니다.이 경우 책임있는 프로그램은 가능한 한 빨리 실행을 중지해야합니다 (스택을 해제하는 것도 원하는 정도에 따라 다소 위험합니다). 예, 주장은 일반적으로 실행이 불가능 해야 하지만 상황이 야생에서 진행될 때 무엇이 ​​가능한지 알 수 없습니다. 불가능하다고 생각한 것이 프로덕션에서 발생할 수 있으며 책임있는 프로그램은 위반 된 가정을 감지하고 즉시 조치를 취해야합니다.
kizzx2 2010-12-05

2
@ kizzx2 : 좋습니다. 생산 코드 줄당 불가능한 예외를 몇 개나 작성합니까?
Eric Lippert

4
@ kixxx2 : 이것은 C #이므로 Trace.Assert를 사용하여 프로덕션 코드에서 어설 션을 유지할 수 있습니다 . app.config 파일을 사용하여 최종 사용자에게 무례하지 않고 프로덕션 어설 션을 텍스트 파일로 리디렉션 할 수도 있습니다.
HTTP 410

3
@AnorZaken : 귀하의 요점은 예외의 설계 결함을 보여줍니다. 내가 다른 곳에서 언급했듯이 예외는 (1) 치명적인 재난, (2) 절대 일어나서는 안되는 뼈 저리 한 실수, (3) 예외가 예외가 아닌 상태를 알리기 위해 사용되는 설계 실패 또는 (4) 예상치 못한 외생 적 조건입니다. . 이 네 가지가 모두 예외로 표현되는 완전히 다른 이유는 무엇입니까? 내 druthers이 있다면, boneheaded 예외는 잡을 수 없을 것이다 "는 null 참조를 취소했다" 전혀 . 그것은 결코 옳지 않으며 더 많은 해를 끼치기 전에 프로그램을 종료 해야 합니다 . 그들은 주장과 더 비슷해야합니다.
Eric Lippert

2
@Backwards_Dave : 거짓 주장은 나쁘지, 사실은 아닙니다. 어설 션을 사용하면 프로덕션에서 실행하고 싶지 않은 값 비싼 확인 검사를 실행할 수 있습니다. 생산 과정에서 어설 션이 위반되면 최종 사용자는 이에 대해 어떻게해야합니까?
Eric Lippert

17

어설 션은 프로그래머의 세계 이해를 확인하는 데 사용됩니다. 어설 션은 프로그래머가 잘못한 경우에만 실패해야합니다. 예를 들어 어설 션을 사용하여 사용자 입력을 확인하지 마십시오.

"발생할 수없는"조건에 대한 테스트를 어설 션합니다. 예외는 "발생하지 않아야하지만 발생하는"조건에 대한 것입니다.

어설 션은 빌드시 (또는 런타임에) 동작을 변경할 수 있기 때문에 유용합니다. 예를 들어, 종종 릴리스 빌드에서 어설 션은 불필요한 오버 헤드를 유발하기 때문에 확인조차되지 않습니다. 이것은 또한주의해야 할 사항입니다. 테스트가 실행되지 않을 수도 있습니다.

어설 션 대신 예외를 사용하면 값이 손실됩니다.

  1. 예외를 테스트하고 던지는 것은 적어도 두 줄이고 어설 션은 하나뿐이기 때문에 코드는 더 장황합니다.

  2. 테스트 및 throw 코드는 항상 실행되지만 어설 션은 컴파일되지 않습니다.

  3. 어설 션은 확인하고 던지는 제품 코드와 다른 의미를 갖기 때문에 다른 개발자와의 의사 소통을 잃게됩니다. 실제로 프로그래밍 어설 션을 테스트하는 경우 어설 션을 사용하십시오.

추가 정보 : http://nedbatchelder.com/text/assert.html


그것이 "일어날 수 없다"면, 왜 주장을 작성하십시오. 중복되지 않습니까? 실제로 발생할 수 있지만해서는 안되는 경우 예외를위한 "일어나지 않아야하지만 실행"과 동일하지 않습니까?
David Klempfner

2
"발생할 수 없음"은 따옴표로 묶인 이유가 있습니다. 프로그래머가 프로그램의 다른 부분에서 뭔가 잘못한 경우에만 발생할 수 있습니다. 어설 션은 프로그래머의 실수에 대한 검사입니다.
Ned Batchelder

@NedBatchelder 프로그래머 라는 용어 는 라이브러리를 개발할 때 약간 모호합니다. 그러한 "불가능한 상황" 도서관 이용자가 불가능 해야 하지만 도서관 저자가 실수를했을 때 가능할 수 있다는 것이 맞 습니까?
Bruno Zell

12

편집 : 게시물에서 작성한 편집 / 노트에 대한 응답으로 : 예외를 사용하는 것이 달성하려는 유형에 대한 주장을 사용하는 것보다 사용하는 것이 옳은 것처럼 들립니다. 나는 당신이 똑같은 목적을 달성하기 위해 예외와 ​​주장을 고려하고 있으므로 어떤 것을 사용하기에 '올바른'지 파악하려고 노력하고 있다는 것이 정신적 걸림돌이라고 생각합니다. 어설 션과 예외를 사용할 수있는 방법에 약간의 겹침이있을 수 있지만 동일한 문제에 대한 서로 다른 솔루션이라는 점을 혼동하지 마십시오. 그렇지 않습니다. 주장과 예외에는 각각 고유 한 목적, 강점 및 약점이 있습니다.

나는 내 자신의 말로 답을 입력하려고했지만 이것은 내가 가진 것보다 더 나은 개념을 정의합니다.

C # 스테이션 : 어설 션

assert 문을 사용하면 런타임에 프로그램 논리 오류를 효과적으로 포착 할 수 있지만 프로덕션 코드에서 쉽게 필터링 할 수 있습니다. 개발이 완료되면 컴파일 중에 전 처리기 기호 NDEBUG [모든 어설 션을 비활성화 함]을 정의하여 코딩 오류에 대한 이러한 중복 테스트의 런타임 비용을 제거 할 수 있습니다. 그러나 어설 션 자체에 배치 된 코드는 프로덕션 버전에서 생략된다는 점을 기억하십시오.

어설 션은 다음이 모두 유지되는 경우에만 조건을 테스트하는 데 가장 적합합니다.

* the condition should never be false if the code is correct,
* the condition is not so trivial so as to obviously be always true, and
* the condition is in some sense internal to a body of software.

어설 션은 소프트웨어가 정상적으로 작동하는 동안 발생하는 상황을 감지하는 데 거의 사용되지 않아야합니다. 예를 들어 일반적으로 사용자 입력의 오류를 확인하는 데 어설 션을 사용해서는 안됩니다. 그러나 호출자가 이미 사용자 입력을 확인했는지 확인하기 위해 어설 션을 사용하는 것이 합리적 일 수 있습니다.

기본적으로 프로덕션 애플리케이션에서 포착 / 처리해야하는 항목에는 예외를 사용하고, 개발에는 유용하지만 프로덕션에서는 해제되는 논리적 검사를 수행하기 위해 어설 션을 사용합니다.


나는 그 모든 것을 깨닫는다. 그러나 문제는 굵게 표시 한 것과 동일한 진술이 예외에도 적용된다는 것입니다. 그래서 내가 보는 방식으로, 어설 션 대신 예외를 던질 수 있습니다. ( "발생해서는 안되는 상황"이 배포 된 버전에서 발생하는 경우에도 여전히 이에 대한 정보를 받고 싶습니다. 손상된 상태가 될 수 있으므로 예외가 적합합니다. 정상적인 실행 흐름을 계속하고 싶지 않을 수 있습니다.)

1
불변성에 대해 어설 션을 사용해야합니다. 예외는 무언가가 null이 아니어야하는 경우에 사용해야하지만 (메서드에 대한 매개 변수처럼) 될 것입니다.
Ed S.

나는 모든 것이 얼마나 방어 적으로 코딩하고 싶은지에 달려 있다고 생각합니다.
Ned Batchelder

나는 당신이 필요로하는 것으로 보이는 것에 대해 예외가 갈 길이라는 것에 동의합니다. 당신은 당신이 원한다고 말했습니다 : 생산에서 감지 된 실패, 오류에 대한 정보를 기록하는 능력, 실행 흐름 제어 등.이 세 가지가 당신이해야 할 일은 몇 가지 예외를 던지는 것이라고 생각하게합니다.
Tom Neyland

7

나는 (기고 된) 실용적인 예가 차이점을 밝히는 데 도움이 될 것이라고 생각합니다.

( MoreLinq의 Batch 확장 에서 수정 됨 )

// 'public facing' method
public int DoSomething(List<string> stuff, object doohickey, int limit) {

    // validate user input and report problems externally with exceptions

    if(stuff == null) throw new ArgumentNullException("stuff");
    if(doohickey == null) throw new ArgumentNullException("doohickey");
    if(limit <= 0) throw new ArgumentOutOfRangeException("limit", limit, "Should be > 0");

    return DoSomethingImpl(stuff, doohickey, limit);
}

// 'developer only' method
private static int DoSomethingImpl(List<string> stuff, object doohickey, int limit) {

    // validate input that should only come from other programming methods
    // which we have control over (e.g. we already validated user input in
    // the calling method above), so anything using this method shouldn't
    // need to report problems externally, and compilation mode can remove
    // this "unnecessary" check from production

    Debug.Assert(stuff != null);
    Debug.Assert(doohickey != null);
    Debug.Assert(limit > 0);

    /* now do the actual work... */
}

따라서 Eric Lippert 등이 말했듯이, 여러분 (개발자)이 실수로 다른 곳에서 잘못 사용한 경우 대비하여 정확할 것으로 예상되는 내용 만 주장 하므로 코드를 수정할 수 있습니다. 예를 들어 사용자 입력 과 같이 들어오는 것을 제어 할 수 없거나 예상 할 수없는 경우 기본적으로 예외를 발생 시켜 잘못된 데이터를 제공 한 모든 것이 적절하게 응답 할 수 있습니다 (예 : 사용자).


3 개의 Assert가 완전히 중복되지 않습니까? 매개 변수가 거짓으로 평가되는 것은 불가능합니다.
David Klempfner

1
그것이 요점입니다. 불가능한 것을 문서화하기위한 주장이 있습니다. 왜 그렇게 하시겠습니까? DoSomethingImpl 메서드 내부에 "여기서 null을 역 참조 할 수 있습니다."라고 경고하는 ReSharper와 같은 것이있을 수 있으며 "내가하는 일을 알고 있습니다. 이것은 null이 될 수 없습니다."라고 말하고 싶을 수 있습니다. 또한 DoSomething과 DoSomethingImpl 사이의 연결을 즉시 인식하지 못할 수있는 후기 프로그래머를위한 표시이기도합니다.
Marcel Popescu

4

Code Complete의 또 다른 너겟 :

"어설 션은 가정이 사실이 아닌 경우 큰 소리로 불평하는 함수 또는 매크로입니다. 코드에서 만들어진 가정을 문서화하고 예기치 않은 조건을 제거하려면 어설 션을 사용하십시오. ...

"개발 중 단언은 모순 된 가정, 예상치 못한 조건, 루틴에 전달 된 잘못된 값 등을 제거합니다."

그는 계속해서 주장해야 할 것과해서는 안되는 것에 대한 몇 가지 지침을 추가합니다.

반면에 예외 :

"예외 처리를 사용하여 예상치 못한 경우에주의를 기울이십시오. 예외적 인 경우는 개발 중에 명확하게 만들고 프로덕션 코드가 실행 중일 때 복구 할 수있는 방식으로 처리해야합니다."

이 책이 없다면 구입해야합니다.


2
책을 읽었습니다. 훌륭합니다. 그러나 .. 당신은 내 질문에 대답하지 않았습니다 :)

당신 말이 맞아요. 내 대답은 아니오입니다. 동의하지 않습니다. 어설 션 및 예외는 위에서 설명한 것과 다른 동물이며 여기에 게시 된 다른 답변 중 일부가 있습니다.
Andrew Cowenhoven

0

Debug.Assert는 기본적으로 디버그 빌드에서만 작동하므로 릴리스 빌드에서 예기치 않은 잘못된 동작을 포착하려면 예외를 사용하거나 프로젝트 속성에서 디버그 상수를 설정해야합니다 ( 일반적으로 좋은 생각이 아닙니다.)


첫 번째 부분 문장은 사실이고 나머지는 일반적으로 나쁜 생각입니다. 단언은 가정이며 유효성 검사가 없습니다 (위에 언급 됨). 릴리스에서 디버그를 활성화하는 것은 실제로 옵션이 아닙니다.
Marc Wittke

0

가능하지만 일어나서 는 안되는 일에 대해 단정을 사용하십시오 (불가능하다면 왜 단언을 하시겠습니까?).

를 사용하는 경우처럼 들리지 Exception않습니까? 대신 어설 션을 사용하는 이유는 무엇 Exception입니까?

어설 션의 매개 변수가 거짓이되는 것을 막는 어설 션 전에 호출되는 코드가 있어야하기 때문입니다.

일반적으로 이전에 코드가 없습니다. Exception 으로 던지지 않을 것이라는 것을 보장하는 .

Debug.Assert()찌르기에서 컴파일 된 것이 좋은 이유는 무엇 입니까? 디버그에서 그것에 대해 알고 싶다면 prod에서 알고 싶지 않습니까?

일단 Debug.Assert(false)상황 을 찾으면 Debug.Assert(false)다시 발생하지 않도록 코드를 작성 하기 때문에 개발 중에 만 원합니다 . 개발이 완료되면 Debug.Assert(false)상황을 발견 하고 수정 했다고 가정하면 Debug.Assert()이제 중복되므로 안전하게 컴파일 할 수 있습니다.


0

당신이 상당히 큰 팀의 일원이고 클래스에 대한 겹침을 포함하여 모두 동일한 일반 코드 기반에서 작업하는 여러 사람이 있다고 가정합니다. 다른 여러 메서드에 의해 호출되는 메서드를 만들 수 있으며 잠금 경합을 피하기 위해 별도의 잠금을 추가하지 않고 특정 잠금을 사용하여 호출하는 메서드에 의해 이전에 잠긴 것으로 "가정"합니다. 예를 들면 Debug.Assert (RepositoryLock.IsReadLockHeld || RepositoryLock.IsWriteLockHeld); 다른 개발자는 호출 메서드가 잠금을 사용해야한다는 주석을 간과 할 수 있지만 무시할 수는 없습니다.

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