////
Search

7장 - 객체 분해

Date
2024/03/02 16:02
Tags
인지 과부화 = 문제 해결에 필요한 요소의 수가 단기 기억의 용량을 초과하는 순간
추상화 = 불필요한 정보를 제거해 현제의 문제 해결에 필요한 핵심만 남기는 작업
분해 = 큰 문제를 해결 가능한 작은 문제로 나누는 작업
큰 문제를 분해하여 추상화 시킴으로서 인지능력을 올릴 수 있다.

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

현대적 프로그래밍 언어의 특징 두 가지 추상화 매커니즘
프로시저 추상화 = 무엇을 해야 하는가
기능 분해 → 알고리즘 분해
데이터 추상화 = 무엇을 알아야 하는가
타입을 추상화 = 추상 데이터 타입
프로시저를 추상화 = 객체지향

1.1. 메인 함수로서의 시스템

기능과 데이터 분해 중 기능 분해를 많이 사용해 왔었다.
이런 시스템 분해를 알고리즘 분해 혹은 기능 분해라고 부른다.
기능 분해의 관점에서 추상화의 단위는 프로시저이며 시스템은 프로시저를 단위로 분해된다.
반복적으로 실행되거나 거의 유사하게 실행되는 작업들을 하나의 장소에 모아 로직을 재사용하고 중복을 방지할 수 있는 추상화 방법
프로시저는 상세한 구현을 몰라도 인터페이스만 알면 사용 가능하기에 추상화라 부를 수 있다.
다만 정보 은닉체계를 구축하는데는 한계가 있다.
전통적 기능 분해 방법은 하향식 접근법(Top-Down Approach)을 따른다.
시스템을 구성하는 최상위 기능을 정의하고, 이 최상위 기능을 좀 더 작은 하위 기능으로 분해해 나가는 방법을 말한다.
분해는 세분화된 마지막 하위 기능을 구현 가능한 수준이 될 때까지 계속된다.
상위 기능은 하나 이상의 더 간단하고 더 구체적이며 덜 추상적인 하위 기능의 집합으로 분해된다.

1.2. 급여 관리 시스템 (기능 분해)

직원의 급여를 계산한다 사용자로부터 소득세율을 입력받는다 “세율을 입력하세요: “라는 문장을 화면에 출력한다 기보드를 통해 세율을 입력받는다 직원의 급여를 계산한다 전역 변수에 저장된 직원의 기본급 정보를 얻는다 급여를 계산한다 양식에 맞게 결과를 출력한다 "이름: {직원명}, 급여: {계산된 금액}" 형식에 따라 출력 문자열을 생성한다
Plain Text
복사
기능적 분해의 예시
기능 분해 방법에서는 기능을 중심으로 필요한 데이터를 결정한다.
기능 분해라는 무대의 주연은 기능이며 데이터는 기능을 보조하는 조연의 역할에 머무른다.
하향식 기능 분해는 논리적이고 체계적인 시스템 개발 절차를 제시한다.
커다란 기능을 좀 더 작은 기능으로 단계적으로 정제해 가는 과정은 구조적이며 체계적인 동시에 이상적인 방법으로까지 보인다.
But. 현실은 이와 다르게 체계적이거나 이상적이지 않아 불규칙/불완전한 인간과 만나는 지점에서 혼란과 동요가 발생한다.

1.3. 하향식 기능 분해의 문제점

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

1.3.1. 하나의 메인 함수라는 비현실적인 아이디어

어떤 시스템도 최초에 릴리스됐던 당시의 모습을 그대로 유지하지는 않는다.
시간이 지나 새로운 요구사항을 도출해나가면서 지속적으로 새로운 기능을 추가하게 된다.
이것은 시스템이 오직 하나의 메인 함수만으로 구현된다는 개념과는 완전히 모순된다.
대부분의 경우 추가되는 기능은 최초에 배포된 메인 함수의 일부가 아닐 것이다.
결국 처음에 중요하게 생각했던 메인 함수는 동등하게 중요한 여러 함수들 중 하나로 전락하고 만다.
하향식 접근법은 하나의 알고리즘을 구현하거나 배치 처리를 구현하기에는 적합하지만 현대적인 상호작용 시스템을 개발하는 데는 적합하지 않다.

