애스팩트 지향 프래그래밍(AOP)는(단일한 책임 범위 내에 있지 않은) 하나 이상의 객체에 유용한 코드를 한데 묶어 눈에 띄지 않게 객체에 배포하는 기법이다. AOP 에 대해 잘 모르는 관계로 일단 용어 정리부터 한다.

용어 의미
Advice 배포할 코드 조각
Aspect Advice 가 처리할 문제
cross-cutting concern Aspect 와 동일
Target 부가기능을 부여할 대상으로 핵심기능이 담긴 클래스이거나 추가적인 부가기능을 제공할 프록시 오브젝트 일 수 있다
Join Point Advice 가 적용될 위치. 타깃 오브젝트가 구현한 인터페이스의 모든 메서드가 조인 포인트가 된다.
Point Cut Join Point 를 선별하는 기능을 정의한 모듈
Proxy 클라이언트와 타깃 사이에 존재하면서 부가기능을 제공하는 오브젝트. 클라이언트는 타깃을 요청하지만, 클라이언트에게는 DI를 통해 타깃 대신 프록시가 주입된다. 클라이언트의 메소드 호출을 대신 받아서 타깃에게 위임하며, 그 과정에서 부가기능을 부여한다. 스프링 AOP는 프록시를 이용한다.
Advisor 포인트컷과 어드바이스를 하나씩 갖고 있는 오브젝트. AOP의 가장 기본이 되는 모듈이다. 스프링은 자동 프록시 생성기가 어드바이저 단위로 검색해서 AOP를 적용한다.

출처 : AOP(Aspect Oriented Programming)

자바스크립트 패턴과 테스트
길벗출판사,(지은이) 래리 스펜서, 세스 리처즈 ,(옮긴이) 이일웅

의 예이다.

행사 주최자는 비용 문제로 항공권 판매 여행사와 제휴를 맺었다. 콘퍼런스 웹 개발자 승현은 로그인한 참가자가 원하는 지역 공항의 항공권 할인 운임을 조회하는 웹 서비스를 호출해야한다.

바로바로 알림창이 뜨면 좋겠지만, 웹 서비스 호출은 아무래도 시간이 걸리기 마련이다. 따라서 승현은 참가자 본인이 공항을 바꾸지 않는 한 해당 항공권 정보를 캐시하기로 한다.

물론 항공권 말고도 웹 사이트에서 캐시할 항목은 더 있다. 어쨋든 캐싱은 횡단 관심사이자 애스팩트 지향 프로그래밍의 유력한 후보자다.

라는 내용이 있다. AOP 에서는 우리가 작성해야하는

‘로그인한 참가자가 원하는 지역 공항의 항공권 할인 운임을 조회하는 웹 서비스를 호출’을 하는 핵심 기능에서 부가적인 기능들을 나눠서 모듈로 만들어서 설계하는 방법이다.

캐싱은 핵싱 기능이 아니라 위 핵심기능을 하는데 있어서 유용한 부가 기능이다. 이를 Aspect( or cross-cutting concern(횡단 관심사)) 모듈로 작성하는 것이다. 핵심 기능이 아닌 부가적인 기능의 관점에서 작성할 수 있고 이렇게 새로운 관점에서 작성할 수 있게 해준다는 의미로 관점 지향 프로그래밍(Aspect Oriented Programming)이라고 한다.

AOP 를 하는데 있어서 Proxy 의 사용 유무는 Dependency Injection Container 의 존재 유무와 상관이 있다.

대략적인 AOP 의 용어와 개념은 위 표를 보면 알 것이다.

그래서 다시 위의 핵심기능을 하기위해 필요한 부가기능인 캐싱을 구현해본다. 책의 예제코드는 잘못된 내용이 좀 있어서 수정이 필요하다. 일단 책에서 나와 있는 초기 코드를 본다.

AOP 없는 캐싱(작동 불가)
TravelService = (function (ramWebService) {
    let conferenceAirport = 'BOS';
    let maxArrival = new Date();
    let minDeparture = new Date();
    let cache = [];
    return{
        getSuggestedTicket: (homeAirport)=>{
            let ticket;
            if(cache[homeAirport]){
                return cache[homeAirport];
            }
            ticket = ramWebService.getCheapestRoundTrip(homeAirport, conferenceAirport, maxArrival, minDeparture);
            cache[homeAirport] = ticket
            return ticket;
        }
    };
})();

이 코드가 작동한다고 적혀 있는데 클로저와 즉시 실행함수를 이용하여 생성 패턴을 변경하였으면 인자를 전달해야하는데 전달 부분이 없다. 그렇다고 ramWebService 를 set 할 수 있는 공개함수도 없다. 저자의 의도는 ramWebService 를 래핑하는 객체를 만들고자 했으므로 공개함수를 추가하지말고 우리가 사용할 서비스를 넣어준다.

