Java assert 키워드는 무엇을하며 언제 사용해야합니까?


601

주장의 주요 역할을 이해하기위한 실제 사례 는 무엇입니까 ?


8
실제 생활에서는 거의 볼 수 없습니다. 결론 : 어설 션을 사용하는 경우 세 가지 상태를 고려해야합니다. 어설 션 통과, 어설 션 실패, 어설 션이 두 개가 아닌 해제됩니다. assert는 기본적으로 해제되어 있으므로 가장 가능성이 높은 상태이므로 코드에서 사용하도록 설정하기가 어렵습니다. 어설 션은 사용이 제한적인 조기 최적화라는 것입니다. @Bjorn의 답변에서 볼 수 있듯이 어설 션을 항상 실패하지 않으려는 유스 케이스를 생각해내는 것은 어렵습니다.
Yishai

35
@Yishai : "생각해야합니다 ... 주장이 꺼져 있습니다" 그렇게해야한다면 잘못하고있는 것입니다. "어설 션은 제한된 사용을 조기에 최적화하는 것입니다 ." 다음은이에 일의 포획은 " 자바 기술에 어설 션을 사용하여 "이 읽고도 좋다 " (일명 문 주장) 주장과 프로그래밍의 장점을 "
데이비드 Tonhofer

5
@DavidTonhofer, 실제 생활에서는 거의 볼 수 없습니다. 이것은 검증 가능합니다. 원하는만큼 오픈 소스 프로젝트를 확인하십시오. 나는 당신이 불변성을 검증하지 않는다고 말하지 않습니다. 그것은 같은 것이 아닙니다. 다른 길을 봅시다. 주장이 매우 중요한 경우 왜 기본적으로 해제되어 있습니까?
Yishai

17
참조, FWIW : 소프트웨어 주장과 코드 품질 사이의 관계 : "우리는 또한 주장의 효능을 소스 코드 정적 분석 도구와 같은 인기있는 버그 찾기 기술의 효과와 비교합니다. 사례 연구에서 주장 밀도가 증가함에 따라 파일에서 결함 밀도가 통계적으로 크게 감소합니다. "
David Tonhofer 2012 년

4
@DavidTonhofer David, 어설 션에 대한 당신의 사랑은 당신이하고있는 매우 구체적인 유형의 프로그래밍에 대한 것이라고 생각합니다. 제 분야에서 웹 응용 프로그램과 함께 작동하는 웹 응용 프로그램과 함께 작동하는 어떤 분야에서도 가장 큰 것은 아닙니다. 단위 이상 / INTEG 테스트 다른 주장 사용
nightograph

답변:


426

주장 (의 방법에 의한 어설 션 키워드) 자바 1.4에 추가되었습니다. 코드에서 불변의 정확성을 확인하는 데 사용됩니다. 프로덕션 코드에서 트리거되지 않아야하며 코드 경로의 버그 나 오용을 나타냅니다. 명령 의 -ea옵션을 통해 런타임시 활성화 될 수 java있지만 기본적으로 켜져 있지는 않습니다.

예를 들면 :

public Foo acquireFoo(int id) {
  Foo result = null;
  if (id > 50) {
    result = fooService.read(id);
  } else {
    result = new Foo(id);
  }
  assert result != null;

  return result;
}

71
사실, Oracle은 assert공용 메소드 매개 변수 ( docs.oracle.com/javase/1.4.2/docs/guide/lang/assert.html ) 를 확인하는 데 사용하지 말라고 지시합니다 . 그것은 Exception프로그램을 죽이는 대신에 던져야합니다 .
SJuan76

10
그러나 당신은 여전히 ​​그들이 왜 존재하는지 설명하지 않습니다. if () 검사를 수행하고 예외를 던질 수없는 이유는 무엇입니까?
El Mac

7
@ElMac-어설 션은주기의 개발 / 디버그 / 테스트 부분을위한 것이며 프로덕션을위한 것이 아닙니다. if 블록은 prod에서 실행됩니다. 간단한 어설 션으로 인해 은행이 깨지지는 않지만 복잡한 데이터 유효성 검사를 수행하는 비싼 어설 션으로 인해 프로덕션 환경이 다운 될 수 있습니다.
hoodaticus

2
@hoodaticus 당신은 전적으로 코드 코드에 대한 모든 주장을 켜고 끌 수 있다는 사실만을 의미합니다. 어쨌든 복잡한 데이터 유효성 검사를 수행 한 다음 예외로 처리 할 수 ​​있기 때문입니다. 프로덕션 코드가 있으면 복잡한 (어쩌면 비싼) 어설 션을 해제 할 수 있습니다. 작동하고 이미 테스트 되었기 때문입니까? 이론적으로 그들은 당신이 어쨌든 문제가 있기 때문에 프로그램을 중단해서는 안됩니다.
El Mac

8
This convention is unaffected by the addition of the assert construct. Do not use assertions to check the parameters of a public method. An assert is inappropriate because the method guarantees that it will always enforce the argument checks. It must check its arguments whether or not assertions are enabled. Further, the assert construct does not throw an exception of the specified type. It can throw only an AssertionError. docs.oracle.com/javase/8/docs/technotes/guides/language/…
Bakhshi

325

원자력 발전소를 제어하는 ​​프로그램을 작성한다고 가정 해 봅시다. 따라서 코드는, 심지어 가장 사소한 실수가 치명적인 결과를 가질 수있는 매우 분명하다 이어야 버그 프리 (JVM이 버그가없는 인수를 위해서라고 가정).

