진실 2: Smalltalk는 어렵다 -2-

Jun 16, 2005 02:40
 

3. 사례 2: Smalltalk

앞에서 우리는 어떤 것이 '어렵게 느껴지는' 7가지의 이유를 살펴보고 이를 토대로 C/C++ 언어에 적용시켜 생각해 보았다. ※ 참고
진실 2: Smalltalk는 어렵다 -1- (C언어, 왜 어려운가?)
이번에는 본격적으로 사람들이 흔히 말하는 것처럼 "Smalltlak가 어려운가"에 대해서 생각해 보기로 하자. 지금까지 살펴본 일곱 가지의 기준으로 설명해 나가겠다.
 

1) 다르다. / 새롭다.

프로그래밍 언어의 초심자들에게는 물론 Smalltlak가 어려울 것이다. 아니, 그들에게는 어쩌면 '프로그래밍의 개념' 자체가 어려운 것일지 모른다. 그러므로 엄밀히 말하면 프로그래밍 초심자들은 Smalltalk가 어렵다기 보다는 '프로그래밍 그 자체'가 어렵다고 봐야 한다.
그럼 기존 언어에 익숙한 사람들은 어떨까? 대부분의 사람들이 Smalltalk가 어렵다고 하는 이유는 대략 다음과 같이 생각해 볼 수 있다.
[1] 소스 코드의 모양이 다른 (대중적인) 언어들과 사뭇 다르다.
[2] 객체지향 패러다임을 무조건 강요한다.
 
 
우선 [1]의 경우가 아마 Smalltlak가 어렵다고 느끼는 가장 타당한 경우일 것이다. 사실 오랫동안
while (not( fileStream.eof() )) { someObject.doSomething( data[i] ); }
라는 구문에 익숙한 프로그래머들이
 
원본
[ fileStream atEnd ] whileFalse: [ someObject doSomething: (data at: i) ]
라는 Smalltalk의 표기법을 보면 당혹스럽고 황당한 건 어찌보면 당연하다.
그러나 이것은 지금부터 이야기할 이유들 때문에 실은 무척 쉽게 익숙해 질 수 있다. 그리고 프로그래밍의 초심자들이 C 언어의 코드와 Smalltalk의 코드 중 어떤 것을 보다 더 거부감 없이 받아들일까 한번 곰곰히 생각해보자.
십중팔구 초심자들은 복잡한 기호와 단어의 연결 보다는 일관된 문법을 갖는 코드를 좀 더 쉽게 받아들일 것이다. [2]의 경우처럼 말하는 사람들의 마음속에는 다음과 같은 생각이 뿌리를 박고 있을 가능성이 무척 크다.
 
"객체지향은 어렵다"
 
바로 그것이다. 사실 "객체지향을 강요한다"고 말하는 사람들은 아직 객체지향의 이 제대로 정립되지 않았거나 혹은 부정확하게 [관]을 세운 사람들이다.
여러분이 영문 원서를 읽으려면 영어를 공부해야 하고, 일본어로 된 신문을 읽을 때도 일본어를 공부해야 하는 것은 너무나 당연하다. 객체지향 패러다임을 군더더기 없이 가장 순수하고 명확하게 표현하는Smalltalk 언어에다 대고 '객체지향을 강요한다'느니 '한 가지 패러다임에 얽메인다'등과 같은 말을 한다는 건 언어도단이다.
 
일본 신문을 읽으려면 일본어를 배우는 것은 당연한 일일본 신문을 읽으려면 일본어를 배우는 것은 당연한 일
일본 신문을 읽으려면 일본어를 배우는 것은 당연한 일
 
이런 사람들은 대부분 '객체지향'은 현실과 동떨어져 있는 것이며 어렵고 부담스럽다고 말하면서 다음과 같이 모든 이론과 패러다임의 공통된 단점을 가지고 스스로를 방어하려고 한다.
 
"객체지향은 만능이 아니다."
 
