Java에서 assert 사용하기

프로그래밍/자바 2018. 5. 4. 22:52
반응형

Java에서 단언문 assert는 JDK 1.4 부터 지원합니다. 객체가 아니고 예약어 입니다.


사용법은 두 가지 형식이 있는데, 다음과 같습니다.


assert expression1;

assert expression1: expression2;


첫 번째는 인자로 boolean으로 평가되는 표현식 또는 값을 받아서 참이면 그냥 지나가고, 거짓이면 AssertionError 예외가 발생합니다. 두 번째는 표현식1이 거짓인 경우 두번째 표현식의 값이 예외 메세지로 보여지게 됩니다.


Assertion은 디버깅 용도로 사용되어서 JVM 기본 설정으로 프로그램을 실행하게 되면 assert문은 모두 실행에서 제외 됩니다. assert가 동작하게 하려면 실행시 -ea 옵션을 사용해서 실행합니다.


java -ea 클래스명


이클립스에서 assert가 동작 하도록 하려면  Run Configurations 에서 VM arguments에 -ea 를 추가해서 실행하면 됩니다.



Java 실행 옵션에서 assertion을 활성화 하는 여러가지 방법들이 있습니다.


-ea Main : 시스템 클래스를 제외한 모든 클래스 활성화

-ea:<class name> Main : 지정한 클래스만 활성화

-ea:... Main : 기본 패키지를 활성화(모든 클래스)

-ea:<package name>... Main : 지정된 패키지를 활성화(모든 클래스와 하위 패키지 포함)

-ea -da:<class name> Main : 지정된 클래스를 제외한 모두 활성화


Assertion을 사용하면서 주의해야할 점이 있습니다. Assertion이 최종 실행환경에서는 빠져버리고 실행되지 않기 때문에 업무 로직에 해당하는 체크사항을 Assertion을 사용해서 체크하게 되면 실행시에는 체크가 되지 않아서 문제가 발생할 수 있습니다. 개발환경과 실행환경의 프로그램이 달라지는것 입니다.


assert가 디버깅시에 사용될 때 단순히 로그를 찍어보는 정도가 아니라 특정 조건이 맞지 않으면 예외를 발생시키는 방식이라 예외처리와 혼동되어 사용되어질 가능성이 있습니다. 앞에서 말한 것과 같이 예외처리와 Assertion을 구분하지 않으면 실제 실행환경에서의 프로그램 실행에 문제가 발생할 수 있습니다.


입력값의 검증이나 업무 로직의 체크는 예외처리를 사용해서 프로그램 해야 합니다. 예외가 발생했을때 상황을 로깅하고, 사용자에게 알려서 사용자가 대처할 수 있도록 해줘야 합니다. 그럼, Assertion은 언제 사용하는 것일까요? 꼭 쓰는 것이 좋을 까요?


Assertion은 코드가 실행될 때 반드시 어떤 값일지 확신하는 값, 범위 또는 확실한 클래스의 상태 등을 체크하여 프로그램의 신뢰성을 높이기 위해서 사용된다고 합니다. 일반적인 사용처는 보통 다음과 같은 곳에 사용되어 질 수 있습니다.


- 사전 조건(pre-conditions) : 메소드를 호출할때 지켜야 하는 요구사항을 체크합니다.(private 메소드에만 사용합니다.)

- 사후 조건(post-conditions) : 코드를 수행할 결과 확신하는 값을 체크합니다.

- 클래스 불변성(class invariants) : 객체가 반드시 특정 상태에 있다고 확신하는 것을 검증

- public 메소드의 인자에 대해서는 Assertion을 사용하지 않습니다.(누가 어떤 값을 넣을지 확신하지 못함.)



사용예를 보겠습니다. 실용적인 예는 아니지만 설명을 위해 좀 억지로 만들어 보았습니다. 예제 클래스는 1씩 값을 증/감할 수 있습니다. 카운트 값을 항상 0보다 크거나 같게 유지됩니다.


package com.tistory.offbyone;


public class Counter {


  private int count;


  public Counter(final int initialCount) {

    //assert initialCount >= 0;

    if(initialCount < 0) {

      throw new IllegalArgumentException("초기값은 0이상이어야 합니다: " + initialCount);

    }

    this.count = initialCount;

  }


  private void changeCount(final int val) {

    // 사후 조건 체크용

    final int preCount = this.count;


    // 사전 조건 체크

    assert val == 1 || val == -1;


    if((this.count + val) < 0) {

      this.count = 0;

    } else {

      this.count += val;

    }


    // 사후 조건 체크

    assert (val == 1 && (this.count > preCount)) || (val == -1 && (this.count <= preCount));


    // 클래스 불변성  체크

    assert this.count >= 0 : this.count;

  }


  public void incCount() {

    changeCount(1);

  }


  public void decCount() {

    changeCount(-1);

  }


  public int getCount() {

    return this.count;

  }


  public static void main(String[] args) {

    Counter counter = new Counter(0);

    counter.incCount();

    System.out.println("inc counter : " + counter.getCount());

    counter.decCount();

    System.out.println("dec counter : " + counter.getCount());

    counter.decCount();

    System.out.println("dec counter : " + counter.getCount());

  }

}


- 생성자에서 초기 카운터 값을 받을 수 있는데, 이 초기값은 Assertion 대상이 아닙니다. 예외로 처리합니다.

- private 메소드인 changeCount에서 사전 조건으로 인자는 반드시 1 또는 -1 임을 체크합니다.

- 사후 조건 체크는 preCount에 이전값을 보관해 뒀다가 체크하는 방식으로 체크했습니다. preCount는 Assertion용도로만 사용되어지는데 항상 만들어지므로 별로 좋은 방법은 아니지만 사후 조건 체크를 보여주기 위해서 사용했습니다.

- 클래스 불변성 체크는 Counter가 가지는 count는 항상 0이상 임을 체크합니다.


Assertion의 사용은 대충 이런 느낌인데, 코드의 실행 시점에서 확신하는 값을 체크하는데 사용되어 집니다. 이 개념은 계약에 의한 디자인(Design by contract)이나 방어 프로그래밍(Defensive programming)과도 관련되어 집니다.

반응형

댓글을 달아 주세요