AOP 없는 캐싱(작동하도록 수정)
TravelService = (function (ramWebService) {
    let conferenceAirport = 'BOS';
    let maxArrival = new Date();
    let minDeparture = new Date();
    let cache = [];
    return{
        getSuggestedTicket: (homeAirport)=>{
            let ticket;
            if(cache[homeAirport]){
                return cache[homeAirport];
            }
            ticket = ramWebService.getCheapestRoundTrip(homeAirport, conferenceAirport, maxArrival, minDeparture);
            cache[homeAirport] = ticket
            return ticket;
        }
    };
})(new ConferenceWebSvc());

단지 즉시실행함수 호출부에 인자로 ConferenceWebSvc 객체를 생성하여 전달하였다. 책이 좀 불친절해서 읽는 속도가 많이 떨어진다. 코드를 보고 이게 돌아간다고? 하는 부분들이 좀 있다. 그런 부분은 수정해가면서 글을 써나간다.

캐싱은 기본적인 내용은 변경이 잦지 않은 데이터등을 미리 사용환경보다 빠른곳에 올려놓은 채로 해당 데이터가 필요시 읽어오는 것이다. 지금은 javascript application 의 runtime 에서 해당 데이터들은 Array 의 형태로 저장될 것이다.

읽기 쓰기가 동시에 일어나는 환경에서는 해당 데이터에 대해서 lock 을 걸고 해제하는 프로세스등이 필요할 것이다.

하지만 현재 작성중인 코드는 그런환경을 타는 코드가 아니기때문에 간단하게 구현한다. 지금까지 작성한 코드는 AOP 를 이용하지 않았다.

AOP(Aspect Oriented Programming) 는 함수를 단순하게 유지한다(단일 책임 원칙을 지키기 위하여 하니 당연하다). 그리고 AOP 는 코드를 DRY 하게 해준다(module 지향적인 코드이니 중복코드는 줄것이다.).

시작도 전부터 판을 깨는것 같지만 어느 사람들은 이러한 모듈지향적인 코드가 오히려 프로그램의 복잡도를 올리고 스파게티코드를 양산한다고 말한다. 일정부분 동의하는 부분은 있다. 추상화된 레이어가 늘어나면 늘어날 수록 분명 각각의 커플링은 줄어든다. 하지만 커플링이 줄어든다는 것이 성능에 꼭 좋은 영향을 준다는 말은 아니다.

또한 언젠가 추상화된 레이어들의 깊은 밑의 코드를 봐야할 때는 오히려 하나 하나 추상화된 코드들의 관계를 생각해가며 봐야하는 지옥이 펼쳐질 수 있다. 그리고 사람이 하는 일이기에 잘못된 추상화로 인해 오히려 강한 의존성을 가지는 코드가 어느 한부분에는 존재할 수 있다.

어느것이 옳으냐 가지고 싸워서는 답이 없다고 생각한다. 분명히 어느 시점에서 우리는 필요에 의해 OOP 를 생각하였다. 그저 그것이 필요한 상황에서 혹은 본인에게 실보다 득이 조금 더 있다면 하는 것이다.

TDD 와 BDD, DDD(Data Driven Development), DDD(Domain Driven Development),EDD 등의 개발 방법론 또한 마찬가지로 생각한다. EDD 와 같은 내용은 현대 소프트웨어에서 필수 불가결이라고 생각한다. 단순 응용프로그램으로서 동일한 환경에서 돌아가는 프로그램은 거의 없다.

다양한 환경에서 다양한 시스템 또는 다양한 응용프로그램과 커뮤니케이션해가며 동작하는 프로그램들이 대부분인데 이에 대해 모두 그때 그때 개발하는 것은 말이 안된다. 그러한 상황 자체를 이벤트 로 보고 해당 이벤트를 핸들링할 수 있는 API 의 연계로 작성하는 것이 현실적이라고 생각한다. 또한 점점 복잡해져가는 사용자와의 인터랙션을 처리하고 정의하기 위해서는 괜찮다고 생각한다.

TDD 의 경우는 얼마전 어느 커뮤니티에서 벌어진 언쟁을 보며 나또한 생각이 많아졌다. 유닛테스트들을 작성하고 유닛테스트를 통합하는 통합테스트를 작성하고 이를 작성하는 과정중에 개발이 이뤄지는 프로세스에서 한쪽은 이것이 해봤더니 약장수의 약팔이더라라고 주장하고 한쪽은 광적으로 찬양일색이었다.

일단 약팔이로 규정한쪽은 ‘TDD 가 하는일은 QA가 하는일이며 TDD 를 하느니 차라리 제대로 된 QA 프로세스와 인력을 구축하는 것이 옳다. 그 이유는 TDD 를 하면서 테스트를 작성하는 이가 개발자 본인인데 개발자 본인이 테스트를 작성할 때는 본인도 어떻게 동작해야할지 감이 없는 상태에서 테스트를 작성하는 경우가 많다. 그렇다고 테스트작성만을 하는 사람을 두려면 굳이 TDD 를 하는 이유가 약해진다.’ 라는 이유로 굳이 TDD 를 지금 시점에서 공부하고 적용해야할 필요는 없다이다.

