애노테이션(Annotation)이란 조건문(if)을 늘리는 것

alankang님이 쓰신 “OOP란 조건문(if)을 줄이는 것“이란 글을 읽다가 문득 든 생각이다. 그 글이랑 별 상관없는 생각이긴 하지만.

자바5+에서 애노테이션이 등장했을 때 대체로 환영하는 분위기였다. iBatis의 개발자처럼(누구더라..) 자바5+의 새로운 언어기능은 다 쓰레기다라고 분노한 사람도 있긴 하지만, 스프링의 설정은 죽어도 XML이면 충분하다고 꽤 오랜동안 고집을 부렸던 로드존슨 조차도 닷넷의 애노테이션을 보라면서 자바의 소스코드 애트리뷰트 도입의 필요성을 자주 이야기하곤 했을 정도로 애노테이션 자체에 대해서는 반기는 입장이었다. 1.x시절부터 SpringMVC를 이용해서 웹 프로그램을 만들때면 지금의 @RequestMapping과 같은 아파치 애트리뷰트를 이용한 소스코드 내의 URL매핑을 가장 먼저 적용한다고 한 것도 로드존슨이다.

XML과 외부설정에 지쳐서 하이버네이트(2.x)를 비롯해서 왠만한 설정은 XDoclet을 적용해왔던 나도 역시 대 환영이었다.

JPA의 애노테이션 설정이라든가 스프링의 @Transactional, @Required에 이은 2.5의 @Autowired류의 @DI등을 만났을 때, 또 @RequestMapping으로 한방에 URL매핑을 해버리는 @MVC를 사용했을 때의 만족감은 대단했다. JUnit 3.x의 TestCase 상속구조에 발이 묶여있던 스프링테스트가 @Test를 가진 JUnit4를 지원하는 TestContextFramework을 내놓으면서 심지어 도메인 클래스를 확장해서 테스트를 만들어도 된다는 놀라운 사실에 감탄했다.

하지만 애노테이션에 대한 환상은 금방 깨졌다.

애노테이션이 소스코드랑 결합되어있어 설정을 바꾸려고 해도 컴파일을 다시 해야 한다거나, XML처럼 구조적인 정보를 만들기도 어렵고 정보가 모여있지도 않고, 검증도 힘들다는 것 등등은 이미 예상했던 것들이니까 그려려니 했다.

사실 애노테이션을 적용하면서 놀라게 된 것은 “애노테이션 덕분에 POJO가 됐어요”라는 캐치프레이즈 뒤에 숨은 애노테이션에 대한 뒤치닥 거리 코드들이었다. 애노테이션을 단지 코멘트처럼 개발중에 코드를 읽기 위해서 사용할 수도 있고, 빌드타임중에 설정파일 대용으로 사용할 수도 있다. 하지만 대부분의 경우 애토네이션은 런타임시에 프레임워크 또는 애플리케이션 스스로 사용한다.

애노테이션 정보를 얻으려면 자바의 리플렉션 API를 이용해야 한다. 애노테이션 자체가 단순 텍스트가 아니라  @Interface로 정의되어 컴파일되는 일종의 클래스 정보이므로 애노테이션 자체를 얻어오고 비교하고 엘레먼트 값을 읽고 하는 것은 텍스트 설정정보를 다루는 것보다는 분명 우아하다.

문제는 애노테이션이 단순히 XML과 같은 초기 설정정보를 대치하는 것에서 벗어나서 원래 자바의 객체지향적인 기법을 이용해서 만들었어야 하는 것들을 대체하기 시작했다는 점이다. 그것은 애노테이션 탓이라기 보다는 사실 애노테이션이 길을 닦아준 메타프로그래밍 스타일의 개발방법 때문이라고 생각된다. 거기다 RoR이 일으킨 CoC의 열풍은 적절한 애노테이션으로 마킹하고, 나머지는 CoC에 의해서 미리 정해진 전제조건을 가지고 일단 시도해본다는 참 낙관적인 스타일의 코드를 만들게 하고 있다.

JUnit4는 더 이상 템플릿메소드니 아답타니 코맨드니 플러거블셀렉터이니 콤포짓이니 하는 그럴싸한 디자인패턴이 필요없이, 그저 @Before, @Test, @After 따위가 붙으면 프레임워크가 애노테이션을 일일히 읽어가면서 순서를 정하고 타입을 판단하고 부가적인 기능을 부여해준다.

