Home [Effective Java] Item39. 명명 패턴보다 애너테이션을 사용하라!
Post
Cancel

[Effective Java] Item39. 명명 패턴보다 애너테이션을 사용하라!

Item39. 명명 패턴보다 애너테이션을 사용하라!

명명패턴

  • 전통적으로 도구나 프레임워크가 특별히 다뤄야 할 프로그램 요소에는 딱 구분되는 명명 패턴을 적용해왔다.

명명패턴의 단점

  • 오타가 나면 안된다.
    • JUnit3 버전까지 테스트 메서드를 test로 시작하게끔 명명했는데, 실수로 test를 잘못 적으면 테스트를 건너뛴다.
  • 올바른 프로그램 요소에서만 사용되리라 보증할 방법이 없다.
    • test 명명은 메서드에 적용해야 하는데, 클래스에 이러한 이름 명명을 적용해놓고 테스트가 진행되기를 기대할 수 있다.
      • 실제로 테스트 안된다.
  • 프로그램 요소를 매개변수로 전달할 마땅한 방법이 없다.
    • 특정 예외를 던져야만 성공하는 케이스가 있을 때, 기대하는 예외 타입을 테스트에 매개변수로 전달해야 하는 상황이다.
      • 명명 패턴으로 이를 전달하는 데에는 무리가 있다.

애너테이션

  • 명명 패턴의 모든 문제를 해결해주는 멋진 개념이다.
  • JUnit도 버전 4부터 도입했다.

선언

  • 애너테이션 선언에 다는 애너테이션인 메타애너테이션이 필요하다.

    • @Retention : @Test가 런타임에도 유지되어야 한다는 표시

    • @Target : @Test가 반드시 메서드 선언에서만 사용돼야 한다고 알려줌.

@Test 애너테이션 적용 예

1
2
3
4
5
6
7
public class Sample {
    @Test public static void m1(){} // 성공

    public static void m2(){} // 수행 안 됨.

    @Test public static void m3() {throw new RuntimeException("fail");} // 실패 - 약속 안 된 예외 던지기
}
  • @Test 애너테이션이 Sample 클래스의 의미에 직접적인 영향을 주지는 않는다.
  • 그저 이 애너테이션에 관심 있는 프로그램에게 추가 정보를 제공한다. (특별한 처리의 기회를 준다.)

마커 애너테이션을 처리하는 프로그램

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class RunTests {
    public static void main(String[] args) throws Exception{
        int tests = 0;
        int passed = 0;
        Class<?> testClass = Class.forName(args[0]);
        for (Method m : testClass.getDeclaredMethods()) {
            if (m.isAnnotationPresent(Test.class)) {
                tests++;
                try {
                    m.invoke(null);
                    passed++;
                } catch (InvocationTargetException wrappedExc) {
                    Throwable exc = wrappedExc.getCause();
                    System.out.println(m + " 실패 : " + exc);
                } catch (Exception e) {
                    System.out.println("잘못 사용한 @Test: " + m);
                }
            }
        }
        System.out.printf("성공: %d, 실패: %d%n", passed, tests - passed);
    }
}
  • if (m.isAnnotationPresent(Test.class)) : 테스트 애너테이션이 선언된 메서드만 호출한다.
  • 테스트 메서드가 예외를 던지면 리플렉션 매커니즘이 InvocationTargetException로 다시 감싸서 던진다.
    • InvocationTargetException를 잡아 원래 예외에 담긴 정보를 출력한다.
    • InvocationTargetException 외의 예외 발생 시, @Test 애너테이션을 잘못 사용한 것이다.
      • 인스턴스 메서드, 매개변수가 있는 메서드, 호출할 수 없는 메서드 등에 단 경우

특정 예외를 던져야만 성공하는 예제

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
    Class<? extends Throwable> value();
}
  • 매개변수 하나를 받는 애너테이션 타입 예제다.
  • 매개변수의 타입은 Class<? extends Throwable>이다. Throwable을 확장한 클래스의 Class 객체라는 의미다.
    • 모든 예외와 오류 타입을 다 수용한다.

@ExceptionTest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Sample2 {
    @ExceptionTest(ArithmeticException.class)
    public static void m1() { // 성공
        int i = 1 / 0; // ArithmeticException를 던진다.
    }

    @ExceptionTest(ArithmeticException.class)
    public static void m2() { // 실패(다른 예외)
        int[] arr = new int[0];
        arr[1] = 1; // IndexOutOfRange
    }

    @ExceptionTest(ArithmeticException.class)
    public static void m3() {} // 실패(에외 발생 안함)
}
  • @ExceptionTest를 사용하는 예제다.

결론

  • 애너테이션 사용을 통해 코드의 가독성 개선을 보일 수 있다.
    • 그 과정에서 애너테이션을 선언하고 이를 처리하는 부분은 코드 양이 늘어난다. 특히 처리 코드의 경우 복잡해져 오류가 날 수 있다.
  • 애너테이션으로 할 수 있는 일을 명명 패턴으로 처리할 이유는 없다.
  • 자바 프로그래머라면 예외 없이 자바가 제공하는 애너테이션 타입들은 사용해야 한다.
    • 도구 제작자를 제외하고는, 일반 프로그래며가 애너테이션 타입을 직접 정의할 일은 거의 없다.
This post is licensed under younghwani by the author.

[Effective Java] Item38. 확장할 수 있는 열거타입이 필요하면 인터페이스를 사용하라!

[Effective Java] Item40. @Override 애너테이션을 일관되게 사용하라!

Comments powered by Disqus.