Skip to main content

[오브젝트] Chapter07. 객체 분해

· 15 min read

개요

사람이 수용할 수 있는 정보의 허용량을 넘어선 순간부터 문제 해결 능력은 급격하게 떨어지는 인지 과부하가 발생한다.
인지 과부하를 방지하는 가장 좋은 방법은 쏟아지는 정보의 양을 조절하는 것이다. 추상화는 문제 해결에 필요한 핵심만 남기고 불필요한 정보를 제거하는 것을 말한다.

가장 일반적인 추상화 방법은 문제의 크기를 줄이는 것이다. 큰 문제를 해결 가능한 작은 문제로 나누는 것을 분해(decomposition)이라 한다.
한번에 해결하기 어려운 문제를 생각하기 쉽도록 작은 문제로 다시 나누어 생각하면 접근하기 더 쉬워진다.

프로시저 추상화와 데이터 추상화

  • 프로시저 추상화 (Procedure abstraction) 소프트웨어가 무엇을 해야 하는지를 추상화하는 방법이다. 기능 분해(functional decomposition)/알고리즘 분해(algorithm decomposition)를 통해 추상화할 수 있다.

  • 데이터 추상화 (Data abstraction) 소프트웨어가 무엇을 알아야 하는지를 추상화는 방법이다. 데이터 추상화를 하는 방법에는 두 가지가 있다.

    • 타입을 추상화 (Type abstraction)
    • 데이터를 중심으로 프로시저를 추상화

프로시저 추상화와 기능 분해

메인 함수로서의 시스템

시스템을 구성하는 가장 최상위 기능을 정의하고, 이 최상위 기능을 좀더 작은 단계의 하위 기능으로 분해하는 방법을 하향식 접근법이라 한다.
분해는 세분화된 마지막 하위 기능이 프로그래밍 언어로 구현 가능한 수준이 될 때 까지 계속된다.

하향식 기능 분해의 문제점

  • 시스템은 하나의 메인 함수로 구성돼 있지 않다.
  • 기능 추가나 요구사항 변경으로 인해 메인 함수를 빈번하게 수정해야 한다.
  • 비즈니스 로직이 사용자 인터페이스와 강하게 결합된다.
  • 하향식 분해는 너무 이른 시기에 함수들의 실행 순서를 고정시키기 때문에 유연성과 재사용성이 저하된다.
  • 데이터 형식이 변경될 경우 파급효과를 예측할 수 없다.

하나의 메인 함수라는 아이디어는 비현실적이다. 어떤 시스템도 최초에 릴리스됐던 당시의 모습을 그대로 유지하지는 않는다.
계속해서 사용자의 요구를 만족하기 위해 새로운 기능을 추가하게 된다. 메인 함수는 결국 시스템 내의 다른 기능과 동등한 수준이 되어버린다. 하향식 접근법은 현대적인 상호작용 시스템을 개발하는 데는 적합하지 않다. 현대적인 시스템은 동등한 수준의 다양한 기능으로 구성된다.

또한 메인 함수의 빈번한 재설계를 유발하게 된다. 하향식 기능 분해의 경우 새로운 기능을 추가할 때 마다 메인 함수를 수정해야 한다.
새로운 기능을 위한 적절한 위치를 확보하기 위해 메인 함수를 빈번히 건드려야 하기 때문이다.

하향식 접근법은 비즈니스 로직을 설계하는 초기 단계부터 입력 방법과 출력 양식을 함께 고민하도록 강요한다.
코드 안에서 비즈니스 로직과 사용자 인터페이스가 밀접하게 결합되게 된다.

문제는 비즈니스 로직과 사용자 인터페이스가 변경되는 빈도가 다르다는 것이다.
하향식 접근법은 비즈니스 로직과 사용자 인터페이스를 한데 섞기 때문에 사용자 인터페이스를 변경하는 경우 비즈니스 로직도 영향을 받는다.
사용자 인터페이스를 변경하기 위해서는 전체 설계를 변경해야 한다.

하향식 접근법은 설계 초기 단계부터 구현을 염두하기 때문에 시간 제약을 강제한다. 소프트웨어가 무엇을 해결하는지에 초점을 두지 않고 어떻게동작하는지에 초점을 둔다.
메인 함수가 다른 작은 함수들로 분해되기 위해서는 우선 함수들의 실행 순서를 결정해야 한다.
실행 순서나 조건, 반복과 같은 구조를 미리 결정하지 않고는 분해를 진행할수 없기 때문에 기능 분해 방식은 중앙 집중 제어 스타일의 형태를 띌 수 밖에 없다.
문제는 중요한 설계 결정 사항인 함수의 제어 구조가 빈번한 변경의 대상이라는 것이다. 기능이 추가하거나 변경하는 작업은 매번 기존에 결정된 함수의 제어구조를 변경하도록 만든다. 하향식 설계와 관련된 모든 문제의 원인은 결합도다. 분해된 함수들이 상위 함수의 문맥에 강하게 결합되어있다. 가장 큰 문제는 시스템의 핵심적인 구조를 결정하는 함수가 데이터에 강하게 결합되어있다는 것이다.