Java는 검증 가능한 언어가 아닙니다. 즉, 작업 결과가 완벽하다고 계산할 수 없습니다. 이것의 주된 이유는 포인터입니다. 어디에서나 어디에서나 가리킬 수 있으므로 적어도 합리적인 코드 범위 내에 있지 않은 정확한 값으로 계산할 수 없습니다. 이 문제가 발생하면 코드가 전체적으로 정확하다는 것을 증명할 방법이 없습니다. 그러나 당신이 할 수있는 일은 적어도 모든 버그가 발생할 때 발견한다는 것을 증명하는 것입니다.

이 아이디어는 DbC ( Design-by-Contract ) 패러다임을 기반으로합니다 . 먼저 메서드가 수행 할 작업을 수학적으로 정밀하게 정의한 다음 실제 실행 중에 테스트하여이를 확인합니다. 예:

// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
  return a + b;
}

이것은 잘 작동하는 것이 명백하지만 대부분의 프로그래머는이 버그 안에 숨겨진 버그를 보지 못합니다 (힌트 : 비슷한 버그로 인해 Ariane V가 추락했습니다). 이제 DbC는 함수의 입력 및 출력을 항상 확인하여 올바르게 작동하는지 확인 해야한다고 정의 합니다. Java는 어설 션을 통해이를 수행 할 수 있습니다.

// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
    assert (Integer.MAX_VALUE - a >= b) : "Value of " + a + " + " + b + " is too large to add.";
  final int result = a + b;
    assert (result - a == b) : "Sum of " + a + " + " + b + " returned wrong sum " + result;
  return result;
}

이 기능이 이제 실패하면 알 수 있습니다. 코드에 문제가 있음을 알 수 있으며, 코드의 위치와 발생 원인을 알 수 있습니다 (예외와 유사). 그리고 더 중요한 것은 코드가 잘못된 값으로 작동하지 못하게하고 제어하는 ​​모든 항목에 손상을 줄 수있는 경우 즉시 실행을 중지합니다.

Java 예외도 비슷한 개념이지만 모든 것을 확인하지 못합니다. 더 많은 검사를 원한다면 (어택 속도로) 어설 션을 사용해야합니다. 그렇게하면 코드가 부풀어 질 수 있지만 결국 개발 시간이 놀랍도록 짧은 시간에 제품을 제공 할 수 있습니다 (버그를 조기에 해결할수록 비용은 줄어 듭니다). 또한 코드 내부에 버그가 있으면 감지합니다. 버그 제거 방법은 없으며 나중에 문제를 일으킬 수 있습니다.

이것은 여전히 ​​버그가없는 코드를 보장하지는 않지만 일반적인 프로그램보다 훨씬 더 가깝습니다.


29
이 예제는 버그가없는 것처럼 보이는 코드에서 숨겨진 버그를 매우 잘 나타 내기 때문에이 예제를 선택했습니다. 이것이 다른 사람이 제시 한 것과 유사하다면, 그들은 같은 생각을 가지고 있었을 것입니다. ;)
TwoThe

8
어설 션이 거짓이면 어설 션이 실패하므로 어설 션을 선택합니다. if는 어떤 행동을 할 수 있습니다. 프린지 케이스를 치는 것은 유닛 테스팅의 일입니다. 계약 별 설계를 사용하면 계약이 다소 명확하지만 실제 계약과 마찬가지로 계약이 존중되도록해야합니다. 어설 션을 사용하면 계약이 무시 될 때 감시자가 삽입됩니다. 계약 밖에서 또는 서명 한 계약에 위배되는 일을 할 때마다 "WRONG"이라는 비명을 지르는 잔소리가있는 변호사라고 생각하고 집으로 보내서 계속 일을하고 더 이상 계약을 위반할 수 없습니다!
Eric

5
이 간단한 경우에 필요합니다. 아니요. 그러나 DbC는 모든 결과를 확인해야 한다고 정의합니다 . 누군가가 그 기능을 훨씬 더 복잡한 것으로 수정 한 다음 사후 점검도 수정해야한다고 생각하면 갑자기 유용 해집니다.
TwoThe

