주장은 악한가? [닫은]


199

Go언어 제작자는 쓰기 :

Go는 어설 션을 제공하지 않습니다. 그것들은 명백히 편리하지만 프로그래머들은 적절한 오류 처리 및보고에 대한 생각을 피하기 위해 목발로 사용한다는 경험이 있습니다. 적절한 오류 처리는 치명적이지 않은 오류 후에 서버가 충돌하지 않고 계속 작동 함을 의미합니다. 적절한 오류보고는 오류가 직접적이고 정확한 시점임을 의미하므로 프로그래머는 큰 충돌 추적을 해석하지 않아도됩니다. 정확한 오류는 프로그래머가 오류를보고 코드에 익숙하지 않은 경우 특히 중요합니다.

이것에 대한 당신의 의견은 무엇입니까?


4
탄젠트 : Go는 비정상적으로 평가되는 언어입니다. 반드시 나쁜 것은 아닙니다. 그러나 그것은 더 큰 소금 알갱이로 의견을 가져야한다는 것을 의미합니다. 또한 동의하지 않으면 언어를 사용할 때 이빨이 날 것입니다. 현실에도 불구하고 Go가 자신의 의견에 어떻게 고착되는지에 대한 증거로, 두 컬렉션이 같은지 결정하기 위해 반사의 마법에 의지해야한다는 것을 고려하십시오.
allyourcode

당신이 참조하는 경우 @allyourcode는 reflect.DeepEqual, 당신은 확실히하지 않는 필요 를. 편리하지만 성능이 저하됩니다 (단위 테스트는 좋은 사용 사례입니다). 그렇지 않으면, 너무 많은 문제없이 "수집"에 적합한 평등 검사를 구현할 수 있습니다.
Igor Dubinskiy

1
아뇨, 그건 제가 말하는 것이 아닙니다. 리플렉션이없는 slice1 == slice2와 같은 것은 없습니다. 다른 모든 언어는이 슈퍼 기본 작업과 동일합니다. 바둑이 편견이 아닌 유일한 이유는 편견입니다.
allyourcode

forGo 에서 루프를 사용하여 반사없이 두 슬라이스를 비교할 수 있습니다 (C와 동일). 포인터와 구조체 참여 때 비교가 복잡해진다 있지만, 일반적인 조각 작업이 정말 좋은.
kbolino

답변:


321

아니요, assert의도 한대로 사용하는 한 아무 문제가 없습니다 .

즉, 일반적인 오류 처리와는 달리 디버깅 중에 "발생할 수없는"경우를 포착하기위한 것입니다.

  • Assert : 프로그램 논리 자체에 오류가 있습니다.
  • 오류 처리 : 프로그램의 버그로 인한 잘못된 입력 또는 시스템 상태입니다.

109

아니요, 악도 goto아닙니다 assert. 그러나 둘 다 오용 될 수 있습니다.

어설 션은 상태 점검을위한 것입니다. 올바르지 않은 경우 프로그램을 종료해야하는 것. 유효성 검사 또는 오류 처리를 대체하지 않습니다.


goto현명하게 사용하는 방법 ?
ar2015

1
@ ar2015 일부 사람들 goto은 순전히 종교적인 이유로 피하는 것이 좋습니다 goto. 다시 말해 goto, 당신이 정말로 필요하다는 것을 증명할 수 있다면 , 유일한 대안은 궁극적으로 동일한 것을 수행하지만 Goto Police를 변경하지 않고 무의미한 스캐 폴딩을 실행하는 것 goto입니다. 그냥 사용하십시오 . 물론, 이것의 전제 조건은 "만약 당신이 정말로 필요하다는 것을 증명할 수 있다면 goto"입니다. 종종 사람들은 그렇지 않습니다. 그렇다고해서 그것이 본질적으로 나쁜 것임을 의미하는 것은 아닙니다.
underscore_d

2
goto코드 정리를 위해 리눅스 커널에서 사용
malat

61

이 논리에 따르면 중단 점도 악합니다.

어설 션은 디버깅 보조 도구로 사용되어야합니다. "악"은 오류 처리 대신 사용하려고 할 때 입니다.

