http://www.zdnet.co.kr/techupdate/lecture/etc/0,39024989,39131935,00.htm





초보 개발자의 고민「첫 언어 선택은 이렇게!」

김정인(도명정보대학교 교수), 김재우(블루엣 인터내셔널 기술이사), 김상훈(동명정보대학교 연구원)  
2004/12/07        

첫 번째 프로그래밍 언어의 선택은 아주 중요하고도 민감한 문제다. 모든 프로그래머는 평생 동안 처음 배운 프로그래밍 언어의 영향에서 쉽게 벗어나지 못하기 때문이다. 이는 한국에서 태어나 한국어를 첫 번째 언어로 배운 사람이 영어나 일본어 등을 배울 때 자신이 처음 접한 언어인 한국어를 기본으로 그것과 비교하게 되는 것과 같은 이치이다.

첫 번째 배운 프로그래밍 언어는 프로그래머 사고의 기틀이 된다. 또한 프로그래밍은 분야별 특성을 감안한 구체적인 실용성을 논외로 하더라도 창의적 사고를 훈련하는 일반적인 교육 수단으로서의 가치도 있다. 모든 경우에 '첫 번째'라는 어휘가 갖는 의미는 그 뒤에 따르는 같은 수단이지만 다른 도구 또는 객체를 사용하는 비슷한 일들과는 다른 의미를 가지게 되고, 프로그래밍 언어에 있어서는 그 선택의 비중이 매우 크다고 할 수 있다. 그렇다면 처음 배우는 프로그래밍 언어로 어떠한 언어를 선택하는 것이 좋을까? 프로그래밍 언어의 패러다임을 매개로 하여 그 해답을 찾아보자.

모든 이가 프로그래밍을 배워야 하는 이유?
프로그래밍 학습은 문제의 해법 그 자체보다는 올바른 해법을 설계하는 절차를 오류 없이 기술해낼 수 있는 사고를 강조하며, 서술된 문제로부터 해법의 모형을 만들고 모형의 타당성을 검증하는 과정에서 창의성 및 비판적 사고와 추론 능력을 훈련하는 효과가 있다(이는 국내외의 다양한 교육과정 및 관련 연구를 통해 타당성이 입증되어 왔다).

그리고 프로그래밍 기술은 분야별 전문성과 생산성을 한 차원 높은 수준으로 끌어올리기 위해 기계 중심의 사고에서 벗어나 전문 응용 분야별 사고력과 표현력을 향상시키는 쪽으로 발전해왔다. 프로그래밍 기술이 이와 같은 방향으로 진보하고 있다는 것은 프로그래밍 작업이 전통적인 수행적 행위에 근접해가고 있다는 것을 입증하는 것이기도 하다.

프로그래밍 언어는 전산학이나 컴퓨터공학 같은 특정 분야를 전공하거나 집중적인 교육을 이수한 전문인만이 활용할 수 있는 도구의 기술에서 이미 벗어났고, 모든 분야의 전문 인력이 자신의 분야에서 발생하는 다양한 문제 풀이를 위해 그 분야 전문가들의 지식을 공유하고 해법을 나누기 위한 사고의 수단으로 자리매김하고 있다. 이제 프로그래밍 언어는 전문 프로그래머들의 전유물에서 벗어나고 있는 것이다.

CAD를 사용하는 건축 또는 조선 설계 전문가건, 컴퓨터공학을 전공하여 전문 프로그래머로의 길을 원하는 학생이건, 경영학이나 유통학을 공부해서 기업 경영을 꿈꾸는 경영학도건, 월말이면 직원들의 급여를 계산하기 위해 엑셀을 사용하는 경리부서의 직원이건 모두 알게 모르게 프로그래밍 언어를 사용하고 있다.

이렇게 사람들이 원하는 해답을 찾기 위해 문제를 풀어가는 방식은 프로그래밍 언어를 사용하여 문제를 해결하는 기법과 닮아가고 있다. 복잡한 이론을 설명하거나 논문의 예를 들지 않더라도 이는 현재 화제가 되고 있는 문제이며, 결론적으로 요즘 세상에서 뒤쳐지지 않으려면 누구나 프로그래밍 언어를 배워야 한다는 것이다.

