본문 바로가기

CS

객체 지향 프로그래밍의 배경

도입

 현재 널리 사용되는 프로그래밍 패러다임에는 절차적 프로그래밍, 객체지향 프로그래밍, 함수형 프로그래밍이 있다. 이 중 가장 많이 사용되고, 중요하다고 생각하는 프로그래밍 패러다임을 하나 고른다면 대부분의 사람들은 객체지향 프로그래밍을 선택할 것이다. 많은 언어들이 클래스를 시작으로 상속, 인터페이스 등의 객체지향과 관련된 기능을 포함하고 있으며, 실제로 이를 이용하여 개발하는 경우가 많기 때문이다.

 그렇다면, 객체지향 프로그래밍 방식은 왜 널리 사용되고, 중요한 패러다임으로 자리 잡았을까? 이번 글에서는 객체지향이 왜 등장한 배경을 다뤄보고자 한다.


소프트웨어의 복잡함

객체지향을 논하기에 앞서, 개발을 통해 달성하고자 하는 목표인 소프트웨어에 대해 생각해 보자.

세상에는 정말 다양한 요구사항이 있으며, 프로그래머는 이러한 다양한 요구사항을 소프트웨어를 통해 해결한다. 음식을 데칠 때 사용되는 전자레인지부터 온라인 쇼핑 같은 웹 서비스까지, 우리의 삶과 업무를 좀 더 편리하게, 생산성 있게 만드는 소프트웨어는 더 이상 현대 사회와 떼어놓을 수 없는 존재이다.

이때 소프트웨어는 본질적으로 컴퓨터에게 내리는 일련의 명령 모음이다. 컴퓨터는 0과 1의 이진 신호만 받아들일 수 있으며, 입력된 신호를 해석하여 미리 정해져 있는 명령을 수행한다. 따라서 개발자는 신호와 동작을 잘 조합하여 문제를 해결할 수 있는 소프트웨어를 만들어야 한다.

 문제는 사람 입장에서 해석하기 힘든 이진수 또는 명령 레벨의 것들을 조합해서 소프트웨어를 만들기가 정말 어렵다는 것이다. 소프트웨어가 필요한 이유가 복잡한 문제를 쉽게 해결하기 위해서인데, 소프트웨어를 만드는 것도 이해하기 어렵고 복잡하다면, 이건 사실상 문제가 해결된 것이 아니다. 그렇다면, 이 문제는 어떻게 해야 해결할 수 있을까?

소프트웨어와 관점

 컴퓨터가 0과 1의 이진 신호를 인식한다는 점, 소프트웨어가 0과 1로 이루어진 일련의 명령이라는 점을 부정하고 바꿀 수는 없다. 바꿀 수 있는 것은 사람이 소프트웨어를 바라보는 관점뿐이다.

 따라서 소프트웨어를 바라보는 사람의 관점을 바꿔 복잡한 명령의 모음을 보다 이해할 수 있는 단위로 바꿀 필요가 있다. 복잡한 그대로 두는 대신, 인간이 이해할 수 있는 단위로 나눠 좀 더 간단하게 소프트웨어를 바라봐야 한다.

사람의 추상화 능력

 사람은 태어날 때부터 복잡한 것을 단순하게 바라보는 추상화 능력을 가지고 있다. 자동차가 매우 복잡함에도 불구하고 운전할 수 있는 이유는 운전에 꼭 필요한 핵심만 추려 간단하게 이해할 수 있기 때문이다. 자동차에는 수많은 부품과 복잡한 동작 원리를 가지고 있지만, 운전에 정말 필요한 정보는 몇 가지에 불과하다.

  1. 액셀을 밟으면 앞으로 나간다.
  2. 브레이크를 밟으면 멈춘다.
  3. 핸들을 돌리면 방향을 꺾을 수 있다.

 이처럼 사람은 복잡한 세상의 세부사항을 있는 그대로 받아들이는 대신, 추상화를 통해 핵심만 단순하게 이해한다.

사람의 추상화 능력

추상화는 사람이 가진 근본적인 능력이다. 추상화를 통해 복잡한 세상을 단순하게 이해하는 것처럼, 소프트웨어를 추상화하여 좀 더 이해하기 쉽게, 기능이라는 핵심에 집중할 수 있도록 만들어보자.

프로그래밍 언어와 초기 관점

 이 추상화 능력을 소프트웨어에 도입해 보자. 0과 1로 구성된 일련의 명령을 "덧셈 연산" 또는 "변수"로 해석해 보는 것이다. 이렇듯 이진 신호를 보다 사람이 이해하기 쉬운 언어와 로직으로 추상화하는 과정에서 "프로그래밍 언어"가 등장한다.

 

