7장

객체 분해

인지과부하를 방지하는 가장 좋은 방법은 단기 기억 안에 보관할 정보의 양을 조절하는 것으로 불필요한 정보를 제거하고 현재의 문제 해결에 필요한 핵심만 남기는 작업을 추상화라고 한다. 큰 문제를 해결 가능한 작은 문제 단위로 나누는 작업을 분해(decomposition)라고 부른다. 11자리로 조합된 정수 8가지를 외우는 것은 어렵지만 8명의 휴대번호를 외우는 것은 그렇게 어렵지 않다. 단기 기억에 담을 수 있는 추상화의 수에는 한계가 있지만 추상화를 더 큰 규모의 추상화로 압축시키면 단기기억의 한계를 초월할 수 있고 일반적인 문제 해결 방법이다. 이 방법을 소프트웨어 개발에 사용해보자.

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

현대적인 프로그래밍 언어를 특정 짓는 중요한 두 가지 추상화 메커니즘은 프로시저 추상화데이터 추상화다. 프로시저 추상화는 소프트웨어가 무엇을 해야 하는지 추상화하고 데이터 추상화는 소프트웨어가 무엇을 알아야 하는지를 추상화한다.

  • 프로시저 추상화

    • 기능 분해, 알고리즘 분해 두 가지로 불린다.

  • 데이터 추상화

    • 타입을 추상화(추상 데이터 타입)

    • 프로시저를 추상화(객체지향)

기능을 협력하는 공동체를 구성하도록 객체들로 나누는 과정이 객체지향 패러다임에서의 분해다. 객체지향은 데이터를 중심으로 데이터 추상화와 프로시저 추상화를 통합한 객체를 이용해 시스템을 분해하는 방법이다.

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

하향식 급여 관리 시스템

기능과 데이터의 첫 번째 경합에서 기능이 우선시됐다. 전통적인 기능 분해 방법은 하향식 접근법을 따른다. 최상위 기능을 좀 더 작은 단위의 하위 기능으로 분해해 나간다.

급여 관리 시스템을 예로 들면

  • 직원의 급여를 계산한다.

  • 직원의 급여를 계산한다.

    • 사용자로부터 소득세율을 입력받느다.

    • 직원의 급여를 계산한다.

    • 양식에 맞게 결과를 출력한다.

  • 직원의 급여를 계산한다.

    • 사용자로부터 소득세율을 입력받는다.

      • 세율을 입력하세요 라는 문장을 화면에 출력한다.

      • 키보드를 통해 세율을 입력받는다.

    • 직원의 급여를 계산한다.

      • 전역 변수에 저장된 직원의 기본급 정보를 얻는다.

      • 급여를 계산한다.

    • 양식에 맞게 결과를 출력한다.

      • 이름: {직원명}, 급여: {계산된 금액} 형식에 따라 출력 문자열을 생성한다.

이런 식으로 각 단계에서 불완전하고 좀 더 구체화될 수 있는 문장들이 남아있는지 검토한다. 좀 더 정제 가능한 문장이 존재하면 구현이 가능할 정도로 저수준의 문장이 될 때까지 기능을 분해해야 한다.

기능 분해 방법은 기능을 중심으로 필요한 데이터를 결정한다. 기능 분해애서 주연은 기능이며 데이터는 조연의 역할에 불과하다. 데이터는 기능을 분해하고 정제하는 과정에서 종류와 저장 방식이 식별된다.

하향식 기능 분해의 문제점

  • 시스템은 하나의 메인 함수로 구성돼 있지 않다.

  • 기능 추가나 요구사항 변경으로 인해 메인 함수를 빈번하게 수정해야 한다.

  • 비즈니스 로직이 사용자 인터페이스와 강하게 결합된다.

  • 너무 이른 시기에 함수들의 실행 순서를 고정시키기 때문에 유연성과 재사용성이 저하된다.

  • 데이터 형식이 변경될 경우 파급효과를 예측할 수 없다.

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

