객체 지향과 프로그래밍
객체 지향 프로그래밍과 함수형 프로그래밍, 선언적 프로그래밍

이 글은 외국 포스팅과 앨런 케이의 Quora 질의 응답 번역을 포함합니다.
OOP의 창시자 앨런 케이는 생물학을 전공했다. 그는 여러 세포들이 상호작용해 큰 유기체를 만들고, 유지하고, 확장하듯 프로그램도 그럴 순 없을까 고민했다. 그는 프로그램의 개체(Entity)들이 아래와 같이 구성되길 바랬다.
- 서로 내부 구조는 모르고, 서로의 원리를 함부로 바꿀 수 없고, 각자가 맡은 역할에만 충실하되,
- 무슨 일이 생기면 서로 소통하고 요청하면서 복합적인 반응을 일으키고,
- 그러면서 여러 요인, 여러 상황에서 실시간으로 적절한 판단을 할 것.
케이 박사는 3번의 실현을 위해서는 동적 언어가 적합하다고 생각했지만, 정작 객체 지향으로 정말 유명한 Java는 정적 언어다. 그는 어떤 걸 생각하면서 OOP를 발안했을까? 아래 외국 저자의 포스팅과 요 몇 년 간 케이 박사가 Quora에서 답변한 내용들을 보며 다시금 생각해보자.
앨런 케이 박사는 60년 대에 "객체"라는 말을 꺼냈습니다.
그는 프로그래밍 계에서 많은 걸 보고, 많은 기여를 했고, 적절한 표현을 위해 그 단어를 뱉었습니다. 그리고 그는 단어가 잘못됐다고 씁쓸해 합니다.
"객체"란 단어는 사람들로 하여금 "행동(behavior)"이 아닌 "구현(implementation)"에 집중하게 만들어버렸고, 그것이 내리막길의 시작이었습니다.
오늘날 많은 개발자들이 OOP를 클래스, 상속이라고 말합니다. 좀 더 나아가면 캡슐화, 다형성을 얘기하죠. 하지만 그 뿐입니다.
케이 박사는 OOP의 전말이 아래와 같다고 말합니다.
- 메시징
- 상태를 지역적으로 보관, 보호, 숨김
- 최대한 느린 바인딩
왜 이렇게 말했을까요?
그는 세포 생물학을 전공했습니다. 소프트웨어가 확장성이 안 좋은 데에 비해 세포는 쉽게 계산하고, 확장하고, 복잡한 것을 만들어내며 스스로를 수정한다는 사실을 눈여겨봤습니다. 매우 정교하다는 컴퓨터 소프트웨어 프로그램은 느리고, 작고, 버그 투성이인데 말이죠. 그의 고민은 여기서 비롯합니다.
“어떻게 해야 소프트웨어가 이런 확장성을 가질까?”
독립성
먼저 독립성을 말해보죠. "상태를 지역적으로 보관, 보호, 숨김"을 짧게 줄인다면 독립성이라고 하겠습니다.
세포 안쪽은 어지럽고 복잡하지만 세포막이 이를 깔끔하게 포장해서 내부를 숨깁니다. 우리 몸은 매일 500~700억의 세포가 죽습니다. 하지만 우리는 아닙니다. 세포가 죽는다고 우리가 죽나요? 아닙니다. 세포가 죽을 때 여러분이 안 죽는 건 캡슐화 때문이 아닙니다. 독립성 때문이죠. 다음의 (끔찍한) 예시를 보죠.
class MyExample:
def reciprocal(self, num):
return 1.0/num
example = MyExample()
print example.reciprocal(4);
print example.reciprocal(0);
위 코드는 클래스에서 reciprocal을 캡슐화합니다. 하지만...
0.25
Traceback (most recent call last):
File "class.py", line 7, in <module>
print example.reciprocal(0);
File "class.py", line 3, in reciprocal
return 1.0/num
ZeroDivisionError: float division by zero
객체도 죽고, 객체를 소유하는 코드도 죽습니다. 이건 그가 우리에게 전달하고자 한 의도와 정반대입니다.
그가 말하고자 하는 바는 브라우저 - 서버를 생각하면 좋습니다. 서버(객체 1)가 먹통된다고 브라우저(객체 2)까지 함께 먹통이 된다면 MS IIS는 2.0 버전까지도 못 가고 망했겠죠.
아파넷(Arpanet)은 30년 만에 규모가 1억 배 커졌습니다. (게다가 확장할 때마다 유지보수를 위해 다운시킬 필요도 없었죠.) 오늘날 인터넷은 케이 박사가 "자신이 제시한 OO 모델의 유일한 성공 사례"라고 말합니다.
최대한 느린 바인딩
그의 또 다른 핵심 주장은 매우 느린 바인딩입니다. 무슨 말일까요? 코드를 봅시다.
my $order = OrderFactory->fetch(%order_args);
my $invoice = $order->invoice;
"order" 클래스가 여럿 있다면 어떤 클래스를 호출하고 컴파일 때 어떤 invoice 메소드를 호출하게 될까요?
OOP 언어는 다형성을 위해 런타임 전에 호출자($order)에 대한 메소드(invoice)를 바인딩하지 않습니다.
느린 바인딩이란 뭘까요? invoice 메소드의 '존재 여부'를 한 번 볼까요? Java같은 언어는 메소드가 존재하지 않으면 컴파일조차 안 합니다. 최소한 메소드가 존재해야 하고, 호출할 수 있는지 확인합니다.
많은 동적 언어, 예를 들면 Perl같은 경우 코드를 실행할 때까지 메소드를 호출자에 바인딩하지 않으므로 컴파일 문제가 안 납니다.
당신은 새벽 2시 경에 배치 프로세스가 실패했다는 당혹스러운 전화를 받습니다. 아까 회사에서 캡슐화는 해뒀는데 독립성 처리를 못했을지도요.
이런! 이런 게 바로 검사를 거의 거치지 않는 "매우" 느린 바인딩입니다.
Java같은 정적 언어는 '타입을 식별자에 바인딩'하기 때문에 다른 타입의 값을 할당할 수 없습니다.
하지만 Perl, JavaScript 같은 동적 언어는 '값 자체에 타입을 바인딩'합니다. 값은 타입을 알지만 변수는 타입을 모릅니다. 이것이 "느린 바인딩"입니다.
// JavaScript
const str = "hello" // str 식별자에는 타입에 대한 정보가 없고 "hello" 값을 통해 타입을 판단합니다.
// Java
int num = 1; // 선언부에 int라고 적음으로서 num 식별자 자체에 타입이 바인딩됩니다. 이를 통해 컴파일이 정교합니다.
그는 느린 바인딩이 있으면 문제 해결을 위한 "유일한 길"을 미리 확정하지 않고 실행 중간에 변수가 발생해도 시스템이 스스로 판단, 정정하기 때문에 좋다고 말합니다. 프로그램을 변경하면 실행을 정지하고 재시작하고, 복잡한 컴파일을 다시 기다리느라 몇 시간을 허비하던 시절이 기억나나요?
메시징
독립성과 느린 바인딩은 이제 알 듯합니다. 이제 케이가 가장 중요하다고 강조했으며, 여전히 많은 사람들이 간과하는 "메시징"을 봅시다.
객체는 자신이 어떤 작업을 했는지 알리고, 다른 객체는 이를 무시하거나 "아주 좋아."라고 맞장구쳐야 합니다.
많은 개발자들이 공감은 합니다. 하지만 이론만 알기보단 구체적인 증명도 봐야 공감을 사기 좋습니다. 그래서 저는 케이 박사가 조금 모호하게 말했다고 생각합니다. 그가 말하는 객체 지향은 '모든 객체가 컴퓨터 그 자체' 같습니다. 각 객체마다 메시지를 수신하고, 이해하고, 스마트하게 처리합니다.
다시 말해서, 이름을 부르면 정해진 역할만 실행하는 게 아니라 객체에 메시지를 보내면, 객체는 어떻게 반응할지 고민, 판단합니다. 이러한 알고리즘은 독립성을 강화합니다. 수신하는 측의 객체는 못 알아들을 메시지는 무시하기 때문입니다. 많은 사람들에겐 익숙하지 않은 패러다임이지만, 강력합니다.
“중요한 건 메시징일세. 훌륭하고, 성장하는 시스템을 만들려면 모듈이 무슨 자원과 동작을 가질지보다, 모듈들이 어떻게 소통하냐를 설계하는 게 핵심이야.
인터넷이 살아있으려면 두 가지가 필요해.
- 표준을 넘어서서 여러 종류의 아이디어와 구현을 허용.
- 아이디어 간 여러 수준의 안전 상호 운용성 허용.
자네가 메시징에만 집중한다면 (그리고 좋은 메타시스템이 객체에서 쓰는 2nd level 아키텍쳐를 느리게 바인딩할 수 있다는 점을 이해한다면) 이 스레드에서 여러 언어, UI, OS에 기반한 논의는 무의미할 거야.”
- 앨런 케이가 -
아래부터는 Quora에 올라온 질문에 앨런 케이가 대답한 것입니다.
“66년 말 대학원 첫 주에, 저는 우연히 Sketchpad와 Simula를 접했고 생물학적 구조와 네트워크 상의 컴퓨터, 같은 시간을 공유하는 프로세스들, 시스템의 각 부분들이 상호작용하는 모습들을 보면서「기본적이고, 보편적인 단위로서의 컴퓨터」에 대한 실마리를 얻었습니다. 그로부터 동적 언어를 고려했고, 어떻게 해야 효율적이고, 간결하고, 보편적으로 프로그래밍할 수 있을지 고민했습니다.
1960년 대에는 배열보다 복잡한 소프트웨어 복합체를 종종 '객체'라고 불렀습니다. 어느날 누가 뭐하냐고 물었고, 전 별 생각 없이「객체 지향 프로그래밍」이라 대답해버렸습니다. (여러 이유로 매우 나쁜 단어 선택이었지만, 이미 배는 떠나버렸습니다.)
'객체'는 제 의도와는 다르게 활성적이지 못했고 '데이터'를 연상시키는 나쁜 단어였습니다. Simula는 인스턴스를 '프로세스'라 불렀는데 그게 차라리 낫더군요. "프로세스 지향 프로그래밍, 메시지 지향 프로그래밍, 서버 지향 프로그래밍"이라고 이름 붙이는 게 좋았을 것 같습니다.
저는 프로세스 내부에서 상태 처리 방식을 은폐하되, 각 프로세스들을 서로를 위한 '서버'처럼 취급하고 싶었어요. 저는 그런 의도를 전달하고 싶었습니다.”
“"객체"를 이해하려면 각 객체가 아이디어라고 생각해보세요. 요청(명령 X)에 대해 유의미한 반응들을 가진 아이디어요. 차등 권한을 가진 서버처럼요.
이렇게 하면 큰 시스템을 실행하는 중에도 안전하고 다양하게 확장하고, 재구성을 할 수 있습니다.(문제를 해결하거나 변경하기 위해 시스템을 중단해야 한다면, 시스템 설계가 잘못된 겁니다.)”
“좋든 나쁘든, 여러 이유로 C++은 큰 인기를 얻었습니다. C++은 "객체 지향 언어"라는 평가를 받았고, 많은 사람들이 그렇게 얘기합니다. 하지만 제가 생각한 OOP와는 많이 동떨어져 있습니다.
이는 "객체 지향"의 "식민지화" 중 하나였습니다. 80년대 말에 "객체 지향"은 일종의 유행이었습니다. 온갖 분야에서 너무 쓰는 바람에 Parc의 소프트웨어는 "객체 지향"이라는 말로 설명할 수 없어졌고, "진짜 객체 지향"이라는 말까지 써야 했습니다.”
“(적어도 제 생각에는) OOP는 패러다임에 국한할 게 아닌, 그보다도 더욱 실행성이 좋은 '보편적 정의' 체계이고, 대규모 시스템을 정의할 때 적합합니다. ”
- '누군가한테' 메시지를 보낼 때, 그 메시지는 어떤 건지 생각해보세요. 명령인지, 요청인지, 제안인지, 협상인지.
- '누군가한테' 메시지를 보낼 수는 있지만, 도중에 수신자나 송신자의 마음이 바뀔 수도 있습니다.
- 메시지를 보낼 때, 정말로 특정 대상에게만 보내고 싶은지 다시 생각해보세요. 필요에 따라서는 '요청'을 보내거나, 공동체에 x,y,z를 공급할 수 있다고 시스템에게 알리는 게 나을 수도 있습니다.
“보호되는 모듈, 명령이 아닌 메시지, 함수형 관계. 이 개념들 사이에선 충돌이 전혀 없어야 함을 유념하세요.
개인적으로 추천하는 휴리스틱은「시스템을 생각」하지 말고「생물학적으로 생각」해보세요. 세포를 객체라고 생각하면 중요한 원리들이 많이, 금방 떠오릅니다.”
“객체들이 서로 어떻게 통신할지 생각해보세요. 전송할 메시지의 단순함, 풍부함, 모듈의 무결성, 투명성...이것들을 어떻게 프로그래밍해야 한계를 뛰어넘을 수 있을지 고민해보세요.”
“느린 바인딩은 유연성과 안전성을 유지하는 선에서 최대한 다양한 방안을 마련하잔 의도입니다. 캡슐화된 객체는 "어떻게(메소드)"를 느리게 바인딩하고 "무엇(의미)"을 사용함으로서 다양한 대안과 투명성을 구현합니다. ”
“올해는 제가 "비 명령형 메시지로 소통하는 캡슐화된 서버"들로 무엇이든 만들어낼 수 있다는 단순한 생각의 힘을 "인식"한지 50주년이 되는 해입니다.
“선언적 프로그래밍을 생각할 관점 중 하나는 "어떻게"와 "무엇"을 구분하는 것입니다. 역사적으로 프로그래밍은 "어떻게"에서 시작했고, 프로그램은 주어진 자원과 도구로 원하는 결과를 도출하기 위한 전술이었습니다. 설계도 없이 순서대로 집을 짓는 격이었죠.
건축 현장에선 거의 항상 "무엇"을 염두에 둡니다. 본 작업을 하기 전에 구현을 먼저 하죠. 예를 들면 집 설계도나, 섬세한 미니 모형이요. 그런 것이 "선언적인 내용"입니다. 사람은 그걸 보면서 목표를 위한 전술을 구성하고요. 그렇게 해서 전술/방법은 전략/계획을 참조하고, 비교하고, 상황을 판단합니다.”
확장성
소규모 프로젝트에도 자주 마주칠 수 있는 문제 중 하나는 확장성이다. 거의 비슷하거나 흐름은 같은데도, 완전히 동일하지 않으면 재사용이 어려운 코드가 많다. 구현 사항 자체는 같은데도 UI가 다르다는 이유만으로 코드를 새로이 작성해야 하면 입맛이 씁쓸하다.
이러한 경우, 구현 사항이 똑같기 때문에 변경도 똑같이 시행해야 하는데 실수 확률이 높아진다. UI와 로직을 독립적으로 구분하되, 재사용을 고려해서 설계해야 할 때가 있다. 아토믹 디자인 패턴으로 UI를 잘게 나눠 재사용할 수 있고, 레이어에 따로 함수를 API처럼 모아둘 수도 있다. HoC로 감싸서 경우에 따라 선택적으로 컴포넌트를 표시하도록 설정하는 방법도 있다.
방법은 가지각색이지만, 이러한 고민과 해결들 속에도 객체 지향의 정신이 포함돼있다고 생각한다.
마무리