하향식 접근법은 어떤 함수가 어떤 데이터를 사용하고있는지를 알기 어렵다. 단순히 텍스트 검색으로 찾아낼 수 있는 문제가 아니라 모든 함수를 열어 해당 데이터를 사용하는지를 확인해봐야 하기 때문이다.
만약 데이터의 구조가 변경되었다면 그 파급효과는 시스템 전체에 영향을 미칠 것이다. 어떤 데이터가 어떤 함수에 의존하고 있는지를 알아야 변경을 적용할 수 있기 때문에 작업에 필요한 노력도 커진다.
데이터 변경으로 인한 영향을 최소화 하려면 데이터에 의존하는 부분과 아닌 부분을 명확하게 분리해야한다. 잘 정의된 퍼블릭 인터페이스를 통해 데이터로의 접근을 통제해야 한다.

언제 하향식 분해가 유용한가?

설계가 어느 정도 안정화 된 후에 설계의 다양한 측면을 논리적으로 설명하고 문서화 할때 하향식 분해는 유용한 접근법으로 사용할 수 있다.

하향식 접근은 완전히 이해된 사실을 서술하기에 적합한 방법이다.[Michael Jackson.]

모듈

정보 은닉과 모듈

기능을 기반으로 시스템을 분해하는 것이 아닌, 변경의 방향에 맞춰 시스템을 분해하는 것이다. 함께 변경되는 부분을 하나의 구현 단위로 묶고 퍼블릭 인터페이스를 통해서만 접근되도록 하는 것이다.
정보 은닉은 시스템을 모듈 단위로 분해하기 위한 기본 원리로 자주 변경되는 부분을 상대적으로 덜 변경되는 안정적인 인터페이스 뒤로 감춰야 한다는 것이 핵십이다.

모듈은 두 가지의 비밀을 감춰야 한다.

  • 복잡성 모듈이 너무 복잡한경우 이해하고 사용하기 어렵다. 외부에 모듈을 추상화할 수 있는 간단한 인터페이스를 제공해서 모듈의 복잡도를 낮춘다.
  • 변경 가능성 변경 가능한 설계 결정이 외부에 노출될 경우 실제로 변경이 발생했을때 파급효과가 커진다.
    변경 발생시 하나의 모듈만 수정하면 되도록 변경 가능한 설계 결정을 모듈 내부로 감추고 외부에는 쉽게 변경되지 않을 인터페이스를 제공한다.

모듈의 장점과 한계

모듈 분해의 장점은 다음과 같다.

  • 모듈 내부의 변수가 변경되더라도 모듈 내부에만 영향을 미친다.
  • 비즈니스 로직과 사용자 인터페이스에 대한 관심사를 분리한다.
  • 전역 변수와 전역 함수를 제거함으로써 네임스페이스 오염(namespace pollution)을 방지한다.

모듈은 변경의 정도에 따라 모아놓은 기능의 집합이기 때문에 모듈 내부는 높은 응집도를 유지한다. 또한 모듈 간의 통신은 외부로 공개한 퍼블릭 인터페이스를 통해서만 가능하기 때문에 낮은 결합도를 유지한다.
중요한 것은 모듈 분해는 기능을 데이터 중심으로 분해하게 된다는 것이다. 즉 데이터에 강하게 결합되어 데이터 변경시의 파급 효과에 대한 문제점은 여전히 남아있게된다.
또한 인스턴스의 개념을 제공하지 않기 때문에 런타임시의 타입이 고정되어버린다.

데이터 추상화와 추상 데이터 타입

추상 데이터 타입

모듈 분해보다 더 높은 수준의 추상화를 위해 등장한 개념으로 같은 타입의 여러 인스턴스를 독립적으로 존재할 수 있게 하는 개념이다.

추상 데이터 타입은 추상 객체의 클래스를 정의한 것으로 추상 객체에 사용할 수 있는 오퍼레이션을 이용해 규정된다. 추상 데이터 객체를 사용할 때 프로그래머는 오직 객체가 외부에 제공하는 행위에만 관심을 가지며 행위가 구현되는 세부적인 사항에 대해서는 무시한다.
[Liskov74]

추상 데이터 타입을 구현하기 위해서는 아래와 같은 언어의 지원이 필요하다.

  • 타입 정의 선언
  • 타입의 인스턴스를 다룰 수 있는 오퍼레이션의 집합의 정의
  • 데이터를 외부로부터 보호
    • 제공된 오퍼레이션을 통해서만 조작
  • 하나의 타입에 대해 복수의 인스턴스를 생성

클래스

클래스는 추상 데이터 타입인가?

클래스추상 데이터 타입
상속과 다형성 지원 O상속과 다형성 지원 X
객체 지향 프로그래밍객체기반 프로그래밍

클래스와 추상 데이터 타입은 다르다. 클래스는 절차를 추상화한 것이고, 추상 데이터 타입은 타입을 추상화 한 것이기 때문이다.

추상 데이터 타입이 오퍼레이션을 기준으로 타입을 묶는 방법이라면, 클래스는 타입을 기준으로 오퍼레이션을 묶는다.
즉, 복수의 타입을 명시적으로 정의하고 두 타입에 관련된 오퍼레이션의 실행 절차를 두 타입에 분배한다.
절차 분배시에 발생하는 동일한 동작은 간단하게 부모 클래스에 정의하고 이를 상속 받는 것으로 제공할 수 있다.

클라이언트의 관점에서 두 클래스의 인스턴스는 동일하게 보인다. 내부에서 수행되는 절차는 다르지만, 클래스를 이용한 다형성은 절차에 대한 차이점을 감춘다.