7장 정리
사람은 문제 해결에 필요한 요소의 수가 단기 기억의 용량을 초과하는 순간 문제 해결 능력이 급격하게 떨어진다. 이를 인지 과부하라고 부른다.
이런 인지 과부하를 피하기 위해 불필요한 정보를 제거하고 현재의 문제 해결에 필요한 핵심만 남기는 작업을 추상화라고 한다.
맞닥뜨린 문제가 한 번에 해결하기 어려울 정도로 크다면 해결 가능한 작은 문제로 나누는 작업을 분해라고 한다.
인류는 이런 추상화와 분해를 통해 복잡한 분야의 문제를 해결해왔다.
프로시저 추상화와 데이터 추상화
어셈블리어, 고수준 언어 같은 프로그래밍 언어의 발전은 효과적인 추상화를 이용해 복잡성을 극복하려는 개발자의 노력에서 출발했다.
프로그래밍 언어를 통해 표현되는 추상화의 발전은 다양한 프로그래밍 패러다임의 탄생으로 이어졌다.
프로그래밍 패러다임은 프로그래밍을 구성하기 위해 사용되는 추상화의 종류와 이 추상화를 이용해 소프트웨어를 분해하는 방법의 두 가지 요소로 결정되며, 따라서 추상화와 분해의 관점에서 설명할 수 있다.
현대적인 프로그래밍 언어를 특징 짓는 중요한 두 가지 추상화 메커니즘은 프로시저 추상화와 데이터 추상화이다.
프로시저 추상화
- 소프트웨어가 무엇을 해야 하는지를 추상화한다.
데이터 추상화
- 소프트웨어가 무엇을 알아야 하는지를 추상화한다.
프로그래밍 패러다임이란 적절한 추상화의 윤곽을 따라 시스템을 어떤 식으로 나눌 것인지를 결정하는 원칙과 방법의 집합이다.
시스템을 분해하는 방법을 결정하려면 프로시저 추상화를 중심으로 할 것인지, 데이터 추상화를 중심으로 할 것인지를 결정해야 한다.
프로시저 추상화 중심
- 기능분해
데이터 추상화 중심
- 데이터를 중심으로 타입을 추상화 하면 추상 데이터 타입
- 데이터를 중심으로 프로시저를 추상화하면 객체지향
객체지향 패러다임은 역할과 책임을 수행하는 자율적인 객체들의 협력 공동체를 구축하는 것이다.
객체지향 패러다임에서 '역할과 책임을 수행하는 객체'가 추상화를 의미하고, 기능을 '협력하는 공동체'를 구성하도록 객체들로 나누는 과정이 분해를 의미한다.
하향식 기능 분해의 문제점
전통적인 기능 분해 방법은 시스템을 구성하는 가장 최상위 기능을 정의하고, 여기서부터 좀 더 작은 단계의 하위 기능으로 분해하는 하향식 접근법을 따른다.
분해는 세분화된 마지막 하위 기능이 프로그래밍 언어로 구현 가능한 수준이 될 때 까지 계속되며, 각 세분화 단계는 바로 위 단계보다 더 구체적이어야 한다.
하향식 기능 분해는 시스템을 최상위의 가장 추상적인 메인 함수로 정의하고, 메인 함수를 구현 가능한 수준까지 세부적인 단계로 분해한다.
이상적인 방법으로 보일 수 있지만 우리가 사는 세계는 그렇게 체계적이지도, 이상적이지도 않기 때문에 아래와 같은 다양한 문제가 발생한다.
- 시스템은 하나의 메인 함수로 구성돼 있지 않다.
- 기능 추가나 요구사항 변경으로 인해 메인 함수를 빈번하게 수정해야 한다.
- 비즈니스 로직이 사용자 인터페이스와 강하게 결합된다.
- 하향식 분해는 너무 이른 시기에 함수들의 실행 순서를 고정시키기 때문에 유연성과 재사용성이 저하된다.
- 데이터 형식이 변경될 경우 파급 효과를 예측할 수 없다.
데이터 추상화와 추상 데이터 타입 / 클래스
타입을 사용했지만
초기 절차형 프로그래밍 언어에서는 적은 수의 내장타입만을 제공했고, 새로운 타입을 추가하는 것이 불가능하거나 제한적이었다.
프로시저 추상화로는 프로그램의 표현력을 향상시키는데 한계가 있었고, 이를 보완하기 위해 데이터 추상화의 개념이 나왔다.
추상 데이터 타입과 클래스의 차이는 아래와 같다.
- 추상 데이터 타입은 오퍼레이션을 기준으로 타입을 묶는다.
- 클래스는 타입을 명시적으로 정의하고 타입의 유형을 기준으로 오퍼레이션을 지정한다.
클래스는 타입을 기준으로 절차들을 추상화 함으로써 다형성을 이용할 수 있다.
객체지향은 단순히 클래스를 구현 단위로 사용하는 것을 의미하지 않는다.
클래스를 사용하더라도 타입을 기준으로 절차를 추상화하지 않았다면 그것은 객체지향 분해가 아니다.
객체지향에서는 클라이언트가 객체의 타입을 확인한 후 적절한 메서드를 호출하는 것이 아니라, 객체가 메시지를 처리할 적절한 메서드를 선택한다.
객체지향에서는 타입 변수를 이용한 조건문을 다형성으로 대체하고, 이로써 새로운 로직을 추가해도 클라이언트 코드를 수정하지 않아도 된다.
하지만 항상 절차를 추상화하는 객체지향 설계 방식을 따라야하는 것은 아니다.
설계는 변경과 관련된 것이므로 변경의 방향성과 발생 빈도에 따라 결정해야한다.
변경의 방향성이 타입 추가인지, 오퍼레이션 추가인지에 따라 추상 데이터 타입과 클래스를 적절하게 선택해야 한다.
타입 계층과 다형성은 협력이라는 문맥 안에서 책임을 수행하는 방법에 관해 고민한 결과물이어야 하며, 그 자체가 목적이 되어서는 안 된다.
인상깊었던 점
"다형성은 협력이라는 문맥 안에서 책임을 수행하는 방법에 관해 고민한 결과물이어야 하며, 그 자체가 목적이 되어서는 안 된다."
이번 주에는 마지막 쪽에 있던 이 말이 가장 와닿았다.
항상 코드를 짤 때 "재사용 할 수 있고 변경에 더 좋은 설계를 고민해야 한다"는 생각을 하는데, 최근 들어 필요해서 객체지향 설계를 적용하는게 아닌, 객체지향 자체가 목적이 된 것 같다는 생각이 자주 들었다.
객체지향적으로 코드를 짤 땐 과연 쓸데 없는 노력을 쓰는건 아닌지 다시 생각을 해보면서 주의해야겠다는 생각이 들었다.