-
아이템39. 명명 패턴보다 에너테이션을 사용하라JAVA/Effective java 2021. 4. 16. 23:26
명명 패턴에 단점
- 오타가 나면 안된다. JUnit 3을 예로 이름을 test로 시작하지 않으면 개발자는 이 테스트가 통과했다고 오해할 수 있다.
- 올바른 프로그램 요소에서만 사용되리라 보증할 방법이 없다.
- 프로그램 요소를 매개변수로 전달할 마땅한 방법이 없다. 특정 예외를 던져야만 성공하는 테스트가 있고 기대하는 예외 타입을 테스트에 매개변수로 전달해야 하는 상황이 그 예이다.
- 애너테이션은 이 모든 문제를 해결해주는 개념으로, Junit도 4부터 전면 도입하였다.
- 애너테이션의 동작 방식을 보여주는 직접 제작한 작은 테스트 프레임워크를 사용해보자.
- Test라는 이름의 애너테이션을 정의한다. 자동으로 수행되는 간단한 애너테이션으로, 예외가 발생하면 해당 테스트를 실패로 처리한다.
- @Test 애너테이션 타입 선언 자체도 두 가지 다른 애너테이션이 달려있다.
- @Retention과 @Target인데 애너테이션 선언에 다른 애너테이션을 메타애너테이션 이라 한다.
- 다음 코드는 실제 @Test 애너테이션을 적용한 모습이다.
- Sample 클래스는 정적 메서드가 7개고, 그 중 4개에 @Test를 달았다.
- m3와 m7는 예외를 던지고 m1 m5는 그렇지 않다. 또 m5는 인스턴스 메서드이므로 @Test를 잘못 사용한 경우다.
- 요약하면 총 4개의 테스트 메서드 중 1개는 성공, 2개는 실패, 1개는 잘못 사용됐으며 @Test를 붙이지 않은 나머지 4개의 메서드는 테스트 도구가 무시할 것이다.
- @Test 애너테이션이 Sample 클래스의 의미에 직접적인 영향을 주지는 않는다.
- 그저 애너테이션에 관심 있는 프로그램에게 추가 정보를 제공할 뿐이다.
- 즉 대상 코드의 의미는 그대로 둔 채 그 애너테이션에 관심 있는 도구에서 특별한 처리를 할 기회를 준다. 다음의 RunTests가 그 도구의 예다.
- 이 테스트 러너는 명령줄로부터 완전 정규화된 클래스 이름을 받아, 그 클래스에서 @Test 애너테이션이 달린 메서드를 차례로 호출한다.
- 테스트 메서드가 예외를 던지면 리플렉션 매커니즘이 InvocationTargetException으로 감싸서 다시 던진다.
- 그 외의 예외는 @Test 애너테이션을 잘못 사용했다는 뜻이다.
- 이제 특정 예외를 던져야만 성공하는 테스트를 지원하도록 해보자. 새로운 애너테이션 타입이 필요하다.
- 여기서 와일드카드 타입은 많은 의미를 담는다.
- Throwable을 확장한 클래스의 Class 객체라는 뜻이며, 따라서 모든 예외(와 오류) 타입을 다 수용한다.
- 다음은 이 애너테이션을 실제 활용하는 모습이다.
- @Test 애너테이션용 코드와 비슷해 보인다. 차이라면, 이 코드는 애너테이션 매개변수의 값을 추출하여 테스트 메서드가 올바른 예외를 던지는지 확인하는 데 사용한다.
- 형 변환 코드가 없으니 ClassCastException 걱정은 없다. 테스트 프로그램이 문제없이 컴파일되면 애너테이션 매개변수가 가리키는 예외가 올바른 타입이라는 뜻이다.
- 이 예외 테스트 예에서 한 걸음 더 들어가, 예외를 여러 개 명시하고 그중 하나가 발생하면 성공하게 만들 수도 있다.
- 애너테이션 매커니즘에는 이런 쓰임에 아주 유용한 기능이 기본으로 들어있다.
- @ExceptionTest 애너테이션의 매개변수 타입을 Class 객체의 배열로 수정해보자.
- 다음 메서드는 IndexOutOfBoundsException 이나 NullPointerException을 던질 수 있다.
- 이 새로운 @ExceptionTest를 지원하도록 테스트 러너를 수정한 모습이다.
- 자바 8에서는 여러 값을 받는 애너테이션을 다른 방식으로도 만들 수 있다.
- 배열 매개변수를 사용하는 대신 애너테이션에 @Repeatable 메타 애너테이션을 다는 방식이다.
- @Repeatable을 단 애너테이션은 하나의 프로그램 요소에 여러 번 달 수 있다.
- 주의할 점은 첫 번째 @Repeatable을 단 애너테이션을 반환하는 "컨테이너 애너테이션"을 하나 더 정의하고, @Repeatable에 이 컨테이너 애너테이션의 class 객체를 매개변수로 전달해야 한다.
- 또한 컨테이너 애너테이션은 내부 애너테이션 타입의 배열을 반환하는 value 메서드를 정의해야 한다.
- 마지막으로 컨테이너 애너테이션 타입에는 적절한 보존 정책(@Retention), 적용 대상(@Target)을 명시해야 한다.
- 그렇지 않으면 컴파일 되지 않는다.
- isAnnotationPresent로 컨테이너 애너테이션이 달렸는지 검사한다면 반복 가능 애너테이션을 한 번만 단 메서드를 무시하고 지나친다.
- 그래서 달려 있는 수와 상관없이 모두 검사하려면 둘을 따로따로 확인해야 한다.
- 다음은 RunTests 프로그램이 ExceptionTest의 반복 가능 버전을 사용하도록 수정한 모습이다.
- 반복 가능 애너테이션을 사용해 하나의 프로그램 요소에 같은 애너테이션을 여러 번 달 때의 코드 가독성을 높여보았다.
- 이 방식으로 여러분 코드의 가독성을 개선할 수 있다면 이 방식을 사용하도록 하자.
- 하지만 애너테이션을 선언하고 이를 처리하는 부분에서는 코드 양이 늘어나며, 특히 처리 코드가 복잡해져 오류가 날 가능성이 커짐을 명심하자.
정리
- 이번 테스트 프레임워크는 간단하지만 애너테이션이 명명 패턴보다 낫다는 점은 확실히 보여준다.
- 테스트는 애너테이션으로 할 수 있는 일 중 극히 일부이다. 만일 다른 프로그래머가 소스코드에 추가 정보를 제공하는 도구를 만드는 일을 한다면 적당한 애너테이션 타입도 함께 정의해 제공하자.
- 애너테이션으로 할 수 있는 일을 명명 패턴으로 처리할 이유는 없다.
'JAVA > Effective java' 카테고리의 다른 글
아이템41. 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라. (0) 2021.05.04 아이템 40. @Override 애너테이션을 일관되게 사용하라 (0) 2021.04.18 아이템 38. 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라 (0) 2021.04.14 아이템 37. ordinal 인덱싱 대신 EnumMap을 사용하라. (0) 2021.03.28 아이템36. 비트 필드 대신 EnumSet을 사용하라 (0) 2021.03.26