프로그래밍은 여러 문제의 해법을 찾아내고 해법을 조합해서 더 큰 문제의 해답을 찾아내는 능력을 요구한다. 엑셀을 사용하는 능력을 기르고 싶다고 사설 학원에서 배우는 소프트웨어 사용법을 배우는 것은 곤란하다. 특정 회사의 소프트웨어 사용법을 기계적으로 암기하고 익숙하게 만드는 반복학습으로는 더 큰 문제가 주어졌을 때 올바른 방법으로 빠르게 풀어내는 능력을 키울 수 없기 때문이다.

대부분의 소프트웨어는 복잡한 문제를 논리적으로 접근하여 오류 없이 빨리 해결하는 데 그 목적이 있다. 창의적 문제 해결 도구로서의 소프트웨어 기술 활용을 익히기 위해서는 높은 추상화의 수준에서 문제를 해결하는 방법을 연구해야 하고, 다양한 문제 해결 기법을 종합 활용하여 개인의 생산성 향상을 체득할 수 있는 훈련이 필요하다. 그렇다고 해서 '막연히' 프로그래밍을 배우겠다는 생각은 좋은 것이 아니다.

프로그래밍 훈련이 문제 해결 능력에 도움을 주는 것은 사실이지만, 프로그래밍 언어를 '아무거나' 선택해서 '적당한' 방법으로 익힌다는 것은 문제 해결 능력의 향상에 별 도움을 주지 못한다. 표현 수준이 낮은 언어를 선택하거나, 문법이 너무 복잡한 언어를 선택하는 것은 낮은 언어의 표현을 문제를 해결하기 위한 표현 수준으로 끌어들이는 작업에 더 많은 노력을 기울여야 하기 때문이다. 또한 문법과 의미가 복잡한 언어를 선택했을 경우 언어 자체가 가진 문법과 언어를 이해하는 데 질려버려(문제 해결 기법을 익히고자 하는 목표에 도달하기도 전에 익혀야 하는 문서의 양에 질린다) 프로그래밍 언어 자체에 대한 흥미를 잃어버리게 되는 경우도 허다하다.

마이크로소프트(이하 MS)의 비주얼 베이직을 처음으로 배운 사람이라면 다른 모든 언어를 학습하고자 할 때 비주얼 베이직에 대한 이해를 기본으로 접근하게 된다. 비주얼 베이직은 MS 엑셀이나 DTS 등의 사무 생산성을 위한 도구나 데이터 관리를 위한 도구에 광범위하게 사용되어 실무 활용도가 높은 언어라는 장점이 있긴 하다. 하지만 비주얼 베이직은 문법과 의미가 복잡하게 설계되었고(영어 표현에 익숙하지 않은 한국인이라면 더욱 접근하기 힘들다), 애초에 언어 자체가 무원칙하게 설계되어 있어 순수한 수학적 사고에 기반한 문제 해결 능력을 익히는 데 좋지 않은 영향을 줄 수 있다.

어떤 언어가 적당하다고 할 수 있는가?
표현 수준이 낮은 언어를 배우게 되면 문제를 푸는 데 집중하지 못할 가능성이 커지게 된다. C/C++ 류의 언어를 예로 들어보자. C/C++는 문제의 해법을 개발하는 기법을 익히기에 앞서 전공 수준의 전문 지식을 요구한다. 정확히는 컴퓨터 시스템의 기억 공간의 관리 및 기계 수준의 효율적 데이터 처리 방식 등의 동작원리를 정확하게 이해하지 않으면 문제를 풀기위한 표현 수단을 개발하기 힘들다. 복잡한 문제를 풀기 위해서는 자료구조나 알고리즘에 대한 이해가 필수적이고, 문제를 풀기 위한 기반 표현 수단을 생성하기 위해서는 컴퓨터 시스템에 대한 전문적인 지식을 요구하게 된다. 자바나 C# 또한 마찬가지다(C#의 #은 C++의 ++을 겹쳐 놓은 것이라고 한다).

자바나 C#과 같은 언어는 C/C++에 비해 기계 중심적인 사고를 덜 요구하긴 한다. 하지만 자바 또는 C#은 객체지향성이라는 특정 패러다임을 문법적으로 강요한다. 또한 아주 단순한 응용에 있어서도(예를 들면 그림을 그린다거나 데이터를 처리하는 등의) 단순한 응용을 위한 전문 영역 언어를 별도로 학습해야 하는 부담이 따른다(포트란도 C/C++, 자바, C# 등의 단점을 그대로 공유한다).