4
이것을 부활 시켜서 죄송하지만, 구체적인 질문이 있습니다. @TwoThe가 한 것과 assert를 사용하는 대신 new IllegalArgumentException메시지와 함께 a 를 던지는 것의 차이점은 무엇 입니까? 나는 throws메소드 선언과 코드를 추가 하여 그 예외를 다른 곳에서 관리하는 것을 제외하고. 왜 assert새로운 예외가 발생합니까? 아니면 왜 if대신에 assert? 실제로 이것을 얻을 수 없습니다 :(
Blueriver

14
-1 : a음수가 될 수있는 경우 오버플로를 확인하는 어설 션이 잘못되었습니다 . 두 번째 주장은 쓸모가 없다. int 값의 경우 항상 a + b-b == a입니다. 컴퓨터가 근본적으로 고장난 경우에만 해당 테스트가 실패 할 수 있습니다. 이러한 우발적 인 상황을 방지하려면 여러 CPU에서 일관성을 확인해야합니다.
kevin cline

63

어설 션은 코드에서 버그를 포착하는 개발 단계 도구입니다. 그것들은 쉽게 제거되도록 설계되었으므로 프로덕션 코드에는 존재하지 않습니다. 따라서 어설 션은 고객에게 제공하는 "솔루션"의 일부가 아닙니다. 그들은 가정이 올바른지 확인하기 위해 내부 검사입니다. 가장 일반적인 예는 null을 테스트하는 것입니다. 많은 방법이 다음과 같이 작성됩니다 :

void doSomething(Widget widget) {
  if (widget != null) {
    widget.someMethod(); // ...
    ... // do more stuff with this widget
  }
}

이와 같은 방법에서 위젯은 단순히 null이되지 않아야합니다. 따라서 null이면 코드에 추적해야 할 버그가 있습니다. 그러나 위의 코드는 이것을 알려주지 않습니다. 따라서 "안전한"코드를 작성하려는 의도적 인 노력으로 버그를 숨기고 있습니다. 다음과 같은 코드를 작성하는 것이 훨씬 좋습니다.

/**
 * @param Widget widget Should never be null
 */
void doSomething(Widget widget) {
  assert widget != null;
  widget.someMethod(); // ...
    ... // do more stuff with this widget
}

이런 식으로이 버그를 일찍 잡을 수 있습니다. 계약에서이 매개 변수가 널이 아니어야 함을 지정하는 것도 유용합니다. 개발 중에 코드를 테스트 할 때 어설 션을 설정해야합니다. (그리고 동료들에게 이것을하도록 설득하는 것도 종종 어렵 기 때문에 매우 성가시다.)

이제 일부 동료는이 코드에 반대하여 프로덕션에서 예외를 방지하기 위해 여전히 null 검사를 수행해야한다고 주장합니다. 이 경우 어설 션은 여전히 ​​유용합니다. 다음과 같이 작성할 수 있습니다.

void doSomething(Widget widget) {
  assert widget != null;
  if (widget != null) {
    widget.someMethod(); // ...
    ... // do more stuff with this widget
  }
}

이런 식으로 동료들은 프로덕션 코드에 대해 null 검사가 있다는 것을 기쁘게 생각하지만 개발 중에 위젯이 null 일 때 더 이상 버그를 숨기지 않습니다.

실제 예는 다음과 같습니다. 두 번의 임의의 값이 동일한 지 비교하는 방법을 작성했습니다. 두 값 중 하나가 null 일 수 있습니다.

/**
 * Compare two values using equals(), after checking for null.
 * @param thisValue (may be null)
 * @param otherValue (may be null)
 * @return True if they are both null or if equals() returns true
 */
public static boolean compare(final Object thisValue, final Object otherValue) {
  boolean result;
  if (thisValue == null) {
    result = otherValue == null;
  } else {
    result = thisValue.equals(otherValue);
  }
  return result;
}

이 코드 equals()는 thisValue가 null이 아닌 경우 메서드 작업을 위임합니다 . 그러나이 equals()방법 equals()이 null 매개 변수를 올바르게 처리 하여 계약을 올바르게 이행 한다고 가정합니다 .

동료가 내 코드에 반대하여 많은 클래스 equals()에 null을 테스트하지 않는 버그가있는 메소드가 있다고 알려주 므로이 검사를 체크해야합니다. 이것이 현명하거나 오류를 강제해야한다면 논쟁의 여지가 있습니다. 따라서 오류를 발견하고 수정할 수는 있지만 동료에게 연기하고 null 확인을했으며 주석으로 표시했습니다.

public static boolean compare(final Object thisValue, final Object otherValue) {
  boolean result;
  if (thisValue == null) {
    result = otherValue == null;
  } else {
    result = otherValue != null && thisValue.equals(otherValue); // questionable null check
  }
  return result;
}

여기서 추가 검사 는 계약에서 요구 한대로 메소드가 널을 검사하지 못하는 other != null경우에만 필요합니다 equals().

버그가있는 코드를 코드 기반으로 유지하는 데 대한 지혜에 대해 동료들과 결실없는 토론에 참여하기보다는 코드에 두 가지 주장 만 썼습니다. 이 어설 션은 개발 단계에서 클래스 중 하나가 equals()제대로 구현 되지 않으면 알려 줄 수 있습니다.

public static boolean compare(final Object thisValue, final Object otherValue) {
  boolean result;
  if (thisValue == null) {
    result = otherValue == null;
    assert otherValue == null || otherValue.equals(null) == false;
  } else {
    result = otherValue != null && thisValue.equals(otherValue);
    assert thisValue.equals(null) == false;
  }
  return result;
}

명심해야 할 중요한 사항은 다음과 같습니다.

  1. 어설 션은 개발 단계 도구 일뿐입니다.

  2. 주장의 요점은 코드뿐만 아니라 코드 기반에도 버그가 있는지 알려주는 것 입니다. (여기의 주장은 실제로 다른 클래스의 버그를 표시합니다.)

  3. 동료가 수업이 제대로 작성되었다고 확신하더라도 여기에있는 주장이 여전히 유용 할 것입니다. null 테스트에 실패 할 수있는 새로운 클래스가 추가 될 예정이며이 메서드는 이러한 버그를 플래그로 지정할 수 있습니다.

  4. 개발 중에는 작성한 코드가 어설 션을 사용하지 않더라도 항상 어설 션을 설정해야합니다. 내 IDE는 새로운 실행 파일에 대해 항상 기본적 으로이 작업을 수행하도록 설정되어 있습니다.

  5. 어설 션은 프로덕션 환경에서 코드의 동작을 변경하지 않으므로 동료는 null 검사가 있고 equals()메소드가 버그가 있어도이 메소드가 제대로 실행된다는 것을 기쁘게 생각합니다 . equals()개발에서 버그가있는 방법을 잡을 수 있기 때문에 행복 합니다.

또한 실패 할 임시 어설 션을 넣어 어설 션 정책을 테스트해야하므로 로그 파일 또는 출력 스트림의 스택 추적을 통해 알림을받을 수 있습니다.


"버그 숨기기"에 대한 좋은 점과 개발 과정에서 어설 션이 버그를 노출시키는 방법!
nobar

이러한 검사 중 어느 것도 느리지 않으므로 프로덕션에서이를 해제 할 이유가 없습니다. "개발 단계"에 표시되지 않는 문제점을 감지 할 수 있도록 로깅 명령문으로 변환해야합니다. (정말, 어쨌든, 개발 단계 같은 것은 없다 당신이 전혀 코드를 유지 중지하기로 결정하는 경우 개발이 종료됩니다..)
알렉산드르 Dubinsky

20

assert키워드의 기능을 설명하는 좋은 답변 은 많지만 "실제 키워드는 언제 사용해야 assert합니까?"라는 실제 질문에 대한 답변은 거의 없습니다.

대답 은 거의 없습니다 .

개념으로서의 주장은 훌륭합니다. 좋은 코드에는 많은 if (...) throw ...진술이 있고 (과의 친척이 Objects.requireNonNulland Math.addExact) 있습니다. 그러나 특정 디자인 결정은 assert 키워드 자체 의 유용성을 크게 제한했습니다 .

assert키워드 의 원동력 은 조기 최적화이며 주요 기능은 모든 검사를 쉽게 해제 할 수 있다는 것입니다. 실제로, assert검사는 기본적으로 해제되어 있습니다.

그러나 생산에서 변하지 않는 점검을 계속 수행하는 것이 매우 중요합니다. 완벽한 테스트 범위가 불가능하기 때문에 모든 프로덕션 코드에는 진단 및 완화에 도움이되는 버그가 있습니다.

따라서 if (...) throw ... public 메소드의 매개 변수 값을 확인하고 throw하는 데 필요한 것처럼 선호해야합니다 IllegalArgumentException.

때때로, 처리하는 데 바람직하지 않은 시간이 오래 걸리는 불변 검사를 작성하고 싶을 수도 있습니다 (종종 문제가되기에 충분하다고도 함). 그러나 이러한 검사는 테스트 속도를 저하 시키므로 바람직하지 않습니다. 이러한 시간이 많이 걸리는 점검은 일반적으로 단위 테스트로 작성됩니다. 그럼에도 불구하고 때때로 사용하는 것이 합리적 일 수 있습니다assert 이러한 이유로 하는 .

assert그것이 if (...) throw ...깨끗하고 예쁘기 때문에 단순히 사용하지 마십시오 (나는 깨끗하고 예쁘기 때문에 큰 고통으로 말합니다). 자신을 도울 수없고 응용 프로그램이 시작되는 방식을 제어 할 수 있다면 자유롭게 사용 assert하되 항상 프로덕션에서 어설 션을 활성화하십시오. 물론 이것이 내가하는 경향이있다. 나는 롬복 주석을 assert더 밀고 나아갈 것입니다.if (...) throw ... 입니다. 여기에 투표하십시오.

(Rant : JVM 개발자는 끔찍하고 조기에 최적화 된 코더였습니다. 따라서 Java 플러그인 및 JVM의 많은 보안 문제에 대해 들었습니다. 프로덕션 코드에 기본 확인 및 어설 션을 포함하지 않기로했습니다. 가격을 지불하십시오.)


2
@aberglas catch-all 절은 catch (Throwable t)입니다. 등 OutOfMemoryError를, AssertionError를, 복구 / 트랩, 로그, 또는 재 시도에 시도하지 할 이유가 없다
알렉산드르 Dubinsky

1
OutOfMemoryError를 발견하고 복구했습니다.
MiguelMunoz

1
동의하지 않습니다. 내 주장 중 많은 부분이 API가 올바르게 호출되도록하는 데 사용됩니다. 예를 들어, 객체가 잠금을 보유한 경우에만 호출해야하는 전용 메소드를 작성할 수 있습니다. 다른 개발자가 객체를 잠그지 않는 코드의 일부에서 해당 메소드를 호출하면 어설 션은 즉시 실수를했다고 알려줍니다. 개발 단계에서 확실하게 잡힐 수있는 이와 같은 많은 실수가 있으며, 이러한 경우에 주장이 매우 유용합니다.
MiguelMunoz

2
@MiguelMunoz 내 대답에서 나는 주장의 아이디어가 매우 좋다고 말했다. assert키워드 의 구현 이 잘못되었습니다. 개념이 아니라 키워드를 참조하고 있음을보다 명확하게하기 위해 답변을 편집하겠습니다.
Aleksandr Dubinsky

2
나는 그것이 예외 대신 AssertionError를 던진다는 사실을 좋아한다. 너무 많은 개발자들이 코드가 IOException과 같은 것을 던질 수만 있다면 예외를 잡아서는 안된다는 것을 아직 배우지 못했습니다. 누군가 예외를 잡았 기 때문에 코드에 버그가 완전히 삼켜졌습니다. 어설 션은이 함정에 빠지지 않습니다. 프로덕션 코드에서 예상되는 상황은 예외입니다. 로깅의 경우 오류가 드물더라도 모든 오류도 로깅해야합니다. 예를 들어 OutOfMemoryError를 로그하지 않고 전달하도록 하시겠습니까?
MiguelMunoz 1

14

가장 일반적인 사용 사례는 다음과 같습니다. 열거 형 값을 설정한다고 가정합니다.

switch (fruit) {
  case apple:
    // do something
    break;
  case pear:
    // do something
    break;
  case banana:
    // do something
    break;
}

모든 경우를 다루는 한 괜찮습니다. 그러나 언젠가 누군가가 열거 형에 그림을 추가하고 스위치 문에 추가하는 것을 잊어 버릴 것입니다. switch 문을 떠날 때까지 효과가 느껴지지 않기 때문에 잡기가 까다로울 수있는 버그가 발생합니다. 그러나 다음과 같이 스위치를 작성하면 즉시 잡을 수 있습니다.

switch (fruit) {
  case apple:
    // do something
    break;
  case pear:
    // do something
    break;
  case banana:
    // do something
    break;
  default:
    assert false : "Missing enum value: " + fruit;
}

4
따라서 경고를 활성화하고 경고를 오류로 취급해야합니다. 중간 정도의 컴파일러는 열거 형 검사가 누락되어 있음을 알리고 컴파일 시간에 할 수 있다고 알려주는 경우 (어쩌면 언젠가는) 런타임.
Mike Nakis

9
불법 인수 예외와 같은 예외가 아닌 어설 션을 사용하는 이유는 무엇입니까?
liltitus27

4
AssertionError어설 션이 활성화 된 경우 ( -ea)가 발생합니다. 프로덕션에서 원하는 동작은 무엇입니까? 사형 집행이 끝날 무음과 재난이 있을까? 아마 아닙니다. 나는 명시 적으로 제안 할 것이다 throw new AssertionError("Missing enum value: " + fruit);.
aioobe

1
AssertionError를 던지는 것에 대한 좋은 주장이 있습니다. 프로덕션에서의 올바른 동작에 대해서는 어설 션의 핵심이 프로덕션에서 발생하지 않도록하는 것입니다. 어설 션은 프로덕션 코드에서 쉽게 제거 할 수있는 버그를 잡아내는 개발 단계 도구입니다. 이 경우 프로덕션 코드에서 제거 할 이유가 없습니다. 그러나 대부분의 경우 무결성 테스트로 인해 속도가 느려질 수 있습니다. 이러한 테스트를 프로덕션 코드에서 사용되지 않는 어설 션 안에 넣으면 프로덕션 코드가 느려질 염려없이 철저한 테스트를 자유롭게 작성할 수 있습니다.
MiguelMunoz

이것은 잘못된 것 같습니다. IMHO default는 컴파일러가 누락 된 경우에 대해 경고 할 수 있도록 사용해서는 안됩니다 . return대신 break(이 방법을 추출해야 할 수도 있음) 대신 스위치 후 누락 된 사례를 처리 할 수 ​​있습니다. 이렇게하면 경고와 기회를 모두 얻을 수 있습니다 assert.
maaartinus

12

어설 션은 사후 조건을 확인하고 사전 조건을 "실패해서는 안됩니다"를 확인하는 데 사용됩니다. 올바른 코드는 어설 션에 실패하지 않아야합니다. 그들이 발동 할 때, 버그를 나타내야합니다 (실제로 문제의 실제 위치가있는 곳에 가깝습니다).

주장의 예는 특정 그룹의 메소드가 올바른 순서로 호출되는지 확인하는 것입니다 (예 :에서 hasNext()이전 next()에 호출 됨 Iterator).


1
next () 전에 hasNext ()를 호출 할 필요는 없습니다.
DJClayworth

6
@DJClayworth : 어설 션 트리거를 피할 필요가 없습니다. :-)
Donal Fellows