JUnit 3.x는 조금 복잡하긴 해도 상위 클래스인 TestCase를 따라가서 어떻게 이 코드가 실행될지를 파악하고 싶다면 어렵지 않게 그 동작방식을 이해할 수 있었다. 필요하다면 2.0까지의 스프링테스트처럼 템플릿메소드 패턴을 이용해서 작업흐름을 확장하고 적절한 훅 메소드를 제공해서 확장된 기능의 테스트를 만들도록 되어있다. 역시 상속한 코드를 따라가보면 별 어렵지 않게 그 흐름을 읽을 수 있다.

템플릿 패턴과 전략패턴의 왕자 SpringMVC도 그랬다. 프로퍼티를 보면 어떤 기능을 확장해서 쓸지가 한 눈에 보였다. 계층구조를 따라가며 확장되가는 플로우 구조를 파악하면 반나절 정도면 SpringMVC의 전체 흐름을 꿰는 것은 평균수준의 개발자라면 별 어려운 일도 아니다. 그리고 자기 입맛에 맞게 그것을 어떻게 확장해서 쓸지를 머리 속에 그려볼 수 있다. 작업 플로우를 확장하고 싶으면 적절한 단계의 훅 메소드를 상속해서 final로 막고 거기서 자신의 흐름을 만들 후 새로운 확장용 훅 메소드를 제공해주면 그만이다. 기능적인 확장이라면 DI를 이용해 맘것 쓰세요하고 제공해주는 각종 프로퍼티들에 관련 인터페이스를 구현한 기능을 얼마든지 만들어 넣으면 된다.

하지만 MAC(MultiActionController)가 나오면서부터 이상해졌다. CoC라고 하는데, 이게 영 확장도 변경도 하기 힘들다. 3년전에 리플렉션 API로 떡칠이 되어있는데다 final로 있는대로 다 막아버린 “확장금지” MAC를 굳이 확장해서 기능을 추가하려고 어쩔 수 없이 코드를 통채로 배껴서 고쳐서 써야 했을 때의 씁쓸한 마음이 생각난다.

@MVC의 @RequestMapping이니 @ModelAttribute등을 처리하는 클래스를 열어봤을 때도 비슷한 마음이었다. 성능을 생각해서 리팩토링을 일부러 안한 부분까지는 이해는 하겠는데, 이게 무슨 3-5중 중첩 if 문은 기본이고, DI는 극히 제한적으로 허용되고, 클래스의 코드를 직접 고치지 않고는 그 기능을 확장하기 힘들게 만들어진 코드를 보고 얼마나 실망했는지 모른다.

생각해보면 이게 다 애노테이션과 메타프로그래밍을 써서 기존에 OO방식을 써서 해결하던 것을 대치하면서 모두 발생한 일이 아닌가 싶다. 물론 사용자는 편하다. 단, 잠깐만 편하다. 상속도 안하고, 인터페이스 구현도 없는 POJO라서 편해요라고 하는 것도 순간이다. 애노테이션과 CoC로 범벅이 되어있는 Spring2.5+의 @MVC 컨트롤러와 기존 Controller인터페이스 하나만 구현하면 되는 가장 기본적인 제한만 가지고 자유롭게 확장할 수 있던 구식 MVC와 비교해서 어느 것이 정말 사용하기 편한지 곰곰히 생각해보자. 당장에 후다닥 만들어서 쓰기엔 MAC니 @MVC가 나을지 모르겠다. 하지만 그걸 이용해서 범용적인 테스트를 만든다거나, 특정 request패턴을 따르는 컨트롤 로직을 부여한다거나, 모델이나 커맨드를 적절히 조작해주는 기능을 만들려고 해보면 아마도 @MVC는 스프링자체를 직접 손대지 않는한 거의 불가능하다.

JUnit은 안그런가? JUnit 4.x는 애노테이션을 적용하기 시작하면서 이미 그 프레임워크의 매력을 잃었다. 코드를 보면 실망스럽다. 핵심 테스트 러너인 BlockJUnit4ClassRunner을 보면 Conditional Factory 패턴의 적용한 코드가 나온다. 애노테이션을 읽어봐서 있으면 기능을 추가하고 없으면 말고 뭐 그런 식이다. 하나하나 사용할 애노테이션이 이미 다 코드에 박혀있다. 확장하려면? 코드를 직접 고치는 것 말고는 방법이 없다. @Inteceptor가 등장했으니 그걸로 유연하게 전체구조를 바꾸어 준다면 어찌될지 모르겠지만, JUnit5.0이 나오면서 명분을 가지고 설계를 통채로 개선하기 전에는 불가능할 것이다.