물론 전문 프로그래머로 활동할 의사가 있는 사람이라면 조금 다르게 생각할 수도 있다. 전문 프로그래머가 될 사람이라면 산업계에서 널리 사용되고 있는 C/C++, 자바, C# 등의 언어를 익혀놓고, 특정 언어 기반에서 사용되는 모든 전문 영역 언어(Domain-Specific Language)를 사용할 수 있는 능력을 기르는 것이 앞으로의 밥벌이(?)에 지대한 영향을 줄 것이라고 생각하는 독자도 있을 것이다.

그렇다면 이쯤에서 반문하고 싶은 것은 자바 또는 C#이라는 언어가 시장에서 쓰이기 시작한 것이 얼마나 되었느냐 하는 것이다. C#은 MS에서 2001년 발표한 새로운 언어이다(발표된 지 3년이나 지난 언어를 새로운 언어라고 불러도 되는지는 모르겠다). MS라는 기업의 영향력 때문인지 C#은 새로 시작하는 프로젝트에서 가장 심각하게 고려되는 언어로까지 성장했다. 자바를 사용할 줄 아는 프로그래머라면 Anonymous Class 구조를 사용하는 이벤트 모델에 익숙할 것이다. 반면 C#은 델리게이트(delegate)를 사용하여 이벤트를 구현한다. 첫 번째 언어로 자바를 선택하고 죽도록 공부한 사람이라면 C#의 이러한 구조가 쉽게 받아들여질 리 만무하다.

전문 프로그래머로 활동할 의사가 있는 사람이라면 급변하는 프로그래밍 환경에 탄력적으로 적응할 수 있는 훈련도 필요하지만 첫 번째 프로그래밍 언어는 이러한 프로그래밍 환경에 적응하는 기반을 마련해 줄 수 있는 언어를 선택하는 것이 마땅하다. 'C#을 잘 사용하는' 프로그래머보다는 '프로그램을 잘 짜는' 프로그래머가 되는 것이 우선이다.

과연 C/C++는 마땅하지 않은가?
C/C++, 자바, C#처럼 기계 중심 패러다임(Imperative Paradigm)을 사용하는 언어를 사용하는 것이 바람직하지 않다고 이야기할 수는 없다. 오히려 '반드시 거쳐가야 되는' 쪽이 더 적합한 표현일 것이다. 분명히 C/C++는 현재 사용되고 있는 프로그래밍 기술을 충분히 맛보기에 아주 좋은 언어이다. 또한, 기계 중심 패러다임이 문제를 풀이하는 데 반드시 사용되어야 하는 경우도 허다하다. SICP(Structure and Interpretation of Computer Programs, MIT Press)에서 빌려온 다음 예를 살펴보자.

"우리는 보통 세상을 독립 물체들이 살고 있는 곳으로 본다. 각 물체는 자기만의 상태가 있고, 그 상태는 시간에 따라 변화한다. 어떤 물체가 '상태를 가진다'는 말은 그 행동이 그 역사를 지닌다는 뜻이다. 은행 거래를 예로 들면, '지금 100달러를 인출 할 수 있는가?'라는 질문의 답이, 예금과 인출의 지나간 역사에 의존한다는 점에서, 은행 계좌는 상태를 가진다고 할 수 있다."

시간에 따라 상태가 변하는 계산상의 물체라는 것이 어떠한 것인지를 설명하기 위해 은행 계좌에서 돈을 인출하는 상황을 예로 들어보자. 인출 동작은 인출 금액을 amount라는 인자로 받는 withdraw라는 프로시저로 표현한다. 계좌에 예금된 돈이 인출하기에 충분한 금액이라면 withdraw 프로시저는 잔고를 보여주고, 그렇지 않다면 ‘잔고 부족’이라는 문자를 보여주어야 하는 상황이다.

C#을 사용하여 이와 같은 상황을 프로그래밍한다면 객체지향 패러다임(Object-Oriented Paradigm)에 입각하여 <리스트 1>과 같이 프로그래밍할 것이다.

  <리스트 1> C#으로 작성한 코드
  

public class Account {
    private int balance;
    
    public Account(int balance) {
        this.balance = balance;
    }

    public void Withdraw(int account) {
        if (this.balance >= account)
            Console.WriteLine(this.balance - account);
        else
            Console.WriteLine("잔고 부족");
    }
}