다양한 프로그래밍 언어들. 출처: https://gowithcode.com/top-programming-languages

 

 "프로그래밍 언어"가 등장했으니, 이제 전체 로직이 마냥 이해하기도 쉽고 모든 문제가 해결되었을까? 그렇지는 않다. 프로그래밍 언어는 이진 신호를 사람이 이해하기 쉬운 논리 및 키워드(if, let, for, while)로 표현할 방법을 제공하여 조금 더 이해하기 쉬운 명령 모음으로 표현했을 뿐 여전히 이해하고 유지보수하기 어렵다.

사람 1이 청소기를 꺼낸다
1번 방으로 이동한다
플러그를 잡는다
현재 위치에서 가까운 콘센트를 찾는다
콘센트에 플러그를 꽂는다
청소기 손잡이를 잡는다
청소하고 싶은 위치로 이동한다(반복)
청소기 손잡이를 놓는다.
콘센트를 분리한다.
콘센트 선을 정리한다
청소기를 넣는다
사람 1이 청소기를 꺼낸다
2번 방으로 이동한다
플러그를 잡는다
현재 위치에서 가까운 콘센트를 찾는다
콘센트에 플러그를 꽂는다
청소기 손잡이를 잡는다
청소하고 싶은 위치로 이동한다(반복)
청소기 손잡이를 놓는다.
콘센트를 분리한다.
콘센트 선을 정리한다
청소기를 넣는다

위 내용은 컴퓨터가 수행할 명령을 한글로 표현한 것으로, 명령들을 읽어 보면 어떤 방을 청소하는 일련의 로직을 표현하고 있음을 알 수 있다. 초기 개발자들은 이처럼 소프트웨어를 컴퓨터가 실행할 일련의 명령 모음으로 바라봤다. 프로그래밍 언어를 이용하여 사람이 비교적 이해하기 쉬운 "컴퓨터가 실행할 일련의 명령"을 작성했던 것이다.

 그런데, 비교적 이해하기 쉬운 명령이라고 하더라도 규모가 커지면 다시 이해하기 어려워진다. 위 명령이 포함된 프로그램이 커져서 10만 줄 단위가 되었고, 청소기가 변경되어 무선 청소기를 이용하도록 청소 로직을 일괄 수정해야 한다고 생각해 보자. 이를 위해 필요한 과정은 다음과 같다.

  1. 10만 줄이 넘는 명령에서 청소와 관련된 명령들의 위치를 전부 찾는다.
  2. 찾은 명령들에 대해 유선 청소기 관련 로직을 무선 청소기에 맞게 수정한다.

 만약 청소와 관련된 로직이 여려 군데에 흩뿌려져 있고, 최소 100개 이상의 청소 로직이 있다는 이야기를 들었다면? 프로그래밍 언어를 이용하여 좀 더 이해하기 쉬워졌다고는 하지만, 10만 줄을 읽어 어떤 부분이 청소와 관련된 부분인지 파악해야 한다는 점, 또한 거의 동일한 명령인데도 반복해서 작성하고 수정해야 한다는 점에 있어서 참 골치 아플 것이다.

 이처럼 소프트웨어를 "컴퓨터가 순서대로 실행할 명령"으로 바라보는 관점으로 바라보면, 사람이 이해할 수 있는 표현으로 표현되기는 하지만 근본적으로 명령 그 자체이기 때문에 소프트웨어의 크기가 증가함에 따라 각 부분이 어떤 일을 하고 있는지 명확하게 파악하고 유지보수하기 어렵다는 문제점이 있다.

절차적 프로그래밍(Procedural Programming)

아무리 사람이 이해할 수 있는 키워드를 이용했다고 해도 "컴퓨터에게 내리는 명령"이라는 날것 그대로 해석하기에는, 소프트웨어를 구현하고 유지보수하기가 정말 어렵다. 그런데, 잘 생각해 보면 우리는 자연스럽게 일련의 로직에 어떤 의미를 부여하고 있었음을 알 수 있다. 앞서 나온 예제를 생각해 보자.

