•
인지 과부화 = 문제 해결에 필요한 요소의 수가 단기 기억의 용량을 초과하는 순간
•
추상화 = 불필요한 정보를 제거해 현제의 문제 해결에 필요한 핵심만 남기는 작업
•
분해 = 큰 문제를 해결 가능한 작은 문제로 나누는 작업
•
큰 문제를 분해하여 추상화 시킴으로서 인지능력을 올릴 수 있다.
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. 변경을 기준으로 선택하라
•
단순히 클래스를 구현 단위로 사용한다는 것이 객체지향 프로그래밍을 한다는 것을 의미하지는 않는다.
◦
절차를 추상화하지 않았다면 그것은 객체지향 분해가 아니다.
•
클래스가 추상 데이터 타입의 개념을 따르는지를 확인할 수 있는 가장 간단한 방법은 클래스 내부 인스턴스의 타입을 표현하는 변수가 있는지 살펴보는 것이다.
◦
인스턴스 변수에 저장된 값을 기반으로 메서드 내에서 타입을 명시적으로 구분하는 방식은 객체지향을 위반하는 것이다.