프로그램을 실행하려면, Account 객체를 생성하고 생성된 객체에서 값을 빼나가야 한다.


Account account = new Account(100);
account.Withdraw(25);
(75를 화면에 출력한다)
account.Withdraw(90);
("잔고 부족"을 화면에 출력한다)
account.Widthdraw(15);
(60을 화면에 출력한다)


SICP에서는 우리에게 익숙한 이와 같은 프로그래밍 방법을 다음과 같이 설명한다.


"각 물체는 상호 작용으로 객체간에 의존하는 상태 변화를 만들면서 서로에게 영향을 준다. 이렇게 시스템을 분리된 객체의 합성이라고 보는 시각은, 시스템의 상태 변수를 밀도 있게 결합된 여러 개의 서브 시스템으로 묶을 수 있고 서브 시스템끼리는 느슨하게 묶인 구조를 유지하는 경우에 아주 쓸모 있다."

이런 사고방식은 시스템 계산 모델의 조직을 짜맞출 때 아주 쓸모 있는 틀이 된다. 모듈별로 잘 구성된 모델을 만들려면 시스템 내의 실제 물체를 본뜬 계산상의 물체로 모델을 분할해야 한다. 각 계산 객체는 실물의 상태를 묘사하는 자기 상태 변수(local state variable)를 가지고 있어야 한다. 그리고 실물(Object)의 상태는 시간에 따라 변화하기 때문에 이에 대응하는 계산상의 물체 상태 변수가 존재해야 한다. 컴퓨터의 계산 시간으로 시스템의 시간을 흉내내고자 한다면, 프로그램 실행 중에 행동을 바꿀 수 있는 객체를 표현할 수 있는 방법이 있어야 한다. C/C++, 자바, C#에서는 이와 같이 프로그램 실행 중에 행동을 바꿀 수 있는 객체를 표현할 수 있는 방법으로 배정 연산자(Assignment Operator)를 지원한다. 물론 '= '이다.

다시 첫 번째 프로그래밍 언어의 선택 문제로 돌아가 보자. 앞의 예제는 너무도 간결하지만 첫 번째 프로그래밍 언어로 C/C++, 자바, C# 등의 언어가 마땅하지 않은 이유는 우선 문법과 의미가 상당히 복잡하다는 것이다. C++는 기초 수준의 언어만을 익히는 데 수천 페이지의 전문적인 설명서가 필요하다. C++보다 훨씬 의미가 간결한 자바나 C#만 하더라도 대부분의 관련 교재에서 문제 해결 방법을 이야기하는 것이 아닌 언어 그 자체의 문법과 의미를 설명하는 데만 수백 페이지를 할애하는 데 그 문제가 있다.

두 번째 문제는 객체지향성과 같은 특정 패러다임의 이해를 문법적으로 강요한다. <리스트 1>에서 Account 클래스를 선언했는데 자바 또는 C#에서는 이와 같이 클래스 없이는 프로그램을 작성할 수 없고, 언어적으로 객체지향 패러다임을 강요하게 된다. SICP에서 교육용 언어로 선택한 Scheme에서 배정 연산자는 다음과 같이 사용할 수 있다.


(set! )


C#으로 작성한 <리스트 1>을 Scheme으로 작성하면 <리스트 2>와 같이 된다.

  <리스트 2> Scheme으로 작성한 코드
  

(define balance 100)
(define (withdraw amount)
    (if (>= balance amount)
        (begin     (set! balance (- balance amount))
            balance)
    "잔고 부족"))



<리스트 1>과 마찬가지로 balance 변수가 공유 환경에서 동작하지 않도록 하고 싶다면 다음과 같이 해서 '가두어 이름 짓기' 기법으로 withdraw 내부에 가두어버릴 수 있다.