프로그래머는 존재하지 않아야하는 문제를 감지 및 수정하고 가정이 계속 유지되는지 확인하는 데 도움이됩니다.

오류 처리와는 아무런 관련이 없지만 불행히도 일부 프로그래머는 오류를 악용 한 다음 "악"이라고 선언합니다.


40

나는 assert를 많이 사용하고 싶다. 처음으로 응용 프로그램을 구축 할 때 (아마도 새로운 도메인) 매우 유용합니다. 매우 빠른 오류 검사 (조기 최적화를 고려할 것)를 수행하는 대신 빠르게 코딩하고 많은 주장을 추가합니다. 작동 방식에 대해 더 많이 알고 난 후에는 다시 작성하고 어설 션을 제거하고 더 나은 오류 처리를 위해 변경합니다.

주장으로 인해 코딩 / 디버깅 프로그램에 많은 시간을 소비하지 않습니다.

또한이 주장은 프로그램을 망칠 수있는 많은 것들을 생각하는 데 도움이된다는 것을 알게되었습니다.


31

추가 정보로서 go는 내장 기능을 제공합니다 panic. 대신에 사용할 수 있습니다 assert. 예 :

if x < 0 {
    panic("x is less than 0");
}

panic스택 추적을 인쇄하므로 어떤 식 으로든 목적이 assert있습니다.


30

프로그램에서 버그를 탐지하는 데 사용해야합니다. 나쁜 사용자 입력이 아닙니다.

올바르게 사용하면 악 이 아닙니다 .


13

이것은 많이 나오며 어설 션 방어를 혼란스럽게 만드는 한 가지 문제는 종종 인수 확인을 기반으로한다는 것입니다. 따라서 어설 션을 사용할 수있는 다음과 같은 다른 예를 고려하십시오.

build-sorted-list-from-user-input(input)

    throw-exception-if-bad-input(input)

    ...

    //build list using algorithm that you expect to give a sorted list

    ...

    assert(is-sorted(list))

end

때때로 입력이 잘못 될 것으로 예상되므로 입력에 대해 예외를 사용합니다. 알고리즘에서 버그를 찾는 데 도움이되도록 목록이 정렬되어 있다고 주장합니다. 어설 션은 디버그 빌드에만 있으므로 검사 비용이 많이 들더라도 루틴을 호출 할 때마다 수행하지 않아도됩니다.

여전히 프로덕션 코드를 단위 테스트해야하지만, 코드가 올바른지 확인하는 다른 보완적인 방법입니다. 단위 테스트를 통해 일상적인 인터페이스를 유지할 수 있으며 단정은 구현이 예상 한대로 정확하게 수행되도록하는보다 세밀한 방법입니다.


8

주장은 악한 것이 아니지만 쉽게 오용 될 수 있습니다. 나는 "어설 션은 종종 적절한 오류 처리 및보고에 대한 생각을 피하기 위해 목발로 사용된다"는 진술에 동의합니다. 나는 이것을 아주 자주 보았다.