1.3.2. 메인 함수의 빈번한 재설계

시스템 안에는 여러 개의 정상이 존재하기 때무에 결과적으로 하나의 메인 함수를 유일한 정상으로 간주하는 하향식 기능 분해의 경우 새로운 기능을 추가할 때마다 매번 메인 함수를 수정해야 한다.
결과적으로 기존 코드의 빈번한 수정으로 인한 버그 발생 확률이 높아지기 때문에 시스템 변경에 취약해 질 수 밖에 없다.

1.3.3. 비즈니스 로직과 사용자 인터페이스의 결합

로직 설계 초기 단계부터 입력과 출력 양식을 함께 고민하길 강요받게 된다.
비즈니스 로직과 사용자 인터페이스가 변경되는 빈도가 다르다.
때문에 하향식 접근법은 근본적으로 변경에 불안정한 아키텍처를 낳는다.
관심사의 분리라는 아키텍처 설계의 목적을 달성하기 어렵다.

1.3.4. 성급하게 결정된 실행 순서

하향식 분해는 하나의 함수를 더 작은 함수로 분해하고, 분해된 함수들의 실행 순서를 결정하는 작업으로 요약 가능하다.
이는 설계 시작 단계부터 시스템이 무엇을 해야하는지가 아닌 어떻게 동작해야 하는지에 집중하도록 만든다.
하향식 접근법을 통해 분해된 함수들은 재사용이 어려워지게 된다.
하향식 설계는 상위 함수가 강요하는 문맥에 강하게 결합되기 때문에 결합도가 높아지게 된다.

1.3.5. 데이터 변경으로 인한 파급효과

하향식 기능 분해의 가장 큰 문제점으로 어떤 데이터를 어떤 함수가 사용하고 있는지를 추적하기 어렵다는 것이다.
데이터 변경으로 인한 영향을 최소화 하기 위해선 변경되는 부분과 아닌 부분을 명확히 분리해야 한다.
기능 분해의 본질적 문제를 해결하기 위해 나온 개념이 정보 은닉과 모듈이라는 개념이다.

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

하향식 분해가 유용한 경우도 있는데 설계가 어느정도 안정된 후 다양한 측면을 논리적으로 설명하고 문서화하기에 용이하기 때문이다.
하향식 분해는 작은 프로그램과 개별 알고리즘을 위해서는 유용한 패러다임으로 남아있다.
실제로 동작하는 커다란 소프트웨어를 만들기에 적합한 방식은 아니다.

3. 모듈

3.1. 정보 은닉과 모듈

시스템의 변경을 관리하는 기본적인 전략은 함께 변경되는 부분을 하나의 구현 단위로 묶고 퍼블릭 인터페이스를 통해서만 접근하도록 하는 것이다.
즉, 기능을 기반으로 시스템을 분해하는것이 아닌 변경의 방향에 맞춰 시스템을 분해하는 것이다.
정보 은닉 (information hiding)
소프트웨어 개발에서 가장 중요하지만 가장 많이 오해를 받는 개념
자주 변경되는 부분을 상대적으로 덜 변경되는 안정적인 인터페이스 뒤로 감추는게 핵심
외부에 감춰야 하는 비밀에 따라 시스템을 분해하는 모듈 분할 원리
모듈과 기능 분해는 상호 배타적인 관계가 아니며 시스템 모듈 분해 후 내부 모듈 구현을 위해 기능 분해를 적용할 수 있다.
시스템은 감춰야하는 비밀을 찾아 숨겨야하는 부분을 찾아내어 감추도록 하자
모듈이 감춰야 하는 두 가지 비밀
복잡성: 모듈이 너무 복잡한 경우 이해하고 사용하기가 어렵다.
외부에 모듈을 추상화할 수 있는 간단한 인터페이스를 제 공해서 모듈의 복잡도를 낮춘다.
변경 가능성: 변경 가능한 설계 결정이 외부에 노출될 경우 실제로 변경이 발생했을 때 파급효과가 커진다.
변경 발생 시 하나의 모듈만 수정하면 되도록 변경 가능한 설계 결정을 모듈 내부로 감추고 외부에는 쉽게 변경되지 않을 인터페이스를 제공한다.
일반적으로 시스템의 비밀은 데이터지만 반드시 데이터가 비밀일 필요는 없다.