대부분의 추가되는 기능은 최초의 메인 함수의 일부가 아닐 것이다. 메인 함수는 여러 함수들 중 하나로 전락하고 만다.

메인 함수의 빈번한 재설계

새로운 기능을 추가할 때마다 매번 메인 함수를 수정해야 한다. 기존 로직과 상관없는 새로운 기능을 추가할 때마다 메인 함수를 수정해야 한다.

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

급여 계산 기능의 사용자로부터 소득세율을 입력받아 급여를 계산한 뒤 계산된 결과를 화면에 출력한다 는 급여 계산 비즈니스 로직과 소득세율을 입력받아 결과를 화면에 출력하는 인터페이스 관심사가 한데 섞여있게해 서로 밀접하게 결합된다.

사용자 인터페이스는 변경이 잦고 비즈니스 로직은 그에 비해 변경이 적게 발생한다. 하지만 결합이슈로 인터페이스의 변경에 비즈니스 로직이 영향을 받게 된다.

성급하게 결정된 실행 순서

하향식 기능 분해 과정은 시스템이 무엇을 해야 하는지가 아니라 어떻게 동작해야 하는지에 집중하게 만든다. 하향식 접근법은 구현을 염두에 두기 때문에 함수들의 실행 순서를 정의하는 시간 제약을 강조한다. 함수들의 실행 순서를 미리 결정하지 않는다면 기능분해를 할 수 없다.

이를 해결하는 방법이 객체 사이의 논리적 관계를 중심으로 설계를 이끌어 나가는 객체지향이다.

하향식 접근법은 함수의 재사용에도 취약한데 상위 함수를 분해하는 과정에서 상위 함수가 강요하는 문맥 안에서만 의미를 가지기 때문이다. 이는 상위 시스템과의 강한 결합도를 의미하고 변경에 취약함을 의미한다.

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

어떤 데이터를 어떤 함수가 사용하고 있는지 추적하기 어렵기 때문에 데이터 변경으로 어떤 함수가 영향을 받을지 예상하기 어렵다.

데이터 변경으로 인한 영향을 최소화하려면 데이터와 함께 변경되는 부분과 그렇지 않은 부분을 명확하게 분리해야 하는데 이를 위해 데이터와 함께 변경되는 부분을 하나의 구현 단위로 묶고 외부 함수를 통해서만 데이터에 접근해야 한다.

하향식 분해의 장점

하향식 분해는 작은 프로그램과 개별 알고리즘을 위해서는 유용한 패러다임이다.

3. 모듈

정보 은닉과 모듈

정보 은닉은 시스템을 모듈 단위로 분해하기 위한 기본 원리로 자주 변경되는 부분을 덜 변경되는 인터페이스 뒤로 감춰야 한다는 것이 핵심이다.

모듈과 기능 분해는 상호 배타적 관계가 아니라 비밀을 결정하고 시스템을 모듈로 분해한 후에 각 모듈 내부를 구현하기 위해 기능 분해를 적용할 수 있다.

모듈이 감춰야 하는 두 가지 비밀은 다음과 같다.

  • 복잡성: 모듈이 복잡한 경우 이해하고 사용하기 어렵기 때문에 외부에 모듈을 추상화할 수 있는 간단한 인터페이스를 제공해서 모듈의 복잡도를 낮춘다.

  • 변경 가능성: 변경 발생 시 하나의 모듈만 수정하면 되도록 변경 가능한 설계 결정을 모듈 내부로 감추고 외부에는 변경되지 않을 인터페이스를 제공한다.

변경시 시스템이 굴복하는 경우는 대부분 데이터가 변경되는 경우기 때문에 데이터를 자신의 비밀로 삼는 모듈을 만들면 된다.

모듈의 장점과 한계

모듈 내부의 변수가 변경되더라도 모듈 내부에만 영향을 미친다.

