1장. 절차지향과 비교하기

절차지향은 객체지향 방법론에 비해 뒤떨어진 방법론일까?

위 문장을 보면, 자바는 객체지향인데 어떻게 절차지향 코드가 나올 수 있지? 라는 의문이 든다.

그렇다면, 과연 절차지향적인 코드는 무엇일까? 이 질문을 스스로에게 한다면 어떤 답을 할 것인가? 만약, "코드를 순차적으로 실행하는 프로그래밍 방법론" 이라고 답 하지는 않았는가?

위 답변이 오류가 있다는 점을 잠시 짚고 넘어가자면, 절차지향 이전에 순차지향 이라는 패러다임이 존재했었다.

순차지향 패러다임은 위에서 했던 답변 그대로 "코드를 순차적으로 실행하는 프로그래밍 방법론"이며, 엄연히 절차지향과 다른 패러다임이다.

절차지향과 순차지향은 뭐가 다를까?

순차지향을 영문으로 번역하면 "Sequential Oriented Programming" 에서 "Sequential" 이라는 단어의 의미 그대로 순차적으로 라는 뜻이니 "코드를 순차적으로 실행한다" 라고 이해할 수 있다.

절차지향은 "Procedure Oriented Prograaming" 에서 "Procedure" 라는 단어는 직역하면 절차가 맞지만 실제 컴퓨터공학에서는 함수로 받아들인다. 즉, 절차지향은 함수 위주로 생각하고 프로그램을 만드는 패러다임이다.

순차지향을 대표하는 언어인 어셈블리어를 살펴보면, 순차지향 패러다임을 단순에 이해할 수 있다.

add:
	subq    $8, %rsp
	leaq     16(%rsp), %rax
	movq   %rax, 0(%rsp)
	leal       0(%edi,%esi,1), %eax
	addq    $8, %rsp
	ret
main:
	subq    $8, %rsp
	leaq     16(%rsp), %rax
	movq   %rax, 0(%rsp)
	movl    $2, %edi
	movl    $3, %esi
	addq   $8, %rsp
	jmp     add
	

어셈블리어로 작성된 코드에는 함수라는 개념이 존재하지 않기 때문에, 그저 코드를 위에서 아래로 순차적으로 읽을 뿐이다.

반면에 절차지향을 대표하는 언어인 C 로 동일한 예시를 살펴보자.

C언어로 작성된 코드는 순차지향과 달리 함수가 존재한다.

결론적으로 절차지향 프로그래밍은 함수를 만들어서 프로그램을 만드는 방식으로, 복잡한 문제를 개별적인 "함수"로 해결하고, 여러 함수를 이용하여 문제를 해결하는 방식이다.

이제 절차지향이 뜻하는 바가 함수 지향 프로그래밍이라는 것을 알게 된 시점부터, 자바에서도 함수 기반으로 동작하는 코드를 절차지향이라고 표현할 수 있지 않을까?

그럼, 자바로 작성된 아래 코드는 어떨까? 이 코드는 실제 책에 나온 예제 그대로이다.

이 코드를 대충 살펴 봤을 때, Store, Order, Food 객체는 상태만 가지고 있을 뿐 아무런 행위가 존재하지 않다.

이는 C 언어의 구조체와 다를 바 없는데다 속성의 접근제어가 모두 private 으로 설정되어 getter를 이용해 조회하고 있으니 더 불편할 뿐이다.

그럼 이제 위 절차지향적 코드를 객체지향적 코드로 변경해보자.

함수 내부에서 모든 비즈니스 로직을 직접 제어하던 절차지향적인 구조에서 객체가 상태와 행위를 갖게 되었다.

이는 내부 구현을 감추어, 객체가 행위에 대한 책임을 지고 있고 호출하는 입장에서는 전적으로 믿는다는 것이다.

객체지향으로 바꿨는데 코드 가독성이 너무 떨어진다.