사람 1이 청소기를 꺼낸다
1번 방으로 이동한다
플러그를 잡는다
현재 위치에서 가까운 콘센트를 찾는다
콘센트에 플러그를 꽂는다
청소기 손잡이를 잡는다
청소하고 싶은 위치로 이동한다(반복)
청소기 손잡이를 놓는다.
콘센트를 분리한다.
콘센트 선을 정리한다
청소기를 넣는다
사람 1이 청소기를 꺼낸다
2번 방으로 이동한다
플러그를 잡는다
현재 위치에서 가까운 콘센트를 찾는다
콘센트에 플러그를 꽂는다
청소기 손잡이를 잡는다
청소하고 싶은 위치로 이동한다(반복)
청소기 손잡이를 놓는다.
콘센트를 분리한다.
콘센트 선을 정리한다
청소기를 넣는다

 우리는 위에 제시된 일련의 명령을 읽고 자연스럽게 "어떤 방을 청소하는 일련의 로직", 즉 청소 로직이라는 표현으로 간단하게 추상화했다. 그렇다면, 이와 같이 일련의 연관된 명령 모음을 묶어 이름을 붙여 사용하는 것은 어떨까? 이런 발상에서 등장하는 것이 절차적 프로그래밍 패러다임이다.

절차적 프로그래밍(procedural programming)은 연관된 일련의 명령 모음을 묶어 의미 있는 이름을 붙인 프로시저 또는 함수를 활용하는 프로그래밍 방식이다.

청소기_청소( 사람, 방_번호 ) {
    (사람)이 청소기를 꺼낸다
   
(방_번호)번 방으로 이동한다.
   
   청소하고 싶은 위치로 이동한다(반복)
   
    (사람)이 청소기를 넣는다
}
사람1 = 주인장
사람2 = 민트

방1 = 안방
방2 = 거실

청소기_청소(사람1, 방1)
청소기_청소(사람2, 방2)

위 예시의 명령들을 함수로 표현한 것이다. 이제 청소와 관련된 일련의 명령은 "청소기_청소"라는 이름으로 추상화된다. 사람들은 구체적인 명령이 어떻게 동작하는지 모르더라도 "청소기로 청소를 수행" 할 것으로 기대하고 사용할 수 있다. 추상화에 의해 구체적인 명령이 함수 내부로 이동하였으므로, 변경이 한 군데로 집중되며 반복해서 사용할 수 있다는 점은 유지보수에 큰 도움이 된다고 볼 수 있다.

 그러나 절차적 프로그래밍 방식은 명령을 묶고 이해하기 쉽게 이름을 붙여 기존보다는 이해하고 유지보수하기 쉬워졌지만, 사람이 이해하기 어려운 "컴퓨터에 대한 일련의 명령"이라는 관점은 여전하다. 사람이 바라보는 세상의 사물들은 서로 명령하는 대신 상호작용하며 살아가므로, 소프트웨어 명령 덩어리로 간주하는 관점은 사람이 가진 일반적인 관점과 다르며, 직관적으로 소프트웨어를 이해하고 구현하기 어렵게 만든다. 사람이 이해한 상호작용을 "명령"이라는 체계로 변경해야만 프로그래밍이 가능하기 때문이다.

따라서, 이제는 소프트웨어를 "명령"으로 바라보는 관점에서 벗어나 사람이 보다 이해하기 쉬운, 사물 사이의 상호작용이라는 관점으로 소프트웨어를 바라보는 방식에 대해 생각하게 된다. 

객체지향 프로그래밍(Object-Oriented Programming)

 객체지향 프로그래밍은 말 그대로 사람이 바라보는 사물인 객체를 중심으로 진행하는 프로그래밍 패러다임이다. 소프트웨어를 컴퓨터가 이해할 수 있는 명령이 아니라 사람이 바라보는 세상인 객체, 그리고 객체 사이의 상호작용으로 표현하는 것을 객체지향 프로그래밍이라고 말할 수 있다.

 객체지향 프로그래밍이 무엇인지 이해하기 위해서는 사람이 바라보는 세상은 어떤지에 대해 알아야 한다. 우리는 너무나도 당연하게 느끼고 행동하지만, 너무 당연해서 말로 표현하기는 어려운 - 사람이 바라보는 세상에 대해 생각해 보자.

추상화(abstraction)

 잘 체감하지는 못하겠지만, 이 세상은 이로 말할 수 없는 복잡함을 기반으로 구성된다. 길가에 핀 작은 꽃 한 송이를 보기 위해서는 빛이 꽃 표면에 반사되어 내 눈에 들어와 시각 세포를 자극해야 한다. 꽃이 가진 색깔은 엽록소 등 꽃이 가진 물질이 빛과 만나 특정 스펙트럼을 흡수하고, 나머지는 반사하여 표현되며, 이 물질들은 수많은 세포, 분자, 더 깊게 들어가면 원자의 조합으로 구성되어 있다. 이뿐만 아니라 빛으로부터 에너지를 생성하기 위한 화학적 과정인 광합성, 물을 흡수하는 데 사용되는 삼투압 현상 등 다양한 과학적 원리가 조합되어 하나의 꽃이 존재하게 된다.

