ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 프레임워크 - 1부
    [옛날 글들] 설계 이야기 2024. 5. 30. 18:53
    728x90

    재사용과 프레임워크

    가장 이상적인 재사용 방법은 추가적인 프로그래밍 작업 없이 이미 존재하는 컴포넌트(component)를 조립하여 시스템을 구축하는 것이다. 그러나 이와 같은 컴포넌트 기반의 레고 블록(LEGO block, 또는 집적 회로 IC) 접근 방법에는 분명한 한계가 있다.

     

    다양한 컨텍스트 내에서 컴포넌트를 재사용하기 위해서는 조립 시 다양한 매개변수와 옵션을 설정할 수 있어야 한다. 설정 가능한 파라미터와 옵션의 종류가 적을수록 컴포넌트를 재사용할 수 있는 콘텍스트의 범위가 줄어든다. 반대로 설정 가능한 파라미터와 옵션의 종류가 많을수록 재사용을 위해 기억해야 하는 정보의 양이 늘어나기 때문에 프레임워크를 사용하기가 복잡해진다. 컴포넌트와 관련된 역설은 컴포넌트를 재사용 가능하게 만들려고 하는 모든 노력은 컴포넌트의 재사용 가능성을 감소시킨다는 것이다.

     

    보다 본질적인 문제는 비즈니스 컴포넌트와 같은 대규모의 재사용 가능한 컴포넌트를 개발하는 것이 거의 불가능하다는 사실이다. 설정 가능한 파라미터와 옵션이 다양해야 한다는 것은 소프트웨어가 수용해야 하는 요구사항의 스펙트럼이 다양해야 한다는 것을 의미한다. 여러 애플리케이션에 대해 코드를 수정하지 않고도 재사용 가능할 정도로 유사한 요구사항을 발견하는 것은 예외에 가깝다.

    이 논쟁의 핵심은 소프트웨어 다양성이라 불리는 주제에 있을 것이다. 만약 여러 프로젝트나 애플리케이션 도메인 사이에 비슷한 문제가 충분히 많이 존재한다면 컴포넌트 기반의 접근방법은 결국 효과가 있을 것이다. 그러나 많은 사람들이 의심하는 바와 같이, 애플리케이션과 도메인의 다양성으로 인해 두 가지 문제가 아주 비슷한 경우가 거의 없다면, 가장 기본이 되는 공통적 작업만이 일반화될 수 있을 것이고, 이것이 프로그램 코드에서 차지하는 비율 또한 매우 작을 것이다.


    Robert L. Glass, 우리가 미처 알지 못한 소프트웨어 공학의 사실과 오해 

     

    컴포넌트 재사용이 비현실적인 접근방법이라면 컴포넌트를 개발하는 동안 축적된 경험과 지식을 재사용하는 방법을 생각해 볼 수 있다. 컴포넌트 자체를 재사용하는 것은 코드 재사용(code reuse)을 의미한다. 이에 비해 경험과 지식의 재사용은 설계의 재사용(design reuse)을 의미한다. 언어나 플랫폼과 같은 다양한 제약으로 인해 적용 범위가 제한되는 코드 재사용에 비해 설계를 재사용할 수 있는 범위는 무한대에 가깝다. 또한 설계의 재사용은 프로젝트 초반부터 적용 가능하기 때문에 코드 재사용보다 투자 대비 효과가 더 크다.

     

    설계를 재사용하는데 있어 가장 큰 어려움은 재사용 가능하도록 설계를 표현하고 커뮤니케이션할 수 있는 효과적인 방법을 찾는 것이다. UML을 중심으로 한 그래픽적인 표기법과 패턴을 통해 일정 수준의 의사소통은 가능할지 몰라도 설계에 담긴 모든 휴리스틱과 근거, 대안을 모호하지 않도록 전달하는 것은 쉽지 않은 일이다.

     

    패턴의 목적은 설계 의도나 근거를 설명하는 것이지 구현 결과를 제공하는 것이 아니다. 따라서 패턴이라는 형식을 통해 표현된 설계를 재사용하기 위해서는 패턴을 구성하는 각 부분에 대응되는 코드를 재작성하는 반복적인 작업이 요구된다. 또한 패턴은 어떤 애플리케이션에라도 적용 가능해야 하므로 언어나 구현 방법에 독립적이며 추상적이어야 한다. 디자인 패턴을 효과적으로 적용하기 위해서는 트레이드오프를 통해 상황에 적합한 패턴을 선택하고 가공해야 한다. 우리는 항상 TEMPLATE METHOD와 STRATEGY 패턴의 갈림길에서 고민해야 한다.

     

    가장 이상적인 형태의 재사용 방법은 설계 재사용과 코드 재사용을 적절한 수준으로 조합하는 것이다. 코드 재사용만을 강조하는 컴포넌트는 실패했다. 추상적인 수준에서의 설계 재사용을 강조하는 패턴은 설계를 재사용하기 위해 매번 유사한 코드를 작성해야만 한다. 패턴과 유사하게 기존 설계를 재사용할 수 있으면서도 패턴을 반복적으로 구현하는 문제를 피하기 위해 이미 존재하는 코드를 재사용할 수 있는 방법은 없을까?

     

    프레임워크에서 해답을 찾을 수 있다. 프레임워크란 “추상 클래스 집합과 추상 클래스 인스턴스 간의 상호작용을 통해 시스템 전체나 일부를 표현하는 재사용 가능한 설계”, 또는 “애플리케이션 개발자가 현재의 요구사항에 맞게 커스터마이즈 할 수 있는 애플리케이션의 골격(skeleton)”을 의미한다. 첫 번째 정의가 프레임워크의 구조적인 측면에 초점을 맞추고 있는 반면 두 번째 정의는 프레임워크를 사용하는 목적인 코드와 설계의 재사용에 초점을 맞추고 있다.

     

    프레임워크는 애플리케이션의 아키텍처를 제공하며 문제 해결에 필요한 설계 결정과 이에 필요한 기반 코드 모두를 포함하고 있다. 프레임워크는 애플리케이션이 확장할 수 있도록 부분적으로 구현된 추상 클래스와 추가적인 작업 없이도 재사용 가능한 다양한 종류의 컴포넌트 라이브러리를 제공한다. 설계 아이디어를 재사용하기 위한 수단으로 프레임워크를 이용할 경우 패턴에서 살펴본 설계의 전달 방안에 대해 고민할 필요가 없다. 프레임워크에서는 코드가 곧 설계 재사용을 위한 표기법이다.

     

    프레임워크에서 설계를 재사용 가능하도록 지원해 주는 핵심 구성요소는 추상 클래스(abstract class)다. 추상 클래스의 경우 인스턴스를 생성할 수 없기 때문에 서브 클래스를 생성하기 위한 템플릿의 용도로 사용된다. 프레임워크를 사용해서 애플리케이션을 구축할 경우 추상 클래스를 상속받은 서브 클래스를 추가하여 애플리케이션에 특화된 기능을 구현하게 된다. 따라서 프레임워크에 속한 추상 클래스는 애플리케이션에 속한 서브 클래스의 인터페이스를 정의한다.

     

    인터페이스의 정의가 프레임워크에 속하기 때문에 자연스럽게 애플리케이션의 제어 흐름 역시 프레임워크에 의해 결정된다. 프레임워크의 핵심은 추상 클래스가 아니라 추상 클래스의 인터페이스에 의해 정의되는 상호작용 방식이다. 서브 클래스는 추상 클래스가 사용되는 문맥 내에서 추상 클래스와 동일한 방식으로 상호작용할 수 있어야 하며 리스코프 치환 원리(LSP, Liskov Substitution Principle)에 따라 대체 가능해야 한다. 

    프레임워크는 애플리케이션에 대한 아키텍처를 제공한다. 즉, 프레임워크는 클래스와 객체들의 분할, 전체 구조, 클래스와 객체들 간의 상호작용, 객체와 클래스 조합 방법, 제어 흐름에 대해 미리 정의한다. 프레임워크는 설계의 가변성을 미리 정의해 두었기 때문에 애플리케이션 설계자나 구현자는 애플리케이션에 종속된 부분에 대해서만 설계하면 된다. 프레임워크는 애플리케이션 영역에 걸쳐 공통의 클래스들을 정의하여 일반적인 설계 결정을 미리 내려 둔다. 비록 프레임워크가 즉시 업무에 투입할 수 있는 구체적인 서브 클래스를 포함하고 있기는 하지만 프레임워크는 코드의 재사용보다는 설계 자체의 재사용을 중요시 여긴다.

    GOF, 디자인 패턴

    의존성 역전(Dependency Inversion)과 프레임워크

    앞에서 살펴 본 바와 같이 프레임워크의 핵심은 추상 클래스라고 할 수 있다. 그렇다면 추상 클래스(또는 인터페이스)의 어떤 특징이 프레임워크의 재사용성을 향상시키는 것일까?

     

    객체-지향 이전의 구조적 분석/설계와 같은 전통적인 소프트웨어 개발 방법의 경우 상위 레벨 모듈이 하위 레벨 모듈에, 그리고 상위 정책이 구체적인 세부적인 사항에 의존하도록 소프트웨어를 구성한다. 이해를 돕기 위해 핸드폰 과금 시스템에서 가입자가 문자 서비스를 사용할 경우의 요금 계산 로직을 떠올려 보자. 여기에서의 상위 정책은 DOMAIN EVENT의 타입에 따라 DISPATCHER AGREEMENT에서 POSTING RULE을 선택하는 일련의 흐름이다. 실제 요금을 계산하는 DOMAIN EVENT, ACCOUTING ENTRY, POSTING RULE 타입은 구체적인 세부 사항에 속한다.

     

    <그림 1>은 통화 요금을 계산하는 상위 정책인 PeriodicServiceContract가 세부적인 SmsStandardPostingRule, SmsSendingEvent, MonetaryEntry와 강하게 결합되어 있는 전통적인 의존 관계를 표현한 것이다.

    <그림 1> 정책이 세부적인 사항에 의존하는 전통적인 의존 관계

     

    <그림 1>의 설계에 나타난 가장 큰 문제점은 다양한 종류의 POSTING RULE과 DOMAIN EVENT, ACCOUNTING ENTRY를 처리해야 하는 PeriodicServiceContract가 문자 메시지에 특화된 구체적인 클래스들에게 의존한다는 점이다. 따라서 통화 요금 계산을 위해 새로운 POSTING RULE, DOMAIN EVENT, ACCOUNTING ENTRY 타입을 사용해야 할 경우 PeriodicServiceContract를 재사용할 수 없다. 즉, 상위 레벨의 정책이 하위 레벨의 구체적인 세부사항에 의존하도록 구조를 설계할 경우 다양한 콘텍스트에서의 재사용이 불가능해진다.

     

    이와 같은 의존성 문제를 문제를 해결할 수 있는 방법이 없을까? 방법은 의외로 간단하다. 상위 레벨의 정책을 재사용하기 위해서는 상위 레벨의 정책이 하위 레벨에 대해 독립적이어야 한다. 즉, 정책이 상세에 의존하지 않도록 의존성을 역전시켜야 한다.

     

    상위 레벨의 정책이 의존하고 있는 세부적인 클래스들로부터 EXTRACT INTERFACE를 한다. 이제 상위 레벨 정책이 하위 레벨의 구체적인 클래스에 직접적으로 의존하지 않게 되며 상세와 정책 모두 추상적인 인터페이스에 의존하게 된다. 이와 같은 방법은 전통적인 분석/설계 방법에서 다루는 의존성의 방향을 역전시킨다. 이처럼 재사용 가능한 객체 지향 설계를 만들기 위해 인터페이스 또는 추상 클래스를 사용해서 의존성의 방향을 바꾸는 것을 의존성 역전 원리(DIP, Dependency-Inversion Principle)라고 한다. <그림 2>는 의존성을 역전시킨 후의 핸드폰 과금 시스템의 구조를 표현한 것이다.

    <그림 2> 정책과 상세 모두 인터페이스에 의존하도록 의존성을 역전

     

    이제 <그림 3>과 같이 상위 정책인 PeriodicServiceContract와 추출된 인터페이스를 별도의 패키지로 묶어 보자. DOMAIN EVENT와 ACCOUNTING ENTRY의 인터페이스는 상위 정책의 필요에 의해 결정되기 때문에 상위 정책에 속한다. 즉, 인터페이스와 구현 클래스를 별도의 패키지로 분리해야 한다. 이처럼 재사용을 위해 인터페이스와 구현 부를 별도의 패키지로 분리하는 것을 SEPARATED INTERFACE 패턴이라고 한다. 상위 정책과 인터페이스를 포함하는 패키지는 전체 애플리케이션에 있어 프레임워크로서의 기능을 제공한다.

    <그림 3> 상위 정책과 인터페이스를 포함하는 프레임워크

     

    <그림 4>와 같이 상위 정책을 표현하는 패키지에 속한 인터페이스 또는 추상 클래스를 구현하는 새로운 POSTING RULE, DOMAIN EVENT, ACCOUNTING ENTRY를 추가해서 상위 정책의 설계를 재사용할 수 있다. 이것이 애플리케이션이 프레임워크의 설계와 코드를 재사용하는 방법이다.

    <그림 4> 프레임워크를 재사용하는 애플리케이션

    사실, 좋은 객체 지향 설계의 증명이 바로 이와 같은 의존성의 역전이다. 프로그램이 어떤 언어로 작성되었는가는 상관없다. 프로그램의 의존성이 역전되어 있다면, 이것은 OO 설계를 갖는 것이다. 그 의존성이 역전되어 있지 않다면, 절차적 설계를 갖는 것이다. 의존성 역전의 원칙은 객체 지향 기술에서 당연하게 요구되는 많은 이점 뒤에 있는 하위 수준에서의 기본 메커니즘이다. 재사용 가능한 프레임워크를 만들기 위해서는 이것의 적절한 응용이 필수적이다. 이 원칙은 또한 변경에 탄력적인 코드를 작성하는 데 있어 결정적으로 중요하다. 추상화와 구체적인 사항이 서로 고립되어 있기 때문에, 이 코드는 유지보수하기가 훨씬 쉽다.

    Robert C. Martin, 클린 소프트웨어

     

    다음 글 : 프레임워크 - 2부 [끝]

    728x90
Designed by Tistory.