개인적으로 저는 어설 션을 사용하고 싶습니다. 코드를 작성하는 동안 만들었을 수도 있다는 가정을 문서화했기 때문입니다. 코드를 유지하면서 이러한 가정이 깨지면 테스트 중에 문제가 감지 될 수 있습니다. 그러나 프로덕션 빌드를 수행 할 때 (예 : #ifdefs 사용) 내 코드에서 모든 주장을 제거해야합니다. 프로덕션 빌드에서 어설 션을 제거함으로써 누군가를 버팀목으로 잘못 사용할 위험을 제거합니다.

어설 션에 또 다른 문제가 있습니다. 어설 션은 런타임에만 확인됩니다. 그러나 종종 수행하려는 검사가 컴파일 타임에 수행되었을 수 있습니다. 컴파일 타임에 문제를 감지하는 것이 좋습니다. C ++ 프로그래머에게 boost는 BOOST_STATIC_ASSERT를 제공하여이를 수행 할 수 있습니다. C 프로그래머의 경우이 기사 ( 링크 텍스트 )는 컴파일시 어설 션을 수행하는 데 사용할 수있는 기술을 설명합니다.

요약하자면, 내가 따르는 경험의 규칙은 다음과 같습니다. 프로덕션 빌드에서는 어설 션을 사용하지 말고 가능하면 컴파일 타임에 확인할 수없는 항목에 대해서만 어설 션을 사용하십시오 (즉, 런타임시 확인해야 함).


5

적절한 오류보고를 고려하지 않고 어설 션을 사용했음을 인정합니다. 그러나 올바르게 사용하면 도움이된다는 사실을 없애지는 않습니다.

"Crash Early"원칙을 따르려는 경우에 특히 유용합니다. 예를 들어 참조 계산 메커니즘을 구현한다고 가정합니다. 코드의 특정 위치에서 참조 횟수가 0 또는 1이어야한다는 것을 알고 있습니다. 또한 참조 횟수가 잘못되면 프로그램이 즉시 중단되지 않지만 다음 메시지 루프 중에는 문제가 발생한 이유를 찾는 것이 어려울 것이라고 가정하십시오. 주장은 그 기원에 가까운 오류를 감지하는 데 도움이되었을 것입니다.


5

디버그 및 릴리스에서 다른 작업을 수행하는 코드를 피하는 것이 좋습니다.

조건에 따라 디버거를 중단하고 모든 파일 / 라인 정보를 갖는 것은 정확한 표현과 정확한 값입니다.

"디버그에서만 조건을 평가"하는 어설트를 갖는 것은 성능 최적화 일 수 있으며, 따라서 사람들이 자신이하는 일을 알고있는 프로그램의 0.0001 %에서만 유용합니다. 다른 모든 경우에 표현은 실제로 프로그램의 상태를 변경할 수 있으므로 해 롭습니다.

assert(2 == ShroedingersCat.GetNumEars()); 디버그 및 릴리스에서 프로그램이 다른 작업을 수행하게합니다.

우리는 예외를 발생시키는 디버그 매크로 세트를 개발했으며 디버그 및 릴리스 버전에서 모두 예외를 처리합니다. 예를 들어, THROW_UNLESS_EQ(a, 20);what () 메시지와 함께 file, line 및 a의 실제 값 등 이있는 예외가 발생 합니다. 매크로 만이이 기능을 사용할 수 있습니다. 디버거는 특정 예외 유형의 'throw'에서 중단되도록 구성 될 수 있습니다.


4
인수에 사용 된 통계의 90 %가 거짓입니다.
João Portela

5

나는 강렬한 주장을 싫어한다. 나는 그들이 악하다고 말하는 것까지 가지 않을 것입니다.

기본적으로 어설트는 확인되지 않은 예외와 동일한 작업을 수행하지만, 유일한 예외는 어설트 (일반적으로)가 최종 제품에 대해 유지되지 않아야한다는 것입니다.

시스템을 디버깅하고 구축하는 동안 스스로 안전망을 구축하는 경우 고객, 지원 헬프 데스크 또는 현재 구축중인 소프트웨어를 사용할 수있는 사람을 위해이 안전망을 거부해야하는 이유는 무엇입니까? 주장과 예외적 인 상황 모두에 예외를 사용하십시오. 적절한 예외 계층 구조를 작성하면 서로 매우 신속하게 식별 할 수 있습니다. 이 경우를 제외하고는, 주장은 제자리에 남아 있고 실패한 경우에는 손실 될 수있는 귀중한 정보를 제공 할 수 있습니다.

그래서 저는 주장을 완전히 제거하고 프로그래머가 예외를 사용하여 상황을 처리하도록하여 Go 제작자를 완전히 이해합니다. 이것에 대한 간단한 설명이 있습니다. 예외는 왜 구식 주장에 충실합니까?


바둑에는 예외가 없습니다. 예외가 아닌 어설 션을 사용하는 일반적인 이유는 성능상의 이유로 배포에서 생략하기 때문입니다. 주장은 구식이 아닙니다. 유감스럽게 들리지만 죄송하지만이 답변은 원래 질문에 원격으로 도움이되지도 않습니다.
Nir Friedman


3

최근에 내 코드에 몇 가지 어설 션을 추가하기 시작했으며 이것이 내가 한 일입니다.

나는 정신적으로 코드를 경계 코드와 내부 코드로 나눕니다. 경계 코드는 사용자 입력을 처리하고 파일을 읽고 네트워크에서 데이터를 가져 오는 코드입니다. 이 코드에서는 입력이 유효한 경우 (대화 형 사용자 입력의 경우) 종료되거나 루프 할 수없는 파일 / 네트워크 손상 데이터의 경우 예외를 throw하는 입력을 루프에 요청합니다.

내부 코드는 다른 모든 것입니다. 예를 들어, 클래스에서 변수를 설정하는 함수는 다음과 같이 정의 될 수 있습니다.

void Class::f (int value) {
    assert (value < end);
    member = value;
}

네트워크에서 입력을받는 함수는 다음과 같이 읽을 수 있습니다.

void Class::g (InMessage & msg) {
    int const value = msg.read_int();
    if (value >= end)
        throw InvalidServerData();
    f (value);
}

이것은 두 가지 검사 계층을 제공합니다. 런타임에 데이터가 결정되면 항상 예외 또는 즉각적인 오류 처리가 발생합니다. 그러나에 여분의 체크 Class::fassert일부 내부 코드 이제까지 호출하면 문 수단는 것을 Class::f, 나는 아직도 전성 검사가 있습니다. (I 계산 한 수 있기 때문에 내 내부 코드는 유효한 인수를 전달하지 않을 수도 value기능의 일부 복잡한 시리즈에서)에 관계없이 함수를 호출 누구의 것을 문서에 설정 기능의 주장을 가진 것처럼, 그래서 value보다 클 수 또는 안 와 같습니다 end.

이것은 내가 몇 곳에서 읽고있는 내용에 맞는 것처럼 보이며, 잘 작동하는 프로그램에서는 어설 션을 위반하는 것이 불가능해야하지만 여전히 가능한 예외적이고 잘못된 경우에는 예외가 있어야합니다. 이론적으로 모든 입력의 유효성을 검사하기 때문에 어설 션이 트리거 될 수 없습니다. 그렇다면 내 프로그램이 잘못되었습니다.


2

당신이 말하는 주장이 프로그램이 구토를하고 존재한다는 것을 의미한다면, 주장은 매우 나쁠 수 있습니다. 이것은 그들이 항상 잘못 사용 한다고 말하려는 것이 아니며 , 매우 쉽게 오용되는 구조입니다. 또한 더 나은 대안이 많이 있습니다. 그런 것들이 악이라고 불리우기에 좋은 후보입니다.

예를 들어, 타사 모듈 (또는 다른 모듈)은 호출 프로그램을 종료하지 않아야합니다. 이것은 호출 프로그래머에게 프로그램이 그 순간에 어떤 위험을 감수해야하는지에 대한 통제권을 부여하지 않습니다. 많은 경우, 데이터가 너무 중요하여 손상된 데이터를 저장하는 것이 데이터를 잃는 것보다 낫습니다. 어설 션을 사용하면 데이터가 손실 될 수 있습니다.

주장에 대한 몇 가지 대안 :

  • 디버거를 사용하여
  • 콘솔 / 데이터베이스 / 기타 로깅
  • 예외
  • 다른 유형의 오류 처리

일부 참고 문헌 :

주장을 주장하는 사람들조차도 개발이 아닌 생산 에만 사용해야한다고 생각합니다 .

이 사람은 예외가 발생한 후에도 모듈이 잠재적으로 데이터를 손상 시켰을 때 어설 션을 사용해야한다고 말합니다 ( http://www.advogato.org/article/949.html) . 이것은 분명히 합리적인 지점이지만 외부 모듈은 손상된 데이터가 호출 프로그램에 얼마나 중요한지를 규정 해서는 안됩니다 ( "for"를 종료하여). 이를 처리하는 올바른 방법은 프로그램이 이제 일관성이없는 상태임을 분명히하는 예외를 발생시키는 것입니다. 그리고 좋은 프로그램은 대부분 모듈로 구성되어 있기 때문에 (주 실행 파일에 작은 접착 코드가 있음) 어설 션은 거의 항상 잘못된 것입니다.


1

assert 매우 유용하고 문제의 첫 징후에서 프로그램을 중지하여 예기치 않은 오류가 발생할 때 많은 역 추적을 줄일 수 있습니다.

반면에, 학대하는 것은 매우 쉽습니다 assert.

int quotient(int a, int b){
    assert(b != 0);
    return a / b;
}

올바른 올바른 버전은 다음과 같습니다.

bool quotient(int a, int b, int &result){
    if(b == 0)
        return false;

    result = a / b;
    return true;
}

장기적으로는 ... 큰 그림에서는 ... assert남용 될 수 있음에 동의해야합니다 . 나는 항상 그것을한다.


1

assert 타이핑이 적기 때문에 오류 처리로 인해 남용됩니다.

따라서 언어 디자이너는 더 적은 타이핑으로도 적절한 오류 처리가 가능하다는 것을 알아야합니다. 예외 메커니즘이 장황하므로 assert를 제외하는 것은 해결책이 아닙니다. 아 잠깐, Go도 예외는 없습니다. 너무 나쁘다 :)


1
너무 나쁘지 않습니다 :-) 예외 여부, 주장 여부에 관계없이 Go 팬은 여전히 ​​코드가 얼마나 짧은 지에 대해 이야기하고 있습니다.
Moshe Revah