8

Java에서 assert 키워드는 무엇을합니까?

컴파일 된 바이트 코드를 보자.

우리는 결론을 내릴 것입니다 :

public class Assert {
    public static void main(String[] args) {
        assert System.currentTimeMillis() == 0L;
    }
}

거의 동일한 바이트 코드를 생성합니다.

public class Assert {
    static final boolean $assertionsDisabled =
        !Assert.class.desiredAssertionStatus();
    public static void main(String[] args) {
        if (!$assertionsDisabled) {
            if (System.currentTimeMillis() != 0L) {
                throw new AssertionError();
            }
        }
    }
}

Assert.class.desiredAssertionStatus()이다 true경우-ea명령 행에서 가 전달 그렇지 않으면 false입니다.

우리는 System.currentTimeMillis()그것이 최적화되지 않도록 보장하기 위해 사용 합니다 assert true;.

합성 필드는 Java Assert.class.desiredAssertionStatus()가로드시 한 번만 호출 하면 결과가 캐시되도록 생성됩니다. 참조 : "정적 합성"의 의미는 무엇입니까?

다음을 통해 확인할 수 있습니다.

javac Assert.java
javap -c -constants -private -verbose Assert.class

Oracle JDK 1.8.0_45에서는 합성 정적 필드가 생성되었습니다 ( "정적 합성"의 의미는 무엇입니까? 참조 ).