3.2. 모듈의 장점과 한계

모듈의 장점
모듈 내부의 변수가 변경되더라도 모듈 내부에만 영향을 미친다.
모듈을 사용하면 모듈 내부에 정의된 변수를 직접 참조하는 코드의 위치를 모듈 내부로 제한할 수 있다.
모듈은 데이터 변경으로 인한 파급효과를 제어할 수 있기 때문에 코드를 수정하고 디버깅하기가 더 용이하다.
비즈니스 로직과 사용자 인터페이스에 대한 관심사를 분리한다.
전역 변수와 전역 함수를 제거함으로써 네임스페이스 오염(namespace pollution)을 방지한다.
모듈의 한 가지 용도는 네임스페이스를 제공하는 것이다.
모듈은 전역 네임스페이스의 오염을 방지하는 동시에 이름 충돌(name collision)의 위험을 완화한다.
모듈 내부는 높은 응집도와 낮은 결합도를 유지한다.
비록 모듈이 프로시저 추상화보다는 높은 개념의 추상화를 제공하지만 태생적으로 변경을 관리하기 위한 기법이기에 한계적이 명확하다.
큰 단점으로 인스턴스 개념을 제공하지 않는다.
인스턴스가 존재하는 추상화 매커니즘이 필요한 경우 사용하기 부족하다.

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

4.1. 추상 데이터 타입

프로그래밍 언어에서 타입이란 변수에 저장할 수 있는 내용물의 종류와 변수에 적용될 수 있는 연산의 가짓수를 의미한다.
프로그래밍 언어는 다양한 형태의 내장 타입을 제공하고, 과거에 사용되던 절차형 언어들은 적은 수의 내장 타입만을 제공하고 새로운 타입 추가가 제한적 이었다.
바바라 리스코프는 프로시저 추상화의 한계를 인지하고 이를 보완하기 위해 데이터 추상화의 개념을 도입했다.
추상 데이터 타입을 구현하려면 다음과 같은 특성을 위한 프로그래밍 언어의 지원이 필요하다.
타입 정의를 선언할 수 있어야 한다.
타입의 인스턴스를 다루기 위해 사용할 수 있는 오퍼레이션의 집합을 정의할 수 있어야 한다.
제공된 오퍼레이션을 통해서만 조작할 수 있도록 데이터를 외부로부터 보호할 수 있어야 한다.
타입에 대해 여러 개의 인스턴스를 생성할 수 있어야 한다.
이를 제공하지 않아도 추상 데이터 타입 구현은 가능하지만 언어 차원에서 지원하는 것과 관습과 약속, 기법을 통해 추상 데이터 타입을 모방하는 것을 완전히 다른 이야기다.
추상 데이터 타입은 사람들이 세상을 바라보는 방식에 좀 더 근접해지도록 추상화 수준을 향상시킨다.

5. 클래스

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

클래스와 추상 데이터 타입 모두 추상화를 기반으로 하지만 상속과 다형성이라는 핵심적인 차이점이 존재한다.
상속과 다형성을 지원 = 객체지향 프로그래밍
지원하지 않음 = 객체기반 프로그래밍
타입 추상화
추상 데이터 타입을 의미한다.
오퍼레이션을 기준으로 타입을 묶는 방법
절차 추상화 타입
클래스를 의미한다.
타입을 기준으로 오퍼레이션을 묶는다.
동일한 메세지에 다르게 반응할 수 있다.
클라이언트 관점에서 다형적 객체는 어떤 절차가 수행되는지 감춰져 있다.
즉 객체지향은 절차 추상화다.

5.2. 변경을 기준으로 선택하라

단순히 클래스를 구현 단위로 사용한다는 것이 객체지향 프로그래밍을 한다는 것을 의미하지는 않는다.
절차를 추상화하지 않았다면 그것은 객체지향 분해가 아니다.
클래스가 추상 데이터 타입의 개념을 따르는지를 확인할 수 있는 가장 간단한 방법은 클래스 내부 인스턴스의 타입을 표현하는 변수가 있는지 살펴보는 것이다.
인스턴스 변수에 저장된 값을 기반으로 메서드 내에서 타입을 명시적으로 구분하는 방식은 객체지향을 위반하는 것이다.