의사, 약사가 각각 한 명 뿐이라면, 그리고 의사나 약사에게 무슨 일이 생겨서 제 일을 못하게 된다면 전체적인 작업 흐름이 끊길 것이다. 그렇기 때문에 의사 B, 약사 B, C에게 찾아갈 수 있도록 그때그때 상황에 맞출 수 있도록 느린 바인딩, 여러 대안이 필요하다.
소스 코드는 위에서 아래로 순차적으로 흐른다. 고수준에서 결정을 내리고, 저수준에서는 세부적인 처리를 한다.
앨런 케이는 프로그램이 변화에도 부드럽고 쉽게 적용, 확장되길 바랬고 그의 정신은 로버트 마틴이 제안한 SOLID 원칙에서도 조금이나마 계승됐다고 생각한다.
- [단일 책임]: 각자가 자기만의 유일한 책임을 가지고,
- [개방 - 폐쇄]: 확장이 일어날 때에는 변질 없이 양만 늘어나고,
- [리스코프 치환]: 대체가 가능한 개체들은 서로가 딱 맞게 치환이 되고,
- [인터페이스 분리]: 불필요한 것 없이, 정말 필요한 것만 요청해서 협력하고,
- [의존성 역전]: 작은 개체 때문에 전체, 상위가 휘둘리는 일 없이, 상위 개체, 상위 메시지를 중심으로 여러 개체들이 움직이고 소통한다.
참조
- Alan Kay and OO Programming
- Alan Kay On Messaging
- What did Alan Kay mean by, "I made up the term object-oriented, and I can tell you I did not have C++ in mind."?
- Why is object-oriented programming more about messaging than objects?
- I read somewhere that object-oriented programming was coined by Alan Kay circa 1966 or 1967 while he was at grad school. Has anyone influenced/contributed Alan to coin such a term?
- What does Alan Kay think about inheritance in object-oriented programming?
- In object-oriented programming, why is it bad practice to make data members public when the get() & set() public members modify it anyway?
- What is the significance of late binding?
- If one starts with a state of the art object-oriented programming from the late 80s and early 90s (Smalltalk, Self, CLOS), what could have been the next breakthrough in that style of programming, but never happened?
- What's the best way of combining functional and OO programming in practice?
- What is Alan Kay's definition of Object Oriented?