static final boolean $assertionsDisabled;
  descriptor: Z
  flags: ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC

정적 이니셜 라이저와 함께 :

 0: ldc           #6                  // class Assert
 2: invokevirtual #7                  // Method java/lang Class.desiredAssertionStatus:()Z
 5: ifne          12
 8: iconst_1
 9: goto          13
12: iconst_0
13: putstatic     #2                  // Field $assertionsDisabled:Z
16: return

주요 방법은 다음과 같습니다.

 0: getstatic     #2                  // Field $assertionsDisabled:Z
 3: ifne          22
 6: invokestatic  #3                  // Method java/lang/System.currentTimeMillis:()J
 9: lconst_0
10: lcmp
11: ifeq          22
14: new           #4                  // class java/lang/AssertionError
17: dup
18: invokespecial #5                  // Method java/lang/AssertionError."<init>":()V
21: athrow
22: return

우리는 결론 :

  • 바이트 코드 레벨 지원은 없습니다 assert: 그것은 Java 언어 개념입니다.
  • assert명령 줄에서 -Pcom.me.assert=true대체 할 시스템 속성과 꽤 잘 에뮬레이션 할 수 있습니다 .-eathrow new AssertionError()

2
따라서이 catch (Throwable t)조항은 어설 션 위반도 포착 할 수 있습니까? 저에게는 주장의 본문이 시간이 많이 걸리는 경우로만 그 유용성을 제한합니다.
Evgeni Sergeev