절차지향적인 코드가 더 잘 읽히는 말에 전적으로 동의하고, 관리하는 코드의 양도 훨씬 더 적은건 사실이다. 게다가, 모든 비즈니스 로직의 흐름을 직접 제어하기 때문에 한 눈에 알아볼 수 있다.

하지만 객체지향은 가독성을 높이기 위해서 코드를 작성하지 않고, 가독성보다 객체의 책임에 좀 더 집중한다.

우리는 왜 객체지향을 패러다임을 따를까?

객체지향 패러다임을 따르는 이유는 적은 유지보수 비용과 손 쉬운 확장을 위해서이다.

핸드폰을 어플리케이션 내부에서 설계한다고 가정했을 때, 특정 부품이 고장나더라도 우린 다른 부품에 영향을 최소화 하고 고장난 부품만 변경하고자 하는 목적을 이루고 싶은 것이다.

이러한 철학은 객체지향 원칙인 SOLID 원칙을 살펴보면 알 수 있듯이, 낮은 결합도와 높은 응집도를 목적으로 두어 고안된 설계 원칙이다.

즉, 변경하는 구성 요소로 인해 다른 시스템에 영향을 주지 않도록 어플리케이션을 관리하고자 하기 때문에 우리는 복잡한 객체지향 패러다임을 통해 책임과 협력을 설계하여 문제를 해결해나간다.

C 언어도 함수로 책임을 설명할 수 있는데, 그럼 이것도 객체지향 아니야?

책임은 객체지향에서 굉장히 중요한 부분이지만 객체지향만의 특징은 아니다. 일반적인 함수에도 책임은 존재하기 때문이다.

책임을 객체에 할당하는 것이 객체지향의 큰 특징이라면 C 언어도 객체지향이 될 수 있다.

  • 구조체에 함수 포인터를 넣으면 구조체 단위로 책임을 만들 수 있다.

그럼, 왜, C 언어는 객체지향 언어가 아닐까?

C 언어의 구조체는 추상의 개념을 지원하지 못한다. 만약, C 언어에서 추상화 같은 개념을 사용하지 못하고 새로운 요구사항을 반영한다고 생각해보자.

위 처럼 객체지향을 흉내낸 C 코드가 있고, 만약 새로운 음식을 추가해야 한다고 하면 우리는 이 코드를 main 함수에 추가해야 할 것이다.

정리

C 언어는 구조체와 함수 포인터를 이용해 책임을 나눌 수 있지만, 그 책임을 추상화하고 협력 관계로 확장할 수 있는 언어적 장치가 부족하다. 따라서 기술적으로는 객체지향 언어라고 부를 수 없다.

그러나 객체지향의 본질은 단순히 추상화, 상속, 다형성, 캡슐화 같은 문법적 기능이 아니라, 객체 간의 역할, 책임, 협력을 중심으로 문제를 바라보는 사고 방식에 있다.

즉, C 언어는 객체지향 언어는 아니지만, 개발자가 객체지향적인 사고로 책임을 나누고 협력을 설계할 수는 있다.

따라서 추상화, 다형성, 상속, 캡슐화는 객체지향을 표현하는 기능적 특징일 뿐, 객체지향의 핵심은 “역할-책임-협력”이라는 개념적 구조에 있다.

객체지향적 사고 방식을 어떻게 해야할까?

객체지향적 사고를 하도록 만들 수 있는 가장 쉬운 방법은 TDA 원칙을 지키며 개발 하는 것이다.

TDA(Tell, Don't Ask)

객체에게 값에 관해 물어보지 말고 일을 시키라는 의미이다.

TDA 원칙을 따르면 무분별하게 사용되는 게터, 세터 를 줄일 수 있다.

실제로 게터와 세터는 개발자가 객체지향적인 사고를 못하게 하는 방해 요인 중 하나이자, 절차지향적 사고를 하게 되는 대표적 원인이 된다.

  • 위 C 언어가 갖고 있는 구조체의 데이터를 가져와 연산을 하는 행위나 다름 없다.

Last updated