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

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



답변

주장 (의 방법에 의한 어설 션 키워드) 자바 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;
}


답변

원자력 발전소를 제어하는 ​​프로그램을 작성한다고 가정 해 봅시다. 따라서 코드는, 심지어 가장 사소한 실수가 치명적인 결과를 가질 수있는 매우 분명하다 이어야 버그 프리 (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 예외도 비슷한 개념이지만 모든 것을 확인하지 못합니다. 더 많은 검사를 원한다면 (어택 속도로) 어설 션을 사용해야합니다. 그렇게하면 코드가 부풀어 질 수 있지만 결국 개발 시간이 놀랍도록 짧은 시간에 제품을 제공 할 수 있습니다 (버그를 조기에 해결할수록 비용은 줄어 듭니다). 또한 코드 내부에 버그가 있으면 감지합니다. 버그 제거 방법은 없으며 나중에 문제를 일으킬 수 있습니다.

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


답변

어설 션은 코드에서 버그를 포착하는 개발 단계 도구입니다. 그것들은 쉽게 제거되도록 설계되었으므로 프로덕션 코드에는 존재하지 않습니다. 따라서 어설 션은 고객에게 제공하는 “솔루션”의 일부가 아닙니다. 그들은 가정이 올바른지 확인하기 위해 내부 검사입니다. 가장 일반적인 예는 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()개발에서 버그가있는 방법을 잡을 수 있기 때문에 행복 합니다.

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


답변

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의 많은 보안 문제에 대해 들었습니다. 프로덕션 코드에 기본 확인 및 어설 션을 포함하지 않기로했습니다. 가격을 지불하십시오.)


답변

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

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;
}


답변

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

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


답변

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()