Java에서 어설 션을 사용하는 방법

런타임에 올바르게 작동하는 프로그램을 작성하는 것은 어려울 수 있습니다. 이는 실행될 때 코드가 어떻게 동작할지에 대한 가정이 종종 잘못되기 때문입니다. Java의 어설 션 기능을 사용하는 것은 프로그래밍 논리가 올바른지 확인하는 한 가지 방법입니다.

이 튜토리얼에서는 Java 어설 션을 소개합니다. 먼저 어설 션이 무엇인지, 코드에서이를 지정하고 사용하는 방법을 배웁니다. 다음으로 어설 션을 사용하여 전제 조건과 사후 조건을 적용하는 방법을 알아 봅니다. 마지막으로, 어설 션을 예외와 비교하고 코드에서 두 가지가 모두 필요한 이유를 알아 봅니다.

다운로드 코드 받기이 튜토리얼의 예제를위한 소스 코드를 다운로드하십시오. JavaWorld를 위해 Jeff Friesen이 만들었습니다.

Java 어설 션이란 무엇입니까?

JDK 1.4 이전에 개발자는 프로그램 정확성에 대한 가정을 문서화하기 위해 종종 주석을 사용했습니다. 그러나 주석은 가정을 테스트하고 디버깅하는 메커니즘으로 쓸모가 없습니다. 컴파일러는 주석을 무시하므로 버그 감지를 위해 주석을 사용할 수 없습니다. 또한 개발자는 코드를 변경할 때 주석을 업데이트하지 않는 경우가 많습니다.  

JDK 1.4에서는 코드에 대한 가정을 테스트하고 디버깅하기위한 새로운 메커니즘으로 가정이 도입되었습니다. 본질적으로 어설 션  은 프로그램 테스트를 위해 활성화했다고 가정하고 런타임에 실행되는 컴파일 가능한 엔터티입니다. 버그가 발생한 위치를 알려주는 어설 션을 프로그래밍하여 실패한 프로그램을 디버깅하는 데 소요되는 시간을 크게 줄일 수 있습니다.

어설 션은 참 값에 대한 조건 (부울 식)을 테스트 하고 이러한 조건이 거짓 인 경우 개발자에게 알림으로써 프로그램을 올바로 렌더링하는 요구 사항을 코드화하는 데 사용됩니다 . 어설 션을 사용하면 코드의 정확성에 대한 확신을 크게 높일 수 있습니다.

Java로 어설 션을 작성하는 방법

어설 션은 assert문과 java.lang.AssertionError클래스 를 통해 구현됩니다 . 이 문은 키워드로 시작 assert하고 부울 식으로 계속됩니다. 다음과 같이 구문 적으로 표현됩니다.

assert BooleanExpr ;

만약 BooleanExpr평가하여 true로 아무 일도 발생하지 않습니다 및 실행이 계속됩니다. 그러나 표현식이 false로 평가되면 AssertionError목록 1에 표시된 대로 인스턴스화되고 throw됩니다.

목록 1 :AssertDemo.java (버전 1)

public class AssertDemo {public static void main (String [] args) {int x = -1; 주장 x> = 0; }}

목록 1의 주장은 변수 x가 0보다 크거나 같은 값을 포함 한다는 개발자의 믿음을 나타냅니다. 그러나 이것은 분명히 사실이 아닙니다. assertA의 문장의 실행 결과가 발생합니다 AssertionError.

목록 1 ( javac AssertDemo.java)을 컴파일 하고 어설 션을 활성화 한 상태 ( java -ea AssertDemo)를 실행합니다 . 다음 출력을 관찰해야합니다.

AssertDemo.main (AssertDemo.java:6)의 스레드 "main"java.lang.AssertionError 예외

이 메시지는를 AssertionError던진 원인을 식별하지 못한다는 점에서 다소 모호 합니다. 보다 유익한 메시지를 원하시면 assert아래 표현 된 문장을 사용하십시오 .

assert BooleanExpr : expr ;

여기 expr에는 값을 반환 할 수있는 모든 표현식 (메서드 호출 포함)이 있습니다 void. 반환 유형이 있는 메서드는 호출 할 수 없습니다 . 유용한 표현식은 목록 2에 설명 된대로 실패 이유를 설명하는 문자열 리터럴입니다.

목록 2 :AssertDemo.java (버전 2)

public class AssertDemo {public static void main (String [] args) {int x = -1; assert x> = 0 : "x <0"; }}

Listing 2 ( javac AssertDemo.java)를 컴파일 하고 어설 션을 활성화 한 상태 ( java -ea AssertDemo)를 실행합니다 . 이번에는 던진 이유를 포함하여 다음과 같이 약간 확장 된 출력을 관찰해야합니다 AssertionError.

스레드 "main"java.lang.AssertionError : x <0 at AssertDemo.main (AssertDemo.java:6) 예외

두 예 모두 (어설 션 활성화) 옵션 AssertDemo없이 실행 하면 -ea출력이 발생하지 않습니다. 어설 션이 활성화되지 않은 경우 클래스 파일에 여전히 존재하더라도 실행되지 않습니다.

전제 조건 및 사후 조건

어설 션은 다양한 전제 조건과 사후 조건이 위반되지 않았는지 확인하여 프로그램의 가정을 테스트하고 위반이 발생하면 개발자에게 경고합니다.

  • 전제 조건이 몇 가지 코드 시퀀스의 실행 전에 true로 평가해야하는 조건이다. 전제 조건은 발신자가 수신자와의 계약을 유지하도록합니다.
  • 사후 조건은 몇 가지 코드 시퀀스의 실행 후 true로 평가해야하는 조건이다. 사후 조건은 수신자가 발신자와의 계약을 유지하도록합니다.

전제 조건