@RequestMapping의 매핑 알고리즘을 추가하거나 바꾸려면? @MVC의 1/3쯤은 뜯어고쳐야 할 것이다.

그 안에는 무수히 많은 애노테이션을 사용하는 메타프로그래밍과 그 메타 정보를 가지고 플로우를 직접 제어하는 코드들이 등장한다. OO가 제거해줬다는 if 문장이 그 덕분에 다시 출현해서 활동을 재개하고 있는 것이다. 애노테이션은 인터페이스인듯 생겼고, 상속도 되지만 딱 그 뿐이다. 그 안에 로직을 담을 수도, 그대로 가져다 사용할 수도 없다. 어느 곳에선가 애노테이션을 읽고 그걸로 조건을 판단해서 다른 코드를 실행하거나, 제어하거나, 성격을 부여해주거나 해야 한다. 기존의 OO적인 수단을 대치하는 것으로 사용되기 쉽상이다.

다형성도 필요없다. 애노테이션과 CoC로 적절히 생긴모양(메타정보)를 파악해서 스마트 덕 타이핑도 가능하다. 점점 클래스 타입도 별 의미가 없어질지도 모르겠다. 어쩌면 코드는 애노테이션에 담긴 메타정보를 가지고 머리를 굴리지 않으면 정말 어떤 일이 일어날 것인지 이해하기는 점점 힘들어질 것이다. 이젠 수퍼클래스나 사용할 인터페이스의 API문서가 아니라, 애노테이션을 해석하고 뒤치닥 거리를 해주는 관례가 담긴 정책문서를 보고 개발을 해야 할지도 모르겠다.

처음은 편하겠지만. 딱 거기까지이다. 뭔가 다른 레벨에서 기능을 확장하고, 만들어진 POJO를 사용하고 싶다면 애노테이션을 사용해서 동작하는 복잡-지저분한 코드의 프레임워크가 하고있는 노가다를 흉내내야 할 수 밖에 없다. 수많은 if 문을 써야 하면서 말이다.

9 Comments

기선June 23rd, 2009 at 11:47 am

얼마전에 WebTUnit 만들 때 애노테이션의 단순해 보이는 겉모습에 대한 환상을 깰 수 있었는데, 그런 이야기었군요. 애노테이션 제공하는 입장이거나, 기존의 애노테이션 활용 방법을 확장 또는 변경하고자 하는 사람 입장에서 보면 애노테이션 처리가 그다지 우아하진 않더라구요.

그래도 뭐 애노테이션 만한 게 없으니~ ㅎㅎ

RichpapaJune 23rd, 2009 at 12:25 pm

딴지는 아닙니다만, 기능은 비슷 혹은 같지만 닷넷은 애트리뷰트 아닌가요? ^^

TobyJune 23rd, 2009 at 12:46 pm

Richpapa/ 날카로운 지적입니다 :)
로드 존슨은 아마 소스코드 애트리뷰트라고 했을 겁니다.

영회June 23rd, 2009 at 1:34 pm

‘UML매핑’ -> ‘URL매핑’

뭔소린가 했어… 오타인 듯

TobyJune 23rd, 2009 at 1:40 pm

영회/ 까칠하기는.

박성철June 23rd, 2009 at 2:37 pm

전에 AnnotationMethodHandlerAdapter와 DefaultAnnotationHandlerMapping 소스 열어보고는 코드도 복잡하고 확장할여지도 없어보여 실망했었는데 이런 근본적인(?) 문제가 내포되어 있는 것이군요. 과연 어떤 문제인지 좀 생각해봐야겠습니다.
잘 읽었습니다. 감사합니다.

대한민국토리June 23rd, 2009 at 7:57 pm

스프링2.5로 작업을 해보면서 애노테이션 사용으로 XML 설정 파일 작성분량을 줄였다, 라고만 생각했었는데 막상 위 글을 읽어보니 보이는게 전부가 아니구나라는 생각이 듭니다.

애노테이션을 물 속에 숨은 백조발이라고 표현해도 될까요? 글 잘 읽었습니다. 감사합니다.

GlorideaJune 23rd, 2009 at 11:38 pm

그래도 역시 annotation 최대의 미덕이라면 ‘관계있는 것을 가까이 두는’데 있는 것 같네요. : )

[...] 번에 한 생각을 좀 더 정리해보려고 자바의 까칠대마왕 조쉬 블록이 뭐라고 했는지 좀 [...]

Leave a comment

Your comment