1

나는 그것을 보았을 때 저자의 머리를 차는 것처럼 느꼈다.

나는 코드에서 항상 assert를 사용하고 더 많은 코드를 작성할 때 그것들을 모두 대체한다. 필요한 논리를 작성하지 않았을 때이를 사용하고 프로젝트가 완료 될 때 삭제되는 예외를 작성하는 대신 코드를 실행할 때 경고를 원합니다.

예외는 내가 싫어하는 생산 코드와 더 쉽게 혼합됩니다. 주장은 눈치 채기보다 쉽다throw new Exception("Some generic msg or 'pretend i am an assert'");


1

assert를 방어하는 이러한 답변에 대한 나의 문제는 아무도 그것이 일반적인 치명적 오류 와 다른 점을 명확하게 지정하지 않으며 assert가 예외 의 하위 집합이 될 수없는 이유를 명확하게 지정 하지 않는다는 것 입니다. 이제이 말을 통해 예외가 발생하지 않으면 어떻게 될까요? 그것이 명명법에 의한 주장입니까? 그리고 왜 / nothing /이 처리 할 수있는 예외를 제기 할 수있는 언어로 제한을 적용하고 싶습니까?


내 대답을 보면 내 사용은 내가 유지하는 예외와 디버깅에 사용되는 '예외'(어설 션)를 차별화하는 것입니다. 왜 제거하고 싶습니까? 그들이 작동하지 않으면 완전하지 않기 때문입니다. 예를 들어 3 건을 처리하고 4 건을 할 경우입니다. 코드에서 쉽게 찾을 수있는 assert를 검색하고 불완전한 것을 알기보다는 실수로 (다른 프로그래머가 실수로 잡을 수 있거나 예외 또는 코드에서 해결해야 할 논리 검사인지 알기 어려운 예외를 사용합니다.

내 눈에, 이것은 "밀봉 된"수업과 같은 수준에서, 그리고 같은 이유로 나쁜 생각입니다. 아직 모르는 코드를 사용하기 위해 유지하려는 예외가 허용된다고 가정합니다. 모든 예외는 동일한 채널을 거치므로 사용자가이를 원하지 않으면 원하지 않는 것을 선택할 수 있습니다. 만약 그렇다면, 능력도 있어야합니다. 어느 쪽이든, 당신은 가정을하거나 어설 션과 같은 개념으로 연습을 시작합니다.
Evan Carroll

예제 시나리오가 가장 좋다고 결정했습니다. 여기 간단한 것이 있습니다. int func (int i) {if (i> = 0) {console.write ( "숫자는 양의 {0}", i); } else {assert (false); // 부정을하기 위해 게으르다 ATM} return i * 2; <-어설 션없이 어떻게하면 좋을까요? 예외가 더 낫습니까? 이 코드는 출시 전에 구현됩니다.

물론 예외가 더 낫습니다. 사용자 입력 func()을 받고 음수로 전화한다고 가정 해 봅시다. 자, 당신의 주장으로 갑자기 당신은 내 아래에서 카펫을 꺼내어 회복 할 기회를 얻지 못했습니다. 프로그램이 잘못 행동했다는 사실을 고발하고이를 인용하는 데 아무런 문제가 없습니다. 문제는 법을 집행하는 행위를 흐리게하고 중범 죄를 선고하는 것입니다.
Evan Carroll

그 시점에서, 당신은 사용자가하고 싶은 일을 할 수 없다고 말해야합니다. 응용 프로그램은 오류 메시지와 함께 종료해야하며 디버깅하는 사람은 완료해야하지만 그렇지 않은 것을 기억합니다. 당신은 그것을 처리하기를 원하지 않습니다. 귀하는 해지를 원하며 해당 사건을 처리하지 않았다는 사실을 상기 시키십시오. 코드에서 어설 션을 검색하기 만하면되기 때문에 어떤 사례가 남아 있는지 쉽게 알 수 있습니다. 코드가 프로덕션 준비가되면 오류를 처리 할 수 ​​있기 때문에 오류 처리가 잘못 될 수 있습니다. 프로그래머가 그것을 잡고 다른 일을하도록 허용하는 것은 잘못입니다.

1

그렇습니다. 주장은 악합니다.

적절한 오류 처리가 필요한 곳에서 종종 사용됩니다. 처음부터 올바른 생산 품질 오류 처리에 익숙해 지십시오!

일반적으로 테스트 도구와 상호 작용하는 사용자 지정 어설 ​​션을 작성하지 않는 한 단위 테스트 작성에 방해가됩니다. 적절한 오류 처리를 사용해야하는 경우에 종종 사용되기 때문입니다.

대부분 릴리스 빌드에서 컴파일됩니다. 즉, 실제로 릴리스 한 코드를 실행할 때 "테스트"를 사용할 수 없습니다. 다중 스레드 상황에서 최악의 문제는 종종 릴리스 코드에만 표시되므로 이는 나쁠 수 있습니다.

때로는 디자인이 깨진 디자인의 목발이기도합니다. 즉, 코드의 디자인은 사용자가 호출해서는 안되는 방식으로 코드를 호출 할 수있게하고 주장은 이것을 "예방"합니다. 디자인을 수정하십시오!

2005 년에 다시 블로그에이 내용을 더 썼습니다 : http://www.lenholgate.com/blog/2005/09/assert-is-evil.html


0

일반적으로 비생산적인 것만 큼 그렇게 악하지는 않습니다. 영구적 인 오류 검사와 디버깅은 분리되어 있습니다. Assert는 사람들이 모든 디버깅이 영구적이어야한다고 생각하고 많이 사용하면 가독성이 크게 저하된다고 생각합니다. 영구적 인 오류 처리는 필요한 경우보다 우수해야하며, assert는 자체 오류를 유발하기 때문에 꽤 의심스러운 사례입니다.


5
assert는 함수 상단에 사전 조건을 선언하는 데 적합하며 명확하게 작성된 경우 함수 문서의 일부로 작동합니다.
Peter Cordes

0

나는 결코 assert ()를 사용하지 않는다. 예제들은 보통 다음과 같이 보인다 :

int* ptr = new int[10];
assert(ptr);

이것은 나쁘다, 나는 결코 이것을하지 않는다. 내 게임이 괴물 무리를 할당한다면 어떻게 될까? 왜 내가 게임을 중단해야합니까, 대신 오류를 정상적으로 처리해야하므로 다음과 같이하십시오.

CMonster* ptrMonsters = new CMonster[10];
if(ptrMonsters == NULL) // or u could just write if(!ptrMonsters)
{
    // we failed allocating monsters. log the error e.g. "Failed spawning 10 monsters".
}
else
{
    // initialize monsters.
}

14
new절대 반환하지 않습니다 nullptr.
David Stone

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