우리가 바라보는 꽃: https://www.freepik.com/icon/iris_3200044

 현실의 수많은 복잡한 정보를 기반으로 꽃이 존재하지만, 우리가 꽃을 바라보며 생각할 때마다 이 모든 디테일에 집중하지는 않는다. 꽃을 구경할 때는 아름다운 꽃들이 피어 있는 경치에 집중하고, 꽃이 가진 학문적 특징에 관심이 있을 때는 엽록소, 삼투압 같은 과학적 특징에 집중한다. 즉, 우리는 꽃이 가진 복잡함을 있는 그대로 바라보는 대신, 관점에 따라 필요한 핵심 부분만을 추상화하여 집중하게 된다.

 사물을 단순하게 보는 것 말고, 특징을 묶어 일반화하기도 한다. 푸들, 불도그는 개라는 종에 속하고, 개와 고양이를 묶으면 동물로 볼 수 있다. 동물은 살아 있으므로, 식물과 묶어 생물로 볼 수 있다. 이처럼 우리는 무심코 여러 사물이 가진 공통적 특성, 성질을 묶어 보다 상위 개념을 떠올리기도 한다. 이는 사물이 가진 공통점을 모아 추상화한 것이다.

추상화를 통한 동물의 구체화 - 일반화: https://logicmojo.com/inheritance-in-oops

 추상화는 우리가 세상을 이해하고 다루는 핵심적인 방식이다. 자동차나 자전거를 운전하며 우리는 단순히 운송수단으로써의 역할에 집중한다. 이들이 가진 공통된 특성을 보고 "운송수단"이라는 추상적인 개념으로 묶어낸다. 이처럼 우리는 세부적인 디테일을 무시하고 본질적인 특성에 집중함으로써 세계를 일반화, 단순화하고 이해하기 쉽게 만들어간다. 객체지향 프로그래밍은 이러한 인간의 본질을 이용하여 소프트웨어를 사물들 사이의 상호작용으로 표현한다.

객체(object)

 객체는 사람이 바라보고 생각하는 대상을 의미한다. 대상은 개, 고양이처럼 구체적인 대상일 수도 있고, 사랑, 이별처럼 추상적인 개념일 수도 있다. 이보다 더 중요한 것은 결국 우리가 바라보고 생각하는 모든 대상은 생각의 목적과 방향에 따라 추상화되어 핵심만을 표현하게 된다는 점이다.

우리집 강아지 사진

위 사진은 우리 집 강아지 꼬리를 촬영한 것이다. 꼬리는 토이 푸들로, 검은 눈, 코, 입과 갈색 털을 가지고 있다. 수컷(?)에 몸무게는 항상 8kg 정도 나간다. 가족들이 집에 돌아오면 마치 누가 때린다고 의심할정도로 낑낑거리며 반겨주고, 아직 건강해서 장난감만 보면 막 뛰어다닌다.

 난 우리집 강아지 꼬리를 설명할 때 꼬리가 가진 일부 특징을 기반으로 표현했다. 꼬리를 다른 사람에게 설명하기에는 이걸로 충분하다고 생각했기 때문이다. 또한 당연하지만 어떻게 달리는지, 어떻게 짖는지 모른다. 나는 꼬리가 구체적으로 어떻게 행동할 수 있는지 알 수 없고, 알 필요도 없다. 이처럼 우리는 객체가 가진 구체성을 자연스럽게 생략한다.

객체란 내가 바라보고 생각하는 추상적인 대상이다. 추상화 과정을 통해 가지는 핵심을 3개로 분류할 수 있다.

  1. 식별자: 객체의 이름. 나는 사진에 보이는 객체를 "꼬리"라고 불렀다.
  2. 상태: 객체가 가지고 있는 것. 꼬리는 검은색 눈·코·입과 갈색 털을 가지고 있다.
  3. 행동: 객체가 할 수 있는 것. 꼬리는 달리고 짖을 수 있다.

 인터넷 또는 서적 상의 글을 보면 객체를 상태와 행동, 식별자를 가진 것으로 정의하기도 한다. 프로그래밍 측면에서는 연관된 필드와 메서드를 묶어놓은 것으로 정의하기도 한다. 이러한 정의들은 사람이 바라보고 있는, 생각하고 있는 대상을 추상화 한 핵심이라는 측면에서 객체를 설명한다.