1
왜 그것이 어설 션의 유용성을 제한하는지 잘 모르겠습니다. 아주 드문 경우를 제외하고는 Throwable을 잡아서는 안됩니다. Throwable을 잡아야하지만 어설 션을 잡아 내지 않으려면 AssertionError첫 번째를 잡아서 다시 던질 수 있습니다.
MiguelMunoz

7

스택 클래스의 실제 예 ( Java 기사의 어설 션 )

public int pop() {
   // precondition
   assert !isEmpty() : "Stack is empty";
   return stack[--num];
}

80
이것은 C에서 눈살을 찌푸리게 할 것이다. 단언은 결코 일어나지 않아야 할 것이다.-빈 스택을 띄우면 NoElementsException 또는 라인을 따라 무언가가 발생해야한다. Donal의 답변을 참조하십시오.
Konerak

4
동의한다. 이것은 공식 튜토리얼에서 가져온 것이지만 나쁜 예입니다.
DJClayworth


7
메모리 누수가있을 수 있습니다. stack [num] = null을 설정해야합니다. GC가 제대로 업무를 수행 할 수 있도록
H.Rabiee

3
개인 메서드에서는 클래스 또는 메서드의 오작동에 대한 예외가 발생하기 때문에 어설 션을 사용하는 것이 맞을 것이라고 생각합니다. 공용 메서드에서 외부 어딘가에서 호출하면 다른 코드에서 어떻게 사용하는지 알 수 없습니다. 실제로 isEmpty ()를 확인합니까? 당신은 모른다.
Vlasec

7

어설 션을 사용하면 코드의 결함을 감지 할 수 있습니다. 프로그램이 프로덕션 환경에있을 때는 그대로두고 테스트 및 디버깅을위한 어설 션을 설정할 수 있습니다.

그것이 사실임을 알 때 왜 무언가를 주장 하는가? 모든 것이 제대로 작동하는 경우에만 해당됩니다. 프로그램에 결함이있는 경우 실제로는 그렇지 않을 수 있습니다. 프로세스 초기에이를 감지하면 문제가 있음을 알 수 있습니다.

assert문은 옵션과 함께이 문장이 포함되어 String메시지를.

assert 문의 구문은 두 가지 형식이 있습니다.

assert boolean_expression;
assert boolean_expression: error_message;

어설 션을 사용해야하는 위치와 사용하지 않아야하는 위치를 결정하는 몇 가지 기본 규칙이 있습니다. 다음에 어설 션을 사용해야 합니다.

  1. 개인용 메소드의 입력 매개 변수 유효성 검증 공개 메소드에는 적합 하지 않습니다 . public잘못된 매개 변수를 전달하면 메서드에서 정기적 인 예외가 발생합니다.

  2. 거의 확실하게 사실의 유효성을 보장하기 위해 프로그램의 어느 곳에서나.

예를 들어 1 또는 2 만 될 것이라고 확신하는 경우 다음과 같이 어설 션을 사용할 수 있습니다.

...
if (i == 1)    {
    ...
}
else if (i == 2)    {
    ...
} else {
    assert false : "cannot happen. i is " + i;
}
...
  1. 메소드 종료시 사후 조건 검증. 즉, 비즈니스 로직을 실행 한 후 어설 션을 사용하여 변수 또는 결과의 내부 상태가 예상 한 것과 일치하는지 확인할 수 있습니다. 예를 들어, 소켓이나 파일을 여는 메소드는 마지막에 어설 션을 사용하여 소켓이나 파일이 실제로 열려 있는지 확인할 수 있습니다.

어설 션 다음에 사용 해서는 안됩니다 .

  1. 공개 메소드의 입력 매개 변수 유효성 검증 어설 션이 항상 실행되는 것은 아니므로 규칙적인 예외 메커니즘을 사용해야합니다.

  2. 사용자가 입력 한 것에 대한 제약 조건을 검증합니다. 같은 상기와.

  3. 부작용에 사용해서는 안됩니다.

예를 들어, 여기에서는 어설 션이 doSomething()메소드 호출의 부작용으로 사용되기 때문에 이것은 적절하게 사용되지 않습니다 .

public boolean doSomething() {
...    
}
public void someMethod() {       
assert doSomething(); 
}

이것이 정당화 될 수있는 유일한 경우는 코드에서 어설 션이 활성화되어 있는지 여부를 찾으려고 할 때입니다.   

boolean enabled = false;    
assert enabled = true;    
if (enabled) {
    System.out.println("Assertions are enabled");
} else {
    System.out.println("Assertions are disabled");
}

5

여기에 제공된 모든 훌륭한 답변 외에도 공식 Java SE 7 프로그래밍 안내서에는 사용에 대한 간결한 매뉴얼이 있습니다 assert. 어설 션을 사용하는 것이 좋은 (그리고 중요한 것은 나쁜) 아이디어와 예외를 던지는 것과 어떻게 다른지에 대한 몇 가지 예를 들었습니다.