사실 이렇게 말하고 싶은 사람들은 스스로에게 물어보자. 그대들은 Smalltalk가 어려운 것이 아니라 객체지향이 두려운 것이 아닌가? 물론 [패러다임 충격]이라는 것을 충분히 이해한다. 왜냐하면 내가 '절차지향 패러다임'에 젖어 있을 때 '객체지향'이라는 새로운 패러다임은 나에게도 역시 똑같은 충격이었고 두려움이었기 때문이다.
그러나 그런 두려움을 던져두고 Smalltalk가 제공하는 수많은 객체들의 바다에 몸을 내맡겨 풍덩 빠져보라. 그리고 객체들을 머릿속으로 개념화 하지지 말고 그들과 부딪히면서 몸으로 느껴보라. 그러면 그대들은 이렇게 말하게 될 것이다.
 
"객체지향은 만능이 아니다. 그러나 사물과 현상의 본질을 파악하는데 크게 도움을 주는 패러다임이다."
 
'새롭고 낯선' Smalltalk 환경에서 여러분들 도와줄 수 있는 도구는 얼마든지 많다. 이제 하나하나 그것을 살펴볼 차례다.
 
 

2) 크다다 / 많다.

앞서 살펴본 것처럼, 프로그래밍 언어는 크게 두 가지 부분으로 나눌 수 있다.
 
  • 언어의 기본 골격(구성요소, 문법)
  • 라이브러리(추가 기능)
 
이제 이들 특징이 Smalltalk에는 어떻게 적용되어 있는지 알아보자.
 
엽서 한 장을 채 못 채우는  이 코드에 Smalltalk 문법의 모든 요소가 다 들어있다. 진짜다.엽서 한 장을 채 못 채우는  이 코드에 Smalltalk 문법의 모든 요소가 다 들어있다. 진짜다.
엽서 한 장을 채 못 채우는 이 코드에 Smalltalk 문법의 모든 요소가 다 들어있다. 진짜다.
 
먼저 Smalltalk의 문법에 대해서 이야기하겠다. Smalltalk의 문법은 그 이름이 말해주듯이 정말 '작다'. 필자가 올려놓은 "Smalltalk 초간단 문법 정리"를 살펴보면 대략 A4지 3~4장 정도의 분량이다. 놀라지 말라. 이건 '언어의 정의'가 아니라 Smalltalk의 "완전한 문법 설명"이다.
사실 여러분들이 프로그래밍 언어를 이미 다루어본 사람들이고 "Smalltalk 초간단 문법 정리"만 제대로 이해했으면 Smalltalk의 80%를 익힌 것이라 감히 말할 수 있다. 그만큼 Smalltalk의 문법은 언어가 차지하는 비중에 비해서 미세하고 그 수도 적다.
 
 
예를 들자면 
C++C++
C++
언어에는 지정어가 수 십개가 넘는다. 몇 개인지 기억도 안 난다. 하지만 Smalltalk에서는 고작 여섯 개의 지정어만 있을 뿐이다.
true, false, nil, self, super, thisContext. 이들 모두는 어떤 특별한 객체를 지칭하는 용도로만 쓰인다. 결코 이들 지정어가 언어의 문법을 규정하지 않는다.
C++의 연산자는 대략 40개가 넘는다. 하지만 Smalltalk에서는 두 개 뿐이다. 바로 '지정 연산자'인  := 과 '응답 연산자'인 ^ 이 전부이다.
Smalltalk의 문법 대부분은 아주 기본적인 객체들, 즉 리터럴(literal)을 나타내기 위한 것과 객체에게 메시지를 전달하기 위한 것들이 주를 이룬다. 그래서 Smalltalk에는 if, for, swich 같은 제어문도 없고 class, new, delete 같은 클래스나 객체의 생성 및 소멸에 관한 문법도 없다.
그런데 어떤 사람은 이렇게 문법과 언어의 구성 요소가 간단하다는 것은 Smalltalk로 할 수 있는 일의 범위가 작고 표현력도 무척 나쁠 것이라 생각할 지도 모른다. 그러나 놀랍게도 저 수많은 문법 구성 요소를 가진 C++ 언어보다 Smalltalk의 표현력이 훨씬 우수하다. 그것도 C++ 언어보다 훨씬 간결하면서 직관적으로 표현할 수 있다.
예를 들면, 포인터를 전혀 지원하지 않는 Smalltalk언어에서
(unsigned char *)0x18FF0040 = 0xFF;
와 같은 C언어의 표현대로 하고 싶다면 이렇게 할 수 있다.
 