객체(object)와 상호작용

 이제 객체를 그냥 세상에 존재하는, 내가 아닌 모든 것이라고 생각해 보자. 내가 추상적으로 바라보고 생각하는 대상이 객체라고 했으니까, 내 입장에서는 이 세상 모든 것이 객체가 될 수 있다.

미용실에서의 상호작용

이 세상의 모든 것은 상호작용을 통해 존재한다. 작은 단위인 원자부터 거시적인 수준까지, 모든 것이 서로 끌고 당기며 상호작용한다. 이러한 상호작용은 우리가 미용실에 가는 일상적인 상황에서도 나타난다.

 미용실에 가면 우리는 손님으로서 미용사에게 머리를 잘라달라고 한다. 미용사는 자신이 가진 기술을 활용하여 요청에 따라 머리를 잘라준다. 머리를 다 자르면 우리는 결제하고, 종업원은 결제를 처리한다.

 이때, 미용실에서 등장한 각 객체들은 서로에 대한 구체적인 것을 알 수 없다. 나는 미용사가 어떤 경력을 쌓았는지, 어떤 기술을 가지고 있는지 알려주지 않는 이상 알지 못한다. 단지 내가 요청한 스타일을 잘 만들어주겠지, 하고 믿고 맡길 뿐이다. 미용사 입장에서도 손님이 요청한 머리스타일을 나름대로 잘 해석해서 잘라줘야 할 뿐, 손님의 생각을 직접 들여다볼 수는 없다.

 이처럼 객체들은 서로에 대한 구체적 사항을 직접 알 수 없으며, 단지 자신의 역할과 책임을 수행하고 서로에게 필요한 것들 요청과 응답을 통해 얻어간다.

객체를 지향한다는 의미

 사람은 세상을 추상적으로 바라본다. 추상적으로 바라본 대상인 객체는 추상화를 통해 핵심적인 특징만 가지고 있으며, 구체적인 사항은 알 수 없다. 이러한 객체들은 자신의 역할과 책임을 수행하고, 다른 객체와 상호작용하며 필요한 정보를 주고받는다.

 객체 지향 프로그래밍 방식은 복잡한 명령 모음을 사람이 생각하는 객체, 그리고 객체 간 상호작용의 형태로 추상화한다. 프로그램은 나름대로 역할을 가진 객체들, 그리고 이 객체들이 요청하고 응답하는 과정으로 표현된다. 프로그램을 사람이 바라보는 세상과 유사하게, 직관적으로 모델링할 수 있어 우리가 이해한 방식과 설계, 코드를 일치시킬 수 있다는 점이 큰 장점이라고 생각한다.

 소프트웨어를 보다 유지보수하기 쉽게 만들기 위해서는 유지보수하는 사람이 코드를 이해하기 쉬워야 한다. 객체지향 프로그래밍은 이를 가능하게 한다. 객체지향은 사람이 생각하는 방식을 코드에 반영하여 코드를 컴퓨터 입장에서 작성하던 과거의 관행에서 벗어나, 사람이 이해하기 쉬운 형태로 작성할 수 있게 만든다.


후기

 사실 나는 객체지향에서 파생된 기능들을 이용하면서도 객체가 무엇인지, 또 객체를 지향한다는 것은 어떤 의미인지 잘 와닿지 않았다. 전공자이면서도 객체지향에 대해 따로 공부해 본 적이 없었고, 기껏해야 전공 수업이나 정보처리기사 시험을 위해 겉핥기만 할 뿐이었다. 이렇다 보니, 객체지향이 가장 중요하다고 강조하면서도, 정작 객체가 무엇인지 조차 설명하기 어려웠다.

지난 몇 달간 객체지향에 대해 고민하면서 나름대로 결론을 내렸다.

객체지향 프로그래밍은 소프트웨어를 사람이 생각하는 방식대로 설계하는 방식으로, 소프트웨어를 역할을 가지고 있는 여러 객체, 그리고 객체들의 상호작용으로 표현한다. 이를 통해 컴퓨터 관점에서 작성되던 "명령 덩어리"라는 관점을 허물고,  프로그램을 사람이 이해하기 쉽게 작성할 수 있도록 돕는다. 결국 개발은 컴퓨터가 하는 것이 아니다. 개발은 사람이 하는 것이다. 사람의 입장에서 프로그램을 설계하여 이해하고 유지보수하기 쉽게 만드는 것, 그것이 객체지향이다.

 

'CS' 카테고리의 다른 글

sync / async & blocking / non-blocking  (0) 2024.09.20
AOP  (0) 2024.03.13
[CS] DIP / IoC / DI  (0) 2023.10.23
[CS] 객체 지향  (0) 2023.10.18
[batch] cron 표현식  (0) 2023.08.23