어떤 데이터가 변경됐을 때 영향을 받는 함수를 찾기 위해 해당 데이터를 정의한 모듈만 검색하면 되기 때문에 코드 수정과 디버깅에 용이하다.

비즈니스 로직과 사용자 인터페이스에 대한 관심사를 분리한다.

사용자 입력과 화면 출력을 모듈 외부에 둠으로써 GUI에 사용자 인터페이스를 추가해도 비즈니스 로직은 변경되지 않는다.

전역변수와 전역 함수를 제거함으로써 네임스페이스 오염을 방지한다.

변수와 함수를 모듈 내부에 포함시키기 때문에 다른 모듈에서도 동일한 이름을 사용할 수 있게 된다.

모듈은 감춰야 할 데이터를 결정하고 이 데이터를 조작하는데 필요한 함수를 결정한다. 기능이 아닌 데이터를 중심으로 시스템을 분해하는 것으로 데이터와 함수가 통합된 한 차원 높은 추상화를 제공하는 설계 단위다.

그럼에도 모듈의 가장 큰 단점은 인스턴스를 제공하지 않는다. Employee 모듈은 회사에 속한 모든 직원의 정보를 가지고 있는 모듈로 더 높은 수준의 추상화는 개별 직원을 독립적 단위로 다룰 수 있어야 한다. 이를 만족시키기 위해 등장한 개념이 추상 데이터 타입이다.

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

추상 데이터 타입

절차형 프로그래밍 언어들은 적은 수의 내장 타입만을 제공했고 현대의 프로그래밍 언어들은 다양한 내장 타입을 제공한다. 절차형 프로그래밍 언어에서는 이를 추상 데이터 타입으로 구현할 수 있다.

추상 데이터 타입을 구현하려면

  • 타입 정의를 선언할 수 있어야 한다.

  • 타입의 인스턴스를 다루기 위해 사용할 수 있는 오퍼레이션의 집합을 정의할 수 있어야 한다.

  • 제공된 오퍼레이션을 통해서만 조작할 수 있도록 데이터를 외부로부터 보호할 수 있어야 한다.

  • 타입에 대해 여러 개의 인스턴스를 생성할 수 있어야 한다.

추상 데이터 타입으로 객체를 생성하는 것은 가능하지만 여전히 데이터와 기능을 분리해서 본다. struct는 말 그대로 시스템의 상태를 저장할 데이터를 표현한다. 기능을 구현하는 핵심 로직은 추상 데이터 타입 외부에 존재한다.

5. 클래스

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

추상 데이터 타입과 클래스는 명확한 의미에서는 동일하지 않다. 클래스는 상속과 다형성을 지원하는데 비해 추상 데이터 타입은 지원하지 못한다. 클래스 기반 객체지향 프로그래밍 언어와 구분하기 위해 상속과 다형성을 지원하지 않는 추상 데이터 타입 기반의 프로그래밍 패러다임을 객체기반 프로그래밍이라고 부르기도 한다.

추상 데이터 타입은 타입을 추상화 한 것이고 클래스는 절차를 추상화한 것이다.

변경을 기준으로 선택하라

클래스를 구현 단위로 선택하는 것이 객체지향 프로그래밍을 의미하지는 않는다.

객체지향에서는 타입 변수를 이용한 조건문을 다형성으로 대체한다. 기본 코드에 아무런 영향도 미치지 않고 새로운 객체 유형과 행위를 추가할 수 있는 객체지향의 특성을 개방-폐쇄 원칙이라고 부른다.

타입 추가라는 변경의 압력이 더 강한 경우 객체지향이 유용하다. 이에 반해 변경의 주된 압력이 오퍼레이션의 추가라면 추상 데이터 타입이 유리하다.

협력이 중요하다.

객체를 설계하는 방법은 책임 주도 설계의 흐름을 따라야 한다.

Last updated