(define widthdraw
    (let ((balance 100))
    (lambda (amount)
        (if >= balance amount)
            (begin     (set! balance (- balance amount))
                balance)
        "잔고 부족")

여기서는 Scheme 문법을 잘 모르는 독자들이 보기에 익숙하지 않은 명령들이 있으므로 어려울 수도 있지만 <리스트 1>과 <리스트 2>를 비교해 보았을 때, 어떤 언어를 선택하는 것이 ‘문법의 횡포’로부터 자유로울 것인가를 느낄 수 있을 것이다.

C 언어 문법과 고급 프로그래머가 되는 길      

인터넷에 떠도는 이야기 1 - 켄 톰슨과 데니스 리치의 가상 인터뷰
우린 멀틱스를 보고(가능한한 아주 복잡하고 암호같이 모호해서) 일반 사용자들은 아예 사용할 엄두를 내지도 못할 새로운 시스템을 설계했습니다. 그리고 멀틱스의 패러디로 이름을 유닉스로 정했죠. 뭐 일부는 좀 비꼬는 듯한 암시를 주기 위한 이유도 있었지만요. 그 다음 데니스와 브라이언은 파스칼을 완전히 뒤섞어 놓은 듯한 언어를 만들고 이름을 `A`라고 했습니다.

그 뒤 사람들이 그 언어로 진짜 중요한 프로그램을 개발하려고 시도하고 있다는 것을 알고 나서 우리는 재빨리 언어를 암호화해서 더욱 사용하기 어렵게 만들었고 이 언어는 `B`를 거쳐 BCPL, 그리고 결국 C가 되었습니다. 우린 다음과 같은 문장을 깨끗하게 컴파일할 수 있을 때가 돼서야 비로소 개발을 중단했습니다.

for(;P("\n"),R-;P("|"))for(e=C; e-; P("_"+(*u++/8)%2))P("|"+(*u/4)%2);

현대의 프로그래머들이 이렇게 암호 같은 문장을 허용하는 개떡 같은 언어를 사용할 것이라고는 전혀 생각지 못했습니다. 그건 우리의 상식 밖이었죠. 우린 실제로 이걸 소련에 팔아서 소련의 컴퓨터 과학기술을 20년 이상 퇴보하게 만들 생각이었거든요.

인터넷에 떠도는 이야기 2 - 초급 프로그래머와 고급 프로그래머의 차이
다음은 모두 모니터 화면에 "Hello, World"를 찍기 위한 프로그램입니다.

대학 신입생
program Hello(input, output)
begin
writeln('Hello World')
end.

신임 교수
#include
void main(void) {
char *message[] = {"Hello ", "World"};
int i;

for(i = 0; i < 2; ++i)
printf("%s", message[i]);
printf("\n");
}

계약직 전문가
#include

class string {
private:
int size;
char *ptr;

public:
string() : size(0), ptr(new char('\0')) {}

string(const string &s) : size(s.size) {
ptr = new char[size + 1];
strcpy(ptr, s.ptr);
}

~string() {
delete [] ptr;
}

friend ostream &operator <<(ostream &, const string &);
string &operator=(const char *);
};

// 생략...
str = "Hello World";
cout << str << endl;
return(0);
}

숙련된 해커
% cc -o a.out ~/src/misc/hw/hw.c

구루(지존) 해커
% cat
Hello, world.
^D


  

  


다양한 프로그래밍의 틀을 활용할 수 있는가?
흔히 말하는 객체지향 언어가 아닌 Haskell이나 Scheme을 사용해서도 C#이나 자바와 같은 객체지향 언어가 하고자 하는 메시지 전달(message-passing) 프로그래밍 기법을 사용할 수 있다. 다음 프로시저는 인출 계산기를 표현하는 프로시저이다.


(define (make-withdraw balance)
    (lambda (amount)
        (if (>= balance amount)
            (begin     (set! balance (- balance amount)
                balance)
            "잔고 부족")


다음과 같이 make-withdraw를 2번 선언하면 W1과 W2는 두 개의 물체를 만드는 목적으로 사용할 수 있다. W1과 W2는 각각 자신만의 상태변수 balance를 가지고 있는 완전한 독립물체이다.


(define (W1 (make-withdraw 100))
(define (W2 (make-withdraw 200))


다음과 같이 하면 인출뿐만 아니라 예금(deposit)을 관리하는 물체도 만들 수 있고, 그렇게 하면 간단한 은행 계좌를 표현하게 된 셈이다. 다음은 초기 잔고를 주고 ‘은행 계좌’를 만들어 주는 프로시저다.


(define (make-account balance)
  (define (withdraw amount)
    (if (>= balance amount)
        (begin (set! balance (- balance amount))
               balance)
        "Insufficient funds"))
  (define (deposit amount)
    (set! balance (+ balance amount))
    balance)
  (define (dispatch m)
    (cond ((eq? m 'withdraw) withdraw)
          ((eq? m 'deposit) deposit)
          (else (error "Unknown request -- MAKE-ACCOUNT"
                       m))))
  dispatch)


이와 같이 만든 make-account는 계좌를 표현하는 완전한 독립 물체, 즉 객체로 동작하고, Dispatch 프로시저 그 자체가 은행 계좌 물체를 표현하는 값이 된다. 이는 객체지향 패러다임을 완전하게 구현한다. 또한 배정문을 사용하여 메모리의 지정한 곳에 값을 저장할 수 있다는 점에서 기계 중심 패러다임을 사용할 수 있는 환경이라는 것 또한 알 수 있다.

C/C++, C#, 자바 등의 언어 패러다임인 이 방식은 컴퓨팅 역사(Computing History)를 순차적으로 풀어야 하는 문제에 가장 적합한 방식이다. 하지만 득이 있으면 실이 있는 법, 배정 명령의 사용에서 발생하는 손실은 당연히 발생한다. 배정 명령을 사용하지 않는다면, 같은 프로시저를 인자로 프로시저를 실행하면 언제나 같은 값이 나오게 된다. Square를 계산하는 다음과 같은 함수가 있다고 하자.


square(x) = x * x


함수를 연산하는 프로시저를 다음과 같이 작성할 수 있다.


(define (square x) (* x x))


작성된 프로시저에 2 값을 주면 언제나 4라는 값을 반환한다. 이런 경우 프로시저를 수학적 함수(Function)로 볼 수 있고, 이를 값 중심(Value-Oriented) 프로그래밍이라고 한다. '대치 계산 모델(Substitution Model)’은 프로시저의 적용이란 인자를 값으로 바꾸어서 프로시저의 값을 계산하는 방법으로, 값(함수) 중심 패러다임(Functional Paradigm) 전개 방식이다. 제곱들의 합을 계산하는 sumofsquare 함수는 다음과 같이 작성된다.


sumofsquare(x) = square(x) + square(x)


하지만 배정문을 사용할 때 대치 모델은 더 이상 사용할 수 없게 된다. 프로그래밍 언어에서 널리 사용되는 개체와 배정 명령은 사실 그 적절한 이론적 기반이나 깔끔한 수학적인 모델이 존재하지 않기 때문이다.

배정 명령은 문제를 복잡하게 만든다. 배정 명령이 어떤 식으로 문제를 복잡하게 만들 수 있는지 알아보기 위해 일정 금액 balance에서 입력한 금액을 빼는 make-decrementer 프로시저를 작성해 보자. 배정 명령문을 사용하지 않았기 때문에, 금액을 누적하는 효과는 없다.


(define (make-decrementer balance)
  (lambda (amount)
    (- balance amount)))


배정문을 사용하지 않으므로 대치 모델로 make-decrementer가 어떻게 식의 계산 과정을 분석하여 어떻게 동작하는지를 알아낼 수 있다.


((make-decrementer 25) 20)


식을 조합하는 연산자를 간략하게 만들기 위해 make-decrementer를 펼치고 balance를 25로 대치한다. 그러면 다음과 같은 식으로 줄일 수 있다.


((lambda (amount) (- 25 amount)) 20)


이제 lambda 식 내의 amount를 20으로 대치해서 연산자를 적용하면 다음과 같다.


(- 25 20)


마지막 계산 결과는 5다. 그러나 배정문을 사용한 다음 프로시저는 이와 같은 결과를 기대할 수 없게 된다.


(define (make-simplified-withdraw balance)
  (lambda (amount)
    (set! balance (- balance amount))
    balance))


같은 방식으로 make- simplified-withdraw를 분석해 보자.


((make-simplified-withdraw 25) 20)


먼저 연산자를 간략하게 만들기 위해 make-simplified-withdraw 내의 balance를 25로 대치하면 다음과 같은 식이 된다.


((lambda (amount) (set! balance (- 25 amount)) 25) 20)


이제 lambda 식의 amount를 20으로 대치하여 연산자를 적용한다.


(set! balance (- 25 20)) 25


엄격하게 대치 모델에 따라 이 프로시저 적용 과정이 뜻하는 바를 설명해야 한다면, 우선 balance를 5로 둔 다음, 25가 전체 식을 계산한 값이 된다고 할 수밖에 없다. 즉, 이런 식으로 계산하면 틀린 답이 나온다. 맞는 답을 얻으려면 어떻게든 첫 번째(set! 실행의 효과를 반영하기 전의) balance와 두 번째(set!을 실행한 효과가 반영된) balance를 반드시 구분할 수 있어야만 한다. 그러나 대치 모델로는 이런 현상을 설명할 수 없다.

이런 문제가 생기는 원인은 결국 대치 모델이 언어 내의 심벌이란 값에 붙인 이름일 뿐이라는 사실에 바탕을 두고 있기 때문이다. 그러나 set!을 쓰면서 변수의 값을 바꿀 수 있게 되면서부터 변수는 그저 이름일 수가 없다. 이제 변수는 어떻게든 값을 저장했다가 필요에 따라 바꿀 수도 있는 ‘저장소’를 가리키게 된다.

어떤 언어에서 언제나 식의 결과에 영향을 주지 않고 ‘같은 것으로 같은 것을 대치할 수 있다’는 개념이 보장된다면, 그 언어가 ‘참조에 투명(referentially transparent)’하다고 말한다. 우리가 써온 컴퓨터 언어에서 배정문을 쓰게 되는 경우 참조 투명성을 보장할 수 없다. 이로 인해 언제 대치 모델을 써서 식을 줄여도 되는지 판단하기 쉽지 않다. 결국, 배정 명령을 사용하는 프로그램은 엄청나게 논증하기가 어려워진다.

일단 참조 투명성을 포기하고 나면 두 객체가 ‘같다’는 개념을 정형적 방법으로 잡아내는 게 어려워진다. 사실, 실세계에서는 ‘같다’는 의미, 그 자체가 깔끔하지 않다. 일반적으로 확실히 동일한 두 개체가 정말 같은지 증명할 수 있는 유일한 방법은 한 개체를 고쳤을 때 다른 개체도 같은 방식으로 변하는지를 살펴보는 것이다. 그러나 ‘같은’ 개체를 두 번 관찰해서 그 개체가 가지는 어떤 속성이 처음과 다르다는 사실을 확인하는 것 외에 정말 하나의 개체가 ‘변했다’고 말할 수 있는 다른 방법이 있을까? 그래서 ‘같음’의 개념을 먼저 정립하지 않고서는 ‘변화’를 판단할 방법이 없는 것이고, 변화의 효과를 살피지 않고서는 같음을 입증할 수 없는 것이다.

C#이나 자바 같은 언어에서 값 타입의 ‘같음’과 참조 타입의 ‘같음’이 확연하게 구분되는 이유도 바로 여기에 있다. 하지만 C/C++, 자바, C# 등의 언어를 사용한다면 우선의 구현이 눈앞을 가리게 되고 그 구현에만 급급하면 프로그래밍 언어의 목적인 문제를 올바른 방법으로 해결하는 데에 대한 생각이 무색해지게 된다.

SICP에서는 이런 문제의 해결을 위하여 스트리밍 패러다임(Streaming Paradigm)이라는 기법을 제안하고, “이렇게 좋은 대안이 있는데도 여전히 무식한 기법을 쓸래?”라고 묻는다. 여러 패러다임을 다 수용할 수 있는 언어를 첫 언어로 선택함으로써 얻을 수 있는 이점은 바로 이런 것이라 할 수 있다. 프로그래밍을 업으로 삼는 사람과 그렇지 않은 사람을 막론하고, 여러 방향으로 문제를 해결할 수 있는 방법을 제시하고, 문제를 해결하는 방법에 대한 원론을 제시함으로써 다른 문제에 부딪히게 될 때 올바른 해결 방안을 찾아갈 수 있는 길을 열어준다는 것이다.

맥락을 제거한 언어의 우열 논의는 의미 없다
이 글을 읽은 독자가 “그렇다면 자바가 안 좋은 언어라는 말입니까?“ 또는 ”지금 열심히 C#을 배우기 시작했는데 말짱 헛일이었다는 말입니까?“라고 질문할 지도 모르겠다. 필자에게 누군가 그런 질문을 직접 한다면 적당한 말로 얼버무리거나 그 자리를 피하려 할 것이다. 그런 질문 자체가 넌센스이기 때문이다.

어디를 가든지 자신이 하고 있는 것이 옳은 것이고 더 힘든 것이며 심오한 영역에 있는 것이라고 생각하는 사람들이 있는 법이다. 독일어를 쓰는 게르만 족이 머리가 좋은 이유가 독일어의 사용이 사용자의 정신 능력을 향상시키는 데 일조하기 때문이라는 연구가 나온 적도 있었다. 하지만 언어학자들의 연구 결과는 한 언어가 표현할 수 있는 것은 다른 모든 언어로도 표현 가능하다는 것이었다. 따라서 자바와 C# 등 언어의 우열 논의는 무의미한 것이다.

자바로 표현 가능한 것은 C#으로도 표현 가능하기 때문이다. 문제는 자바 또는 C#을 가지고 주어진 문제를 어떻게 표현하느냐 하는데 달린 것이므로 각 언어의 우위를 구분하는 것은 아주 어리석은 일이 된다.

여러 인터넷 게시판에서 아직도 C 언어를 가르치고 있는 국내 대학의 현실에 개탄하면서 이제는 C 따위는 버리고 자바를 가르쳐야 한다고 목청 높여 외치는 글을 여러 번 봤다. C 언어를 가지고 하드웨어를 직접 제어할 일이 별로 없는 지금의 현실에서 C 언어를 배우는 것이 큰 의미가 없다는 주장이다. C/C++가 기계에 직접 명령을 내리기에 아주 적합한 구조의 언어라는 것은 확실하다. 그리고 또 한 가지 확실한 것은 대학에서 배우는 정도의 C/C++로는 하드웨어를 확실하게 제어하기 어렵다는 것이다(API를 조작하거나 OS 레벨을 조작하여 결과를 얻어내는 경우도 있긴 하다. 필자는 한 학생이 OS 레벨을 조작한 프로그램으로 블루 스크린이 아닌 레드 스크린을 화면에 펼치고 있는 것을 본 적이 있다).

여기까지 신경 써서 글을 읽은 독자들이라면 필자가 무슨 말을 하고 있는지 이해할 수 있을 것이라 믿는다. 요즘 시대에 선택해서 사용할 수 있는 프로그래밍 언어는 수없이 많다. 이렇게 많은 언어들 중에서 어떤 언어를 자신의 첫 언어로 선택하여야 할까? 물론 아무 언어나 선택해서 공부해도 열심히만 공부하고 언어의 맥락을 이해하여 문제 해결에 집중할 수 있다면 상관없다. 하지만 첫 언어가 앞으로의 프로그래머로서의 인생에 크게 영향력을 미치는 중요한 요소가 될 것임을 생각한다면 첫 언어의 선택은 신중해야 할 필요가 있다. 이제 결론을 내려보자. 첫 번째 프로그래밍 언어의 선택은 다음 두 가지 기준을 모두 만족해야 한다.


◆ 현재의 프로그래밍 기술을 충분히 맛볼 수 있는가?
◆ 다양한 방식으로 프로그래밍할 수 있는 생각을 갖출 수 있도록 하는가?


한 마디로 말하자면 고급 문제를 쉽게 해결할 수 있고, 특정 패러다임에 종속적인 사고를 강요하지 않으면서도 모든 패러다임을 다 수용할 수 있는 언어라면 아무거나 골라 써도 좋다는 말이다. 물론 그렇게 선택하여 배운 언어가 산업 현장에 투입되었을 때 그다지 많이 활용되지 않는 언어일 수도 있다. 하지만 이 두 가지 기준을 모두 만족하는 언어를 사용하여 여러 패러다임을 전개하며 문제를 해결하는 방식을 충분히 습득했다면 그 사람은 아마 여러 사람이 감탄하는 좋은 프로그램을 작성하고 있을 것이다.

아직도 C/C++ 또는 자바, C#을 첫 프로그래밍 언어로 공부하는 것이 옳다고 생각하는 사람이 있다면 MS가 2006년에 발매할 예정인 윈도우 롱혼의 권장 사양을 살펴보라. 윈도우 롱혼이 나올 때쯤이면 CPU가 2개 달리고 현재로서는 상상할 수 없는 큰 메모리가 장착된 컴퓨터를 홈쇼핑에서 판매할 것이다. CPU가 2개 달린 컴퓨터가 보편화되었을 때 변화할 상용 언어들의 패러다임에 대해 상상해 보는 것도 즐거운 시간이 될 것이다. @

* 이 기사는 ZDNet Korea의 자매지인 마이크로소프트웨어에 게재된 내용입니다

신고
크리에이티브 커먼즈 라이선스
Creative Commons License

+ Recent posts