원본
MainMemory at: 16r18FF0040 putByte: 16rFF.
 
물론 MainMemroy라는 객체를 만드는 것은 사용자의 몫이기는 하지만 일단 이렇게 동작하는 객체가 있다면 얼마든지 C나 C++보다 직관적인 표현이 가능하다.
그렇다면 앞에서 말한 제어문, 클래스나 객체의 생성과 소멸에 관한 것 등은 Smalltalk에서 도대체 어떻게 표현되는 것일까? 믿기 힘들겠지만 이런 모든 것은 '라이브러리'에 속해 있는 기능이다. 내가 C/C++의 '크고 많다'를 설명하는는 부분에서 MFC, STL 등과 같은 라이브러리를 언급하지 않은 것도 바로 이 때문이다.
사실 라이브러리는 언어의 필수 구성요소가 아니기 때문에 자기가 관심을 가지는 분야의 라이브러리만 따로 공부할 수 있다. 그러므로 '라이브러리'는 직접적으로 언어의 학습 난이도에 크게 영향을 주지 않는다. 물론 언어의 구성요소가 어렵고 복잡하기 때문에 라이브러리의 사용 방법이 어려워질 수는 있지만, 결국 이것도 라이브러리 책임이 아니라 언어의 책임이다.
C언어를 설계할 때 '값 의미'(value sementic)와 '참조(reference) 의미'를 분명하게 구별해서 구현해 놓았다면, 애초에 scanf() 함수를 쓸 때 &를 붙이니 마니, 포인터를 사용해야 하느니 마니 등과 같은 쓸 데 없는 고민은 할 필요조차 없었던 것이다.
분명히 Smalltalk에도 수천 개의 클래스가 있고 이들 클래스가 만들어 내는 각종 프레임 워크(frame work)가 존재한다. 결국 이런 것들도 공부해야 한다. 하지만 이러한 프래임 워크를 공부하는 것은 다른 언어에서도 마찬가지이며 대부분의 프레임워크는 이미 Smalltalk에서 다듬어진 것들을 그대로 가져다 쓰는 수준이기 때문에 다른 언어에서도 큰 어려움 없이 받아들일 수 있다.
Java의 Swing이나 C++의 MFC는 Smalltalk의 MVC 프레임 워크의 변형이며 C++, Java, C#의 Collection 라이브러리의 90% 이상은 Smalltalk의 Collection 라이브러리들을 빌려서 각 언어에 특화시킨 것들이다. 심지어 요즘 소위 '뜨고 있는' 디자인 패턴, XP, TDD, Refactoring 등은 이미 Smalltalk에서 오래전부터 적용되어 오던 프레임워크와 방법론들이다.
결정적으로 이러한 프레임 워크는 언어의 구성 요소가 아니기 때문에 모든 것을 다 배워야 할 필요도 없으며 배워야 할 상황에 직면하면 자연스럽게 배우게 되어 있다. 이렇게 말하면 다음과 같이 주장하는 사람들이 있을지 모른다.
 
"Smalltalk는 주류 언어가 아니며, 어차피 나중에는 주류 언어를 배워야 한다."
 