링크


1
동의한다. 이 기사에는 많은 훌륭한 예가 있습니다. 나는 특히 객체가 잠금을 보유 할 때만 메소드가 호출되도록하는 것을 좋아했습니다.
MiguelMunoz

4

Assert는 개발시 매우 유용합니다. 뭔가 할 수 없을 때 사용코드가 올바르게 작동하면 문제가 발생 않을 합니다. 사용하기 쉽고 코드에서 그대로 유지 될 수 있습니다. 실제로는 사용되지 않기 때문입니다.

실생활에서 상태가 발생할 가능성이 있으면 처리해야합니다.

나는 그것을 좋아하지만 Eclipse / Android / ADT에서 그것을 켜는 방법을 모른다. 디버깅 할 때도 꺼져있는 것 같습니다. (이 스레드는 있지만 ADT 실행 구성에 나타나지 않는 'Java vm'을 나타냅니다).


1
Eclipse IDE에서 어설 션을 활성화하려면 tutoringcenter.cs.usfca.edu/resources/…
Ayaz Pasha

Android에서 어설 션을 설정하는 방법이 없다고 생각합니다. 이것은 매우 실망입니다.
MiguelMunoz

3

다음은 Hibernate / SQL 프로젝트를 위해 서버에 작성한 주장이다. 엔티티 bean에는 isActive 및 isDefault라는 두 개의 유효 부울 특성이 있습니다. 각각의 값은 "Y"또는 "N"또는 null 일 수 있으며 "N"으로 처리되었습니다. 브라우저 클라이언트가이 세 값으로 제한되도록하고 싶습니다. 따라서이 두 속성에 대한 설정자 에서이 어설 션을 추가했습니다.

assert new HashSet<String>(Arrays.asList("Y", "N", null)).contains(value) : value;

다음을 주목하십시오.

  1. 이 주장은 개발 단계에만 해당됩니다. 고객이 잘못된 값을 보내면 생산에 도달하기 훨씬 전에 조기에 파악하여 수정합니다. 어설 션은 조기에 파악할 수있는 결함에 대한 것입니다.

  2. 이 주장은 느리고 비효율적입니다. 괜찮아요. 주장은 느려질 수 있습니다. 우리는 개발 전용 도구이기 때문에 신경 쓰지 않습니다. 어설 션이 비활성화되므로 프로덕션 코드 속도가 느려지지 않습니다. (이 시점에서 약간의 의견 차이가 있으며, 나중에 설명하겠습니다.) 그러면 다음 단계로 넘어갑니다.

  3. 이 주장은 부작용이 없습니다. 수정 불가능한 정적 최종 세트에 대해 내 가치를 테스트 할 수 있었지만 해당 세트는 결코 사용되지 않는 프로덕션 환경에서 유지되었습니다.

  4. 이 어설 션은 클라이언트가 올바르게 작동하는지 확인하기 위해 존재합니다. 따라서 프로덕션에 도달 할 때까지 클라이언트가 제대로 작동하는지 확인하여 어설 션을 안전하게 해제 할 수 있습니다.

  5. 어떤 사람들은 이것을 묻습니다. 어설 션이 프로덕션에 필요하지 않은 경우, 완료했을 때 어설 션을 꺼내지 않겠습니까? 다음 버전에서 작업을 시작할 때 여전히 필요하기 때문입니다.

어떤 사람들은 모든 버그가 사라 졌다고 확신 할 수 없기 때문에 어설 션을 사용해서는 안된다고 주장하면서 프로덕션 환경에서도 버그를 유지해야합니다. 따라서 assert 문을 사용하는 데는 아무런 소용이 없습니다. assert의 유일한 장점은 그것들을 끌 수 있다는 것입니다. 따라서,이 생각에 따르면, 당신은 (거의) 주장을 절대 사용해서는 안됩니다. 동의하지 않습니다. 테스트가 프로덕션 환경에 속하는 경우 어설 션을 사용하지 않아야합니다. 그러나이 테스트는하지 않습니다 프로덕션에 속하지 . 이것은 생산에 닿지 않을 수있는 버그를 잡기위한 것이므로 완료되면 안전하게 해제 될 수 있습니다.

BTW, 나는 다음과 같이 쓸 수있었습니다.

assert value == null || value.equals("Y") || value.equals("N") : value;

이것은 세 개의 값에만 유효하지만 가능한 값의 수가 커지면 HashSet 버전이 더 편리해집니다. 효율성에 대한 요점을 밝히기 위해 HashSet 버전을 선택했습니다.


이러한 작은 a 를 사용하면에 HashSet비해 어떤 속도 이점이 있는지 의심 합니다 ArrayList. 또한 세트 및 목록 작성이 조회 시간을 지배합니다. 상수를 사용할 때 괜찮습니다. 모든 것이 +1입니다.
maaartinus

모두 사실입니다. 어설 션이 느리다는 내 요지를 설명하기 위해이 비효율적 인 방법을 사용했습니다. 이것은 더 효율적으로 만들 수 있지만 할 수없는 다른 것들이 있습니다. "고체 코드 작성"이라는 훌륭한 책에서 Steve Maguire는 Microsoft Excel에서 변경해서는 안되는 셀을 건너 뛴 새로운 증분 업데이트 코드를 테스트 할 것을 주장합니다. 사용자가 변경을 할 때마다 어설 션은 전체 스프레드 시트를 다시 계산하여 결과가 증분 업데이트 기능과 일치하는지 확인합니다. 실제로 디버그 버전이 느려지지만 모든 버그를 일찍 발견했습니다.
MiguelMunoz