You can enforce preconditions on public constructors and methods by making explicit checks and throwing exceptions when necessary. For private helper methods, you can enforce preconditions by specifying assertions. Consider Listing 3.

Listing 3:AssertDemo.java (version 3)

import java.io.FileInputStream; import java.io.InputStream; import java.io.IOException; class PNG { /** * Create a PNG instance, read specified PNG file, and decode * it into suitable structures. * * @param filespec path and name of PNG file to read * * @throws NullPointerException when filespec is * null */ PNG(String filespec) throws IOException { // Enforce preconditions in non-private constructors and // methods. if (filespec == null) throw new NullPointerException("filespec is null"); try (FileInputStream fis = new FileInputStream(filespec)) { readHeader(fis); } } private void readHeader(InputStream is) throws IOException { // Confirm that precondition is satisfied in private // helper methods. assert is != null : "null passed to is"; } } public class AssertDemo { public static void main(String[] args) throws IOException { PNG png = new PNG((args.length == 0) ? null : args[0]); } }

The PNG class in Listing 3 is the minimal beginning of a library for reading and decoding PNG (portable network graphics) image files. The constructor explicitly compares filespec with null, throwing NullPointerException when this parameter contains null. The point is to enforce the precondition that filespec not contain null.

It’s not appropriate to specify assert filespec != null; because the precondition mentioned in the constructor’s Javadoc would not (technically) be honored when assertions were disabled. (In fact, it would be honored because FileInputStream() would throw NullPointerException, but you shouldn’t depend on undocumented behavior.)

However, assert is appropriate in the context of the private readHeader() helper method, which will be completed eventually to read and decode a PNG file’s 8-byte header. The precondition that is always be passed a non-null value will always hold.

Postconditions

Postconditions are typically specified via assertions, regardless of whether or not the method (or constructor) is public. Consider Listing 4.

Listing 4:AssertDemo.java (version 4)

public class AssertDemo { public static void main(String[] args) { int[] array = { 20, 91, -6, 16, 0, 7, 51, 42, 3, 1 }; sort(array); for (int element: array) System.out.printf("%d ", element); System.out.println(); } private static boolean isSorted(int[] x) { for (int i = 0; i  x[i + 1]) return false; return true; } private static void sort(int[] x) { int j, a; // For all integer values except the leftmost value ... for (int i = 1; i  0 && x[j - 1] > a) { // Shift left value -- x[j - 1] -- one position to its right -- // x[j]. x[j] = x[j - 1]; // Update insert position to shifted value's original position // (one position to the left). j--; } // Insert a at insert position (which is either the initial insert // position or the final insert position), where a is greater than // or equal to all values to its left. x[j] = a; } assert isSorted(x): "array not sorted"; } }

Listing 4 presents a sort() helper method that uses the insertion sort algorithm to sort an array of integer values. I’ve used assert to check the postcondition of x being sorted before sort() returns to its caller.

The example in Listing 4 demonstrates an important characteristic of assertions, which is that they’re typically expensive to execute. For this reason, assertions are usually disabled in production code. In Listing 4, isSorted() must scan through the entire array, which can be time-consuming in the case of a lengthy array.

Assertions vs. exceptions in Java

Developers use assertions to document logically impossible situations and detect errors in their programming logic. At runtime, an enabled assertion alerts a developer to a logic error. The developer refactors the source code to fix the logic error and then recompiles this code.

Developers use Java’s exception mechanism to respond to non-fatal (e.g., running out of memory) runtime errors, which may be caused by environmental factors, such as a file not existing, or by poorly written code, such as an attempt to divide by 0. An exception handler is often written to gracefully recover from the error so that the program can continue to run.

Assertions are no substitute for exceptions. Unlike exceptions, assertions don’t support error recovery (assertions typically halt program execution immediately — AssertionError isn’t meant to be caught); they are often disabled in production code; and they typically don’t display user-friendly error messages (although this isn’t an issue with assert). It’s important to know when to use exceptions rather than assertions.

When to use exceptions

Suppose you’ve written a sqrt() method that calculates the square root of its argument. In a non-complex number context, it’s impossible to take the square root of a negative number. Therefore, you use an assertion to fail the method if the argument is negative. Consider the following code fragment:

public double sqrt(double x) { assert x >= 0 : "x is negative"; // ... }

It’s inappropriate to use an assertion to validate an argument in this public method. An assertion is intended to detect errors in programming logic and not to safeguard a method from erroneous arguments. Besides, if assertions are disabled, there is no way to deal with the problem of a negative argument. It’s better to throw an exception, as follows:

public double sqrt(double x) { if (x < 0) throw new IllegalArgumentException("x is negative"); // ... }

The developer might choose to have the program handle the illegal argument exception, or simply propagate it out of the program where an error message is displayed by the tool that runs the program. Upon reading the error message, the developer can fix whatever code led to the exception.

어설 션과 오류 감지 논리 사이에 미묘한 차이가 있음을 알 수 있습니다. 어설 션 테스트 x >= 0는 오류 감지 논리가 테스트하는 반면 x < 0. 그 주장은 낙관적입니다. 우리는 그 주장이 괜찮다고 가정합니다. 반대로 오류 감지 논리는 비관적입니다. 우리는 인수가 옳지 않다고 가정합니다. 어설 션은 올바른 논리를 문서화하는 반면 예외는 잘못된 런타임 동작을 문서화합니다.

이 자습서에서는 올바른 프로그램 논리를 문서화하기 위해 어설 션을 사용하는 방법을 배웠습니다. 또한 어설 션이 예외를 대체하지 않는 이유를 배웠으며 예외를 사용하는 것이 더 효과적인 예를 보았습니다.

이 이야기, "자바에서 어설 션을 사용하는 방법"은 원래 JavaWorld에 의해 출판되었습니다.