여기에 대해서 지금 내 생각을 말하고 싶지만 이 글의 주제에서 벗어나는 관계로 위 주장에 대한 생각은 잠시 뒤로 미루어두기로 하자. 분명한 것은 여러분들이 Smalltalk의 프레임워크와 방법론을 이해한다면 다른 객체지향 프로그래밍 언어를 공부할 때에 상당히 많은 도움이 된다는 것이다. 그리고 이런 프레임워크는 여러분이 스스로 학습량을 조절할 수 있기 때문에 다른 언어에서처럼 '문법'과 '라이브러리'를 함께 공부해야 하는 부담에서 여러분들의 소중한 뇌와 시간을 지켜 줄 것이다.
마지막으로 앞에서 C언어로 예를 들었던 "Hello, world!" 프로그램과 같은 일을 하는 Smalltakl 코드를 소개한다.
원본
Transcript print: 'Hello, world!'; cr.
 
 
Transcript는 일종의 '알림창'(log screen)을 의미하는 객체의 이름이고 이 객체에 print: 라는 메시지를 보내면 바로 뒤에 있는 매개변수의 내용을 표시한다. cr 메시지는 줄을 바꾸라는 것이며 세미콜론(;)은 Transcript에게 메시지를 연속해서 보낼 때 쓰이는 Smalltalk의 문법 요소이다. 이로써 위의 코드에 대한 설명은 끝이다. 더도 없고 덜도 없다.
여러분 앞에 [복잡한 문법 + 방대한 라이브러리]와 [간단한 문법 + 방대한 라이브러리]라는 두 가지의 선택이 있다면 이들 중 어느 것을 선택해서 배우겠는가? 곰곰히 생각해 볼 일이다
 

3) 복잡하다.

C언어의 어려움을 설명할 때 사용했던 코드를 Smalltalk로 옮겨보자.
if ((a == 3) || (b == 5)) printf("The result is %d\n", r);
원본
((a = 3) or: [b = 5]) ifTrue: [ Transcript print: 'The results is', r printString; cr ].
 
Smalltalk 코드의 대부분의 구성 요소들이 기호가 아니라 단어로 되어 있기 때문에 C언어의 코드보다 복잡도가 덜하고, 읽기도 쉽다.
 
역시 배열 변수를 다루는 코드를 Smalltalk로 옮겨보자.
 
int data[10]; // do something..... { for ( int i = 0; i < 10; i++ ) cout << data[i] << endl; }
원본
| data | data := Array new: 10. data do: [ :each | Transcript print: each; cr ]
 
우선 Smalltalk에서는 C언어의 자동 변수에 해당하는 것을 '임시 변수'라고 한다. 임시 변수는 | | 안에 적어주는 방식으로 선언할 수 있다. Smalltalk는 동적 자료형 언어이기 때문에 변수에 자료형을 적어줄 필요가 없다.
배열변수의 경우 C++에서는 위의 코드 처럼 정적으로 선언할 수도 있고 new[]와 delete[] 연산자를 사용해서 동적으로 생성할 수도 있다. 그러나 Smalltalk의 경우는 오직 동적으로만 생성이 가능하다. 위 코드의 경우는 Array 클래스에 #new: 메시지를 보내어 배열 클래스의 인스턴스를 생성한 뒤 그것을 data 변수에 지정하라는 명령이다.
Smalltalk에서의 컬렉션 객체들은 모두 #do: 라고 하는 일종의 반복자(iteator)를 구현하고 있다. 이 반복자는 객체의 내부 구조와는 상관 없이 그 객체의 원소를 하나씩  안에 있는 블럭 매개변수(위의 경우는 each)에 넘겨주고 따라서 사용자는 객체의 내부 구조에 상관 없이 하나씩 넘겨지는 원소를 대상으로 어떤 작업을 위해 필요한 메시지를 보내기만 하면 된다.
이 얼마나 간단하고 단순한가! 반복자를 쓰기위해 for 문이나 foreach 문을 사용할 필요가 없다. 이러한 반복자 이외에도 몇 가지가 더 있는데
✏️✐