TDD 를 통해 얻을 수 있는 이점은 작성한 테스트케이스 내에서 동작하는 신뢰할 수 있는 코드를 생산한다. 공통의 문서대용이 될 수있다. 확장에 대해 무결한지(테스트가 깨지는 상황이 발생하는지)에 대한 결과를 얻을 수 있다. 그리고 개발자 본인이 무수히 많은 테스트를 작성하면서 코드의 작성자가 아닌 사용자관점에서 좀 더 사용하기 편한 코드를 생산한다는 것인데

이점만 보면 TDD 는 정말 마법같은 개발 방법론이다. 약팔이 주장을 한쪽도 분명 이러한 마법을 처음에 믿었기에 공부하고 적용했을 것이다.

하지만 그저 방법론이었던 것이다. 이를 실제로 행하는 주체는 개발자들이다. 그런 개발자들이 수많은 Test Framework 들을 학습하고 급한 핫픽스를 하는데 테스트를 작성하고 통과해야하는 과정에서 생산성이 오히려 떨어지는 경우를 더 많이 겪었기에 그럴 것이다. 그리고 일정부분 동의하는 것이 테스트를 작성하면서 개발자 본인의 생각이 정리되고 좀 더 코드의 사용자 지향적인 작업을 할 수있는 좋은 영향이 있지만 테스트 결과에대한 무조건적인 신뢰를 기반으로 진행하면서 오히려 맹복적으로 테스트에 기대는 결과가 벌어질 수 있다. 위에서 이야기 했지만 테스트의 작성자는 개발자 본인이다.

본인이 생각 못한 부분이 있을 수 있지만 테스트를 모두 통과했기에 본인의 생각을 테스트안에 가두는 결과가 벌어지는 것이다. 이는 분명히 악영향이다.

조직에서의 일이라면 논의가 아닌 불건전한 싸움이(정치질) 될 수 있다.

그래서 나는 혼자 개발하는데 있어서 TDD 는 어떤가 생각해보면 좋겠다. 잘 작성해놓은 단위테스트들은 프로그램의 스펙문서가 될 수 있다. 본인의 포트폴리오로 사용해도 될것이며 테스트문서들을 맹목전인 신뢰를 가지지 않고 본다면 이런부분은 테스트가 통과했구나 일단 사용은 해도 괜찮겠다 or 아니다. 정도의 내용은 전달이 가능하다.

어차피 개발자 본인이 알아차리지 못한 오류는 발생하면 잡아야한다. 하지만 일반적으로 실수 하기 쉬운 부분에대해서는 어느정도 필터역할을 해줄수있다.

테스트를 작성할 때 실 환경과 실제 시나리오를 모두 재현하는 것은 불가능하다라고 생각을 하고 맹목적인 신뢰에 대한 경계를 하는 것이 필요하다고 생각한다.

TDD 의 맹목적인 신봉자들은 그저 방법론일 뿐인것을 너무 웅호할 필요가 없다. 각자의 상황, 자원, 능력, etc 에 따라 더 적합한 방법들은 다 다를것이다.

몇안되는 팀원들로 이루어진 스타트업을 생가해보면 더더욱 맹목적인 신봉자들이 웅호하는 것은 불필요한 일이다. 차라리 테스트를 작성하는 시간에 서비스를 만들어서 실제 운영하며 수정해나가는 것이 좀 더 상황에 맞을 수 있다는 것이다. 그리고 테스트의 작성자와 개발자가 분리되어야 하는것은 내 개인적인 생각으로 옳은 이야기라고 생각한다.

개발자 본인이 TDD 를 수행하며 작성한 테스트는 본인의 로직에 대한 본인 스스로의 최소한의 검증을 거친것으로 생각하고 테스터가 작성한 테스트들에 대한 결과와 실제 운영환경에서 오는 버그에 대한 결과는 겸허히 받아들여야한다. 이러한 팀이 구성되려면 어지간한 회사가 아니면 힘들것이다.

하지만 이를 공부하지 않아도 된다는 것은 본인의 경험만을 맹신한 또 하나의 꼰대질이라고 생각한다. 분명히 혼자서라도 사용하면서 얻는 이점이 있을 수 있고 그것이 유용하고 아니고에 대한 만족은 본인이 하는것이다(조직생활 제외). 어떤 이에게는 이점이 더 클 수 있는 걸 본인이 해봤는데 별로더라 그러니 볼필요 없다는 것은 오만이다.

그런식으로 따지면 세상에는 주니어는 없고 시니어만 존재하는 상황이 벌어질 것이다. 미래가 없다는 이야기다. 선배로서 후배들에게 꽃길을 걷게 하겠다는 생각은 좋지만 본인이 그 꽃길까지 어떻게 갔는지 생각해보길 바란다.

AOP 에 대한 이야기를 하려다가 말이 옆길로 가서 다음 포스팅에서 AOP 의 실제 구현에 대해 본다.