완전히 동의했습니다. 어설 션은 일종의 테스트입니다. 일반 테스트보다 다재다능하지는 않지만 개인 메서드를 다룰 수 있으며 작성 비용이 훨씬 저렴합니다. 더 많이 사용하도록 노력하겠습니다.
maaartinus

2

어설 션은 기본적으로 응용 프로그램을 디버깅하는 데 사용되거나 일부 응용 프로그램에서 응용 프로그램의 유효성을 검사하기 위해 예외 처리를 대체하는 데 사용됩니다.

어설 션은 런타임에 작동합니다. 간단한 예를 들어, 즉 매우 간단 전체 개념을 설명 할 수 여기에있다 - 어설 자바에서 할 키워드 무엇? (WikiAnswers).


2

어설 션은 기본적으로 비활성화되어 있습니다. 이를 가능하게하려면 -ea옵션으로 프로그램을 실행해야합니다 (과립도는 다를 수 있음). 예를 들면 다음과 같습니다 java -ea AssertionsDemo.

어설 션 사용에는 두 가지 형식이 있습니다.

  1. 단순 : 예. assert 1==2; // This will raise an AssertionError.
  2. 보다 나은: assert 1==2: "no way.. 1 is not equal to 2"; 주어진 메시지가 표시된 상태에서 AssertionError가 발생하여 더 좋습니다. 실제 구문은 assert expr1:expr2expr2가 값을 반환하는식이 될 수있는 곳 이지만 메시지를 인쇄하는 데 더 자주 사용했습니다.

1

요약하자면 (그리고 이것은 Java뿐만 아니라 많은 언어에서 사실입니다) :

"assert"는 주로 디버깅 프로세스 중에 소프트웨어 개발자가 디버깅 보조 도구로 사용합니다. 어설 션 메시지가 나타나지 않아야합니다. 많은 언어들이 "프로덕션"코드 생성에 사용하기 위해 모든 "어설 션"이 무시되도록하는 컴파일 타임 옵션을 제공합니다.

"예외"는 논리 오류를 나타내는 지 여부에 관계없이 모든 종류의 오류 조건을 처리 할 수있는 편리한 방법입니다. 계속할 수없는 오류 조건이 발생하면 단순히 "공중으로 던질 수 있습니다. "당신이 어디에 있든 다른 누군가가 그들을"잡을 "준비가되기를 기대합니다. 제어는 예외를 발생시킨 코드에서 바로 포수 미트까지 한 단계로 전달됩니다. 포수는 발생한 호출의 전체 역 추적을 볼 수 있습니다.

또한, 해당 서브 루틴의 호출자는 서브 루틴이 성공했는지 여부를 확인할 필요가 없습니다. "지금 우리가 여기 있다면 성공했을 것입니다. 그렇지 않으면 예외가 발생하여 지금 여기에 있지 않을 것입니다!" 이 간단한 전략을 통해 코드 디자인 및 디버깅이 훨씬 쉬워집니다.

예외는 치명적 오류 조건이 "정규에 대한 예외"인 상황을 편리하게 허용합니다. 그리고 그것들이 "규칙에 대한 예외 ... "플라이볼! "인 코드 경로에 의해 처리되기 위해서


1

어설 션은 해제 될 수있는 검사입니다. 그들은 거의 사용되지 않습니다. 왜?

  • 제어 할 수없는 공용 메소드 인수를 확인하는 데 사용해서는 안됩니다.
  • result != null이러한 검사는 매우 빠르며 저장할 것이 거의 없으므로 간단한 검사에는 사용해서는 안됩니다 .

그래서 무엇이 남았습니까? 실제로 예상되는 조건에 대한 비싼 검사 . RB-tree와 같은 데이터 구조의 변형이 좋은 예입니다. 실제로 JDK8에는에 대한 몇 가지 의미있는 주장이 있습니다.ConcurrentHashMapTreeNodes

  • 실행 시간을 쉽게 지배 할 수 있으므로 프로덕션 환경에서 스위치를 켜고 싶지 않습니다.
  • 테스트 중에이를 켜거나 끌 수 있습니다.
  • 코드 작업을 할 때 반드시 켜고 싶습니다.

때로는 수표가 실제로 비싸지는 않지만 동시에 확실하게 통과 할 것입니다. 내 코드에는 예를 들어,

assert Sets.newHashSet(userIds).size() == userIds.size();

내가 방금 만든 목록에 고유 한 요소가 있다고 확신하지만 문서화하고 다시 확인하고 싶었습니다.


0

기본적으로 "true asert"는 통과하고 "false ast"는 실패합니다. 이것이 어떻게 작동하는지 봅시다 :

public static void main(String[] args)
{
    String s1 = "Hello";
    assert checkInteger(s1);
}

private static boolean checkInteger(String s)
{
    try {
        Integer.parseInt(s);
        return true;
    }
    catch(Exception e)
    {
        return false;
    }
}

-8

assert키워드입니다. JDK 1.4에 도입되었습니다. 는 두 종류 assert

  1. 매우 간단한 assert진술
  2. 간단한 assert진술.

기본적으로 모든 assert명령문은 실행되지 않습니다. 는 IF assert문이 거짓 수신 후 자동으로 어설 션 오류가 발생합니다.


1
이 질문의 목적은 어떤 실생활 예제를 제공하지 않습니다
rubenafo

1
amazon.com/Programmer-Study-1Z0-803-1Z0-804-Certification/dp/… 에서 붙여 넣기를 복사 했습니까 ?
Koray Tugay
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.