세상에는 수많은 프로그래밍 언어가 존재한다. 각 프로그래밍 언어는 자신만의 독특한 장단점을 가지고 있기 때문에 모든 프로젝트에 적합한 최고의 프로그래밍 언어는 존재하지 않는다. 결국 프로젝트의 요구 사항과 성격, 개발자의 자질, 여러 환경 요소를 모두 고려하여 가장 적절한 프로그래밍 언어를 고르는 일이 중요하다. 이 글에서는 프로그래밍 언어를 선택할 때 어떤 요소들을 고려해야 하는지 알아보자.
소프트웨어 개발자들은 성경에 나오는 바벨탑의 교훈을 잊어버린 걸까? 세상에 존재하는 수많은 언어 때문에 존재하는 불편함을 잘 알고 있는 프로그래밍 언어 개발자들이 왜 각자 문법과 어휘가 다른 수많은 프로그래밍 언어를 만들어 냈을까? 프로그래밍 언어는 단순히 의사소통을 하기 위한 수단만은 아니다. 소프트웨어는 세상에 산재하는 수많은 도메인(domain)의 문제를 풀어야 하고, 이 모든 문제들을 쉽게 풀 수 있는 최적의 언어는 존재하기 힘들기 때문이다. 소프트웨어 개발자들은 자신이 개발해야 하는 소프트웨어의 성격에 따라 프로그래밍 언어를 달리해야 한다. 시스템 소프트웨어를 작성한다면 C 언어와 어셈블리 언어가 적합할 것이며, 간단히 자신의 알고리즘을 테스트해보거나 프로토타이핑을 한다면 인터프리팅(interpreting)되는 스크립트 언어가 적합하다.
비교적 간단한 프로그램을 작성해보는 것이라면 사실 어떤 언어를 사용해도 무방하다. 하지만 비교적 큰 프로젝트의 산출물은 여러 가지 요소에 의해서 그 품질이 결정되는데, 여기에는 적절한 프로그래밍 언어의 선택이 중요하다. 따라서 프로젝트 초기에 프로젝트를 계획하면서 개발 도구와 환경을 결정할 때 프로그래밍 언어를 정하는 과정도 반드시 포함되어야 한다. 이 과정에서 여러 개의 프로그래밍 언어를 후보로 올려놓고, 프로젝트의 성격에 따른 여러 기준을 적용하며 의사 결정을 하게 된다. 프로그래밍 언어를 고르는데 있어서 절대적인 잣대는 없지만, 이 글에서는 이 과정에서 사용할 수 있는 몇 가지 유용한 판단 기준을 제공하고자 한다.
컴파일 vs. 인터프리터
숙련된 프로그래머 중에서 컴파일되는 프로그래밍 언어와 인터프리터되는 프로그래밍 언어의 차이점을 모르는 사람은 없을 것이다. 인터프리터되는 언어는 비교적 빠른 속도로 프로그램을 짤 수 있고, 결과를 쉽게 확인할 수 있다. 반면 컴파일되는 언어는 수행 속도가 빠르다. 프로그래밍 언어를 선택할 때 가장 먼저 적용해봐야 할 잣대 중에 하나는 해당 언어의 컴파일러나 인터프리터가 존재하느냐는 것이다.
경우에 따라서는 컴파일러와 인터프리터가 모두 존재할 수도 있고, 두 가지가 혼합된 형태일 수도 있다. 예를 들어 자바의 경우 자바 프로그램이 바이트코드(bytecode)로 컴파일되고, 자바 가상 머신(Java Vir tual Machine)에 의해서 다시 인터프리트되는 것이 일반적이다. 그러나 모바일 컨텐츠나 게임을 만들 때는 자바로 프로그램을 만들고 수행 속도 향상을 위해 타겟 플랫폼에 맞춰 미리 컴파일(AOTC, Ahead Of Time Compilation)하는 경우가 흔한데, 이는 자바 언어의 컴파일러라고 볼 수 있다. 비슷한 예로, 수행되기 직전에 머신 코드로 컴파일되는 JIT(Just In Time) 컴파일 방법도 있다.
같은 언어로 프로그래밍되더라도 컴파일러냐 인터프리터냐에 따라 성능과 사용성이 크게 달라진다. 일반적으로 컴파일되는 프로그래밍 언어는 두 가지 장점이 있는데, 하나는 수행 속도 향상이고 또 하나는 소스코드의 암호화(obfuscation)이다. 컴파일러가 인터프리터에 비해 같은 프로그램을 빠른 속도로 수행하게 만드는 것은 분명하다. 컴파일 시간은 런타임과 별도로 한 번만 수행되면 되기 때문에 각종 최적화(optimization) 기술을 적용하기가 유리하다. 물론 최근에는 자바 가상 머신(JVM)이 이런 컴파일러의 최적화 기술을 빌려와 동적으로 머신 코드를 생성하는 기술(adaptive compilation or HotSpot)을 도입했지만, 일부 최적화 기술은 그 특성상 많은 시간이 걸리기 때문에 같은 언어를 컴파일하여 얻어진 프로그램보다 빠를 수는 없다.
또 하나의 차이점은 소스코드를 얼마나 알아보기 힘들게 만들 수 있는가이다. 컴파일된 프로그램의 경우 해당 고급 언어의 특징이 거의 사라진, 기계어로 번역되어 있기 때문에 머신 코드를 바탕으로 원본 소스코드를 복원해 낼 수 있는 가능성은 거의 없다. 반면에 인터프리터되는 언어는 수행을 위해 소스코드가 건네져야 하므로, 소스코드 공개 의사가 없는 상업적인 프로젝트의 경우 해결하기 힘든 문제에 봉착할 우려가 있다.
필자도 프로젝트를 수행하면서 소스코드의 비밀 유지 때문에 많은 문제를 겪었다. 필자는 디지털 텔레비전을 위한 미들웨어 플랫폼을 만드는 일을 했었는데, 여기서 하드웨어와 운영체제를 바탕으로 시스템을 빌드하는 일은 C 언어로 했지만, 해당 플랫폼이 자바를 기반으로 하기 때문에 API를 자바 기반으로 작성해야 했다. 문제는 이를 배포하기 위해 바이너리를 줘야 하는데, C 언어로 컴파일된 바이너리는 문제가 없었지만 자바로 작성한 클래스를 배포할 때 문제가 생겼다.
자바의 경우 일단 한 번 바이트코드로 컴파일된 후에 자바 가상 머신 위에서 인터프리트되지만, 바이트코드는 가상 머신에서 동작하는 일종의 기계어임에도 불구하고 실제 기계어인 인텔이나 ARM, PowerPC 등의 어셈블리 언어에 비해서는 대부분의 자바 심볼(symbol)을 보존하고 있는 고급 언어라 할 수 있다. 시중에는 자바의 클래스 파일을 바탕으로 원래의 자바 소스코드를 복원해주는 디컴파일러(decompiler)가 많이 존재한다. 따라서 자바로 만든 프로그램을 배포할 때는 이를 컴파일하여 클래스 파일만 배포하더라도 누군가가 소스코드를 엿볼 수 있다는 불안에 시달려야 하는 것이다.
필자의 회사에서 시도한 첫 번째 방법은 자바 소스코드를 뒤죽박죽으로 만들어 주는 프로그램(Java Obfuscator)을 이용하는 것이었다. 이 프로그램은 원래 자바 프로그램의 의미를 훼손시키지 않고, 단순히 변수 이름과 클래스 이름을 a, b, c 등의 의미 없는 이름으로 바꾸어서 해당 프로그램을 해독하기 힘들게 만들어 준다. 그러나 이 방법도 완벽하진 않았다. 클래스와 변수 이름을 이상하게 바꿀 수는 있어도, 외부로 참조해야 하는 API의 이름을 바꿀 수는 없을 뿐더러, 비교적 제한된 애플리케이션 범주라면 숙련된 자바 프로그래머의 경우 바이트코드에서 복원된 소스코드를 읽는 것이 어렵지 않기 때문이다.
이 미들웨어는 결국 자바 클래스 파일을 JAR가 아닌 별도의 암호화 방법으로 압축했다. 그리고 이 클래스를 읽는 방법은 비밀에 붙인 채, C 언어로 암호화된 클래스 파일을 해석하여 원래의 클래스로 복원하여 자바 가상 머신에 로딩해 주는 방법을 사용하게 되었다. 이와 달리 일반적인 애플리케이션의 경우 인터프리팅되는 언어를 사용한 프로그램의 소스코드가 공개되지 않기를 바란다면, 이는 패키지로 만들어서 서버에서 수행한 후 결과만 웹 브라우저를 통해 알려주는 서블릿(servlet) 형태가 되어야 할 것이다.
결국 프로젝트에 있어서 성능이 중요하거나 소스코드 유출 방지가 높은 우선순위를 차지한다면, 인터프리터 언어가 주는 유용성, 개발의 편리성을 감안하더라도 치러야 할 대가가 크다는 사실을 알 수 있다. 따라서 특정 프로그래밍 언어를 선택하기 위해서는 해당 프로그래밍 언어의 컴파일러가 존재하는지, 혹은 인터프리터가 존재하는지를 확인해보는 것이 필요하다.
언어간 통합 문제
완벽한 한 가지 프로그래밍 언어는 존재하지 않는다고 말했다. 프로그래밍 언어가 가지는 각각의 특징 때문에 한 프로젝트 안에서 2~3개 이상의 프로그래밍 언어를 사용해야 할 경우는 생각보다 빈번하다. 이 경우 각각의 프로그래밍 언어가 가지는 특징도 중요하지만, 두 언어가 얼마나 유기적으로 잘 융합되어 하나의 시스템을 만들 수 있는지도 중요한 판단 기준이 된다. 대부분의 소프트웨어를 C++로 작성하고, 일부 테스트 프로그램과 간단한 유틸리티를 파이썬으로 작성했다면 언어간 통합은 큰 문제가 아닐 수도 있다. 그러나 시스템의 특성상 2가지 이상의 언어가 서로 유기적으로 얽혀서 구성되어야 하는 경우 두 언어간 통합이 원활하지 않다면 이는 프로젝트의 위기로 연결될 수도 있다.
필자는 프로그래밍 언어간 통합 문제로 크게 고생을 한 적이 있다. 이미 언급했듯이 필자가 참여했던 DTV용 미들웨어 프로젝트는, 셋탑 박스에 올라가는 실시간 운영체제(Real-time OS) 위에 미들웨어를 올리고 미들웨어 응용 프로그램을 위한 자바 API을 제공해야만 했다. 즉 자바 API는 자바로 작성하되, 셋탑 박스(STB)의 하드웨어 자원에 접근해야 하는 경우 C 언어를 사용할 수밖에 없었다. 또한 셋탑 박스 또한 임베디드 환경이기 때문에 메모리가 한정되어 있고, CPU 속도가 충분치 않아 자바로 수행할 경우 속도가 느린 모듈은 C 언어로 내리는 작업도 병행해야 했다.
자바의 경우 “한 번 작성하고 모든 곳에서 사용한다(Write Once, Run Everywhere)”는 모토에 따라 시스템 의존적인 C 언어와 통합할 이유가 없을 것 같다. 하지만 실제로 임베디드 시스템의 경우 자바를 사용하더라도 하드웨어가 제공하는 고유의 기능에 접근하기 위해서는 C 언어로 된 시스템 API를 호출해야 하기 때문에 C 언어와의 통합이 필수적인 경우가 많다. 자바도 이 필요성을 인지하고 JNI(Java Native Inter face)라고 하여, 자바 메쏘드를 C 언어로 작성할 수 있는 표준 인터페이스를 정의해 놓았다.
C 언어와의 통합을 위한 표준 인터페이스까지 존재하는 언어인 자바는 C 언어와 통합이 필요한 경우 이상적인 언어라고 판단할 수도 있다. 사실 다른 언어와의 인터페이스를 표준화하거나 구체적으로 명시한 언어조차 드문 상황이다 보니, 이 정도면 언어간 통합 문제를 어느 정도 해결해주고 있지 않느냐는 결론을 내릴 수도 있다.
그러나 실제로 자바와 C를 사용하여 프로젝트를 수행한 필자의 경험에 따르면, 언어간 통합은 절대로 쉬운 일이 아니었다. 자바에서 C 언어로 작성된 라이브러리를 로드하고 이 메쏘드를 부르는 것은 물론이고, C 언어로 작성된 자바 메쏘드에서 자바 객체의 필드에 접근하고 자바 메쏘드를 호출하게 만드는 일이 결코 간단하지 않았다. JNI 자체가 프로그래머의 실수를 유도하기 쉽게 만들어진 면도 많았고, 숙련된 프로그래머조차 버그 없이 한 번에 C와 자바를 통합하는 일은 힘들었다.
이는 어떻게 생각해보면 당연한 일인지도 모른다. C 언어에서 자바 메쏘드를 호출하고 객체를 사용하기 위해서는 기존의 C 언어에는 존재하지 않는 개념인 클래스나 객체, 필드, 메쏘드 등 객체지향 프로그램 언어의 특징을 C 언어의 구조체(struct)나 함수 포인터(function pointer) 등을 이용해 표현해줘야 하고, 이를 이용하는 것도 자바처럼 간단하지 않다. 자바에서 지원하는 예외 처리(exception handling) 방법도 C 언어에서 일일이 특정 함수를 호출하여 확인한 후 적절히 리턴해줘야 하고, 자바의 메모리를 접근하기 위해서도 가비지 콜렉터(garbage collector)와의 연관 관계를 생각해야만 한다. 즉 자바가 가상 머신 속에 감추어둔 내부 구조가 C 언어로 건너오면서 노출되는 것이다.
좀 더 일반적으로 말하면 두 언어를 연결하기 위해서는 두 언어 중 하나의 언어가 다른 언어의 기능을 흉내내줘야 한다. 프로그래밍 언어가 처음 만들어질 때부터 다른 언어와의 연관 관계를 생각하지 않았다면 이런 기능을 바라는 것은 힘들 수도 있다. 이상적인 관점을 버리고 특정 언어가 최소한 C 언어와 얼마나 잘 통합될 수 있느냐만 보는 것도 현실적인 접근 방법이다. 왜냐하면 대부분의 라이브러리가 C 언어로 작성되어 있기 때문이다. 파이썬이 태생부터 다른 언어와의 접착성(glue)을 강조하며 나타났던 것도 이런 생각이 깔려 있으리라 짐작 된다.
이 문제를 근본적으로 접근하여 해결책을 제시한 것은 마이크로소프트(MS)였다. MS가 닷넷 플랫폼에서 강조한 내용 중에 하나가 교차 언어(cross-language) 플랫폼인데, 이 개념의 핵심은 서로 다른 언어로 작성한 모듈이나 클래스를 서로 호출하여 사용할 수 있음을 의미한다. 대표적인 예로 VB.NET으로 작성한 클래스를 아무런 수정 없이 C#에서 사용할 수 있다는 점을 들 수 있다. 물론 서로 기본형(primitive-types)과 기능이 다른 프로그래밍 언어가 아무런 제약 조건 없이 서로를 호출해 사용할 수는 없기 때문에 CLS(Common Language Specification)를 만들어 이에 부합해야만 교차 언어를 사용할 수 있다. 그러나 닷넷이 언어간 통합 문제를 어느 정도 해결한 것은 분명해 보인다.
비슷한 움직임으로 썬(Sun)에서도 자바 외에도 자바 바이트코드로 변환되어 실행되는 프로그래밍 언어의 표준화 작업을 시작했다. 일례로 그루비(Groovy)는 바이트코드로 변환되어 실행되는 스크립트 언어인데, 자바와 그루비 모두 바이트코드 형태로 변환되기 때문에 그루비는 자바로 짠 라이브러리와 API를 마음대로 접근하여 사용할 수 있는 특성이 있다. 언어 간의 통합 문제가 점차 더 큰 요구 사항이 된다면, 이러한 시도는 점차 확대되리라 생각한다.
얼마나 객체지향적인가
지난달에 기고했던 ‘프로그래밍 언어론을 배우자’에서 프로그래밍 언어의 4가지 패러다임에 대해 설명한 바가 있다. 프로그래밍 언어는 크게 지시형(procedural) 언어, 객체지향 언어, 함수형 언어, 선언형 언어로 나뉘고 각 패러다임에 따라 언어의 특징이 크게 다르다고 이야기했다. 그러나 프로젝트를 위한 프로그래밍 언어를 선정하는 시점에 오면 판단 기준은 한 가지로 집중된다. 얼마나 객체지향적인가?
함수형 언어나 선언형 언어는 프로그래밍 방법론에 있어서 여러 가지 가치있는 시사점을 전달해주고, 기존의 지시형 언어와 객체지향 언어에 많은 영향을 미친 것이 사실이지만, 그 자체로 함수형, 선언형 언어는 비교적 도메인에 제한적이고, 실제로 프로젝트에 사용되는 일이 드문 편이다. 결국 C와 C++로 대변되는 지시형 언어와 객체지향 언어의 사이에서 어느 지점을 택할 것인가가 가장 현실적인 문제로 남게 된다.
객체지향 언어의 장점은 잘 알려져 있다. 데이터의 캡슐화(encapsulation), 코드 재사용(code reuse), 상속(inheritance), 다형성(polymorphism) 등 기존의 지시형 언어가 가지지 못한 여러 장점을 선사한다. 그러나 좀 더 객체지향적일수록 프로젝트에 적합하다는 일반론은 옳지 않다. 순수 객체지향(OO)을 표방하는 언어들이 비교적 느린 수행 속도로 악명 높고, 실제로 객체지향적일 필요가 없는 코드까지 객체지향적으로 사고하도록 강요하는 경우도 있다. 간단히 몇 가지 일련의 명령을 수행하고 싶은데도, 이를 추상화하여 클래스로 만들고 객체를 생성하고 메쏘드를 호출해야 하는 것은 분명 장점만은 아니다. 또 순수 객체지향 언어일수록 비교적 수행 속도가 느리다는 단점도 안고 있다.
반대로 객체지향 언어의 특징 중 일부만 빌려와도 크게 생산성이 향상될 수 있는 경우도 있다. 임베디드 시스템의 경우 칩셋(chipset) 회사들이 보통 컴파일러까지 제공한다. C++나 다른 고급 언어 컴파일러도 제공하면 좋겠지만, 칩셋 회사들의 특성상 보통 C 언어 컴파일러를 제공한다. 따라서 이런 임베디드 시스템에서 프로그래밍을 한다면 할 수 없이 C 언어를 사용해야 하는 경우가 많다. 팜과 같은 초창기 임베디드 시스템의 API를 보면 프리픽스(prefix)만 달리한 수많은 함수의 나열에 기겁한 경험이 있을지도 모른다. 비슷한 기능을 하는 함수들을 묶어서 네임 스페이스만 달리 줄 수 있었어도, 이런 환경에서 프로그래밍하는 개발자의 생산성을 훨씬 높아졌을지도 모른다.
그 예로 비교적 성공한 OS로 평가받고 있는 스마트폰용 운영체제 시스템인 심비안(Symbian)이 있다. 심비안 OS의 API가 C++로 작성되어 있어서 기존의 C 언어 API에 비해 비교적 정리가 잘 되어 있고 개발도 용이한 편이다. 그러나 이 성공이 C++ 언어 전체의 기능 때문만은 아니다. C++의 특성 중 일부인 클래스를 이용한 네임 스페이스 분리, 예외 처리 등만 C에 추가되었어도 비슷한 효과를 얻었을 것이다.
객체지향과 관련하여 또 하나 언급하고 싶은 것은, 객체지향 언어의 기능 중 일부는 아직도 완전한 동의 없이 서로 다른 방법으로 구현되고 있다는 점이다. 대표적인 예가 다중 상속(multiple inheritance)이다. 다중 상속은 말도 많고 탈도 많은 기능 중 하나이다. 잘 사용하면 강력한 기능이 되기도 하지만, 코드를 복잡하고 이해하기 힘들게 만드는 주범 중에 하나이기도 하다. 자바는 C++의 다중 상속이 마음에 들지 않아 이를 인터페이스로 대체했고, 또 루비를 비롯한 일부 언어는 믹스인(mix-ins)이라는 방법을 이용하여 코드를 조합하여 사용하기도 한다. 또 일부 언어는 일반적인 상속 관계와 코드 재사용을 구분하여 코드 상속(code inheritance)을 따로 지원하기도 한다. 언어를 선택할 때 이런 논쟁적인 부분을 해당 언어에서는 어떤 접근 방법으로 해결하고 있는지를 잘 살펴보는 것도 중요하다.
부수적인 조건이 아닌 안정성
보통 프로그래밍 언어의 안정성(reliability)은 부수적인 요건으로 여겨졌다. 하지만 프로그래밍 언어의 안정성은 개발 속도와 버그 발생률, 디버깅의 편의성, 테스트 등에 모두 영향을 미치는 매우 중요한 요건이다. 안정성이나 테스트의 편의를 결정하는 프로그래밍 언어의 특징은 타입 시스템(type system), RTTI(Run Time Type Inspection), 가비지 컬렉션 등을 들 수 있다. C 언어는 비교적 약한 타입 시스템을 가진 언어이다. C 언어에서 데이터 형은 어떤 다른 형으로도 캐스팅될 수 있고, 이 점은 C 언어의 강력한 장점이자 가장 큰 문제점이기도 하다. C 언어 타입 시스템의 약점은 개발 후반에 재앙으로 나타날 수 있는데, 이는 해결하기 힘든 버그 중에 하나이다.
임베디드 시스템 개발에 C 언어를 사용하는 예를 들어보자. 이 경우 C는 최선의 대안임이 분명하지만, C 언어의 특징 때문에 여러 가지 문제가 일어난다. C 언어에서 가장 흔히 하는 실수는 배열의 인덱스가 잘못되어 잘못된 메모리에 값을 쓰는 경우, 포인터 연산이 잘못되어 잘못된 메모리의 값을 읽거나 쓰는 경우다. 임베디드 시스템이 사용하는 OS는 대부분 메모리 공간이 하나로, 커널과 유저 모드의 구분이 없다. 즉 잘못된 메모리에 접근하더라도 세그먼테이션 폴트(segmentation fault)가 나지 않는다. 즉 커널 영역의 메모리 주소에 잘못된 포인터 연산으로 특정 값을 쓰더라도 그 시점에는 아무런 문제없이 넘어간다. 문제는 그렇게 바뀐 메모리 주소를 나중에 읽으려고 할 때 잘못된 값을 읽어 와서 프로그램이 이상 동작할 경우이다. 이 경우 문제 시점과 원인의 발생 시점이 다르기 때문에 그 원인을 파악하는 게 사실상 거의 불가능해 진다.
만약 C 언어가 아니라 자바처럼 메모리 포인터가 없고, 배열의 경우 항상 배열의 경계값 검사를 해주는 프로그래밍 언어를 사용했다면 저런 문제는 절대 발생할 수 없었을 것이다. 즉 프로그래밍 언어의 타입 시스템에 따라 어떤 종류의 프로그램 버그는 원천 봉쇄할 수도 있다는 이야기다. 이러한 점은 프로그래밍 언어를 선택할 때 반드시 고려해야 한다.
리플렉션(reflection)이라 불리는 언어의 기능은 테스트 프레임워크를 만드는데 많은 도움을 준다. 리플렉션은 런타임에 프로그래밍 언어의 타입을 동적으로 조사하고 이를 호출하여 사용할 수 있는 기능을 말한다. 즉 프로그램이 동작 중에 어떤 데이터 타입이 있는지 확인한 후에, 해당 메쏘드를 무작위로 불러보는 일이 가능해진다. 이 기능은 보통 자바로 작성된 API의 경우, API가 표준에 맞는 클래스 이름과 메쏘드 이름, 그리고 인자들을 가지고 있는지 동적으로 확인한다. 또 해당 시스템의 API를 무작위로 호출하여 시스템의 안정성을 테스트할 때 사용된다.
언어의 런타임이 가비지 컬렉션을 지원하느냐의 여부도 넓게 봐서 언어의 안정성에 포함된다. malloc/ free나 new/delete를 사용하는 프로그래밍 언어는 대부분 자원 관리를 적절히 하기 위해 엄청난 노력이 소요된다. malloc한 메모리는 반드시 어느 시점에서인가 free해줘야 하는데, 이를 여러 조건에 빠짐없이 처리해주는 것만으로 시스템의 복잡도가 상당히 올라감을 알 수 있다. 이를 실수하면 곧바로 메모리 누수(memory leak)로 이어지기 때문에 여간 신경쓰이는 일이 아니다. 가비지 컬렉션을 지원한다면 프로그래머를 이러한 자원 관리 문제에서 어느 정도 해방시키기 때문에 생산성이 향상되고, 자원 관리 문제로 인한 버그를 예방할 수 있는 장점이 있다.
얼마나 많은 라이브러리가 표준화되어 있나
프로그래밍 언어를 선택함에 있어서 가장 중요한 부분은 어쩌면 그 언어의 사용을 도와주는 얼마나 많은 지원이 있느냐 일지도 모른다. 여기서 지원은 각종 프로그래밍 언어 서적, 웹 사이트, 그 프로그래밍 언어를 사용하는 사람들의 숫자, 전문가, 바로 사용할 수 있는 라이브러리 등이다.
프로그래밍 언어도 네트워크와 마찬가지(network effect)로 사용하는 사람이 많을수록 그 언어를 사용하는 모든 사람이 이득을 얻는 특징이 있다. 사람들은 전문적인 지원이 없으면 그 언어의 선택을 꺼리는 경향이 있고 더 많은 사람들이 사용하는 언어를 선택하는 경향이 강하다. 컴퓨터 산업이 어느 정도 성숙되고 나서 상업적으로 성공한 언어가 대부분 MS나 썬과 같이 대기업의 전폭적인 지원이 있었다는 사실도 이를 보여준다. 수많은 책을 펴내고 그 언어의 사용을 장려하기 위해 여러 프로모션 정책을 펴는 것이 프로그래밍 언어의 성공에 있어서 생각보다 중요한 요소인 것이다.
이런 지원 중에서 가장 중요한 요소는 사용 가능한 라이브러리가 얼마나 많이 있느냐다. 자바의 성공은 자바 프로그래밍 언어의 특징에도 있었지만, 또 하나의 요소는 바로 사용 가능한(out-of-the-box) 표준 라이브러리가 언어의 런타임과 함께 배포되었다는 점이다. 프로그래밍 언어의 이러한 특징으로 인해 개발 작업은 새로운 것을 만들어내는 일에서 이미 작성된 라이브러리를 최대한 잘 활용하며 어떻게 조합하느냐로 바뀌었을 정도다. 이러한 변화를 잘 반영하는 말이 있다. 흔히 자바 프로그래밍을 처음 시작하는 사람에게 해주는 충고로 “어디에 있는지를 알아라!(Know Where)”라는 이야기를 많이 한다. 과거 프로그래밍은 언어의 기본적인 기능만으로 모든 것을 만들어 써야 하던 ‘Know What, Know How’의 시대였다면 현재의 프로그래밍은 자신이 사용하고자 하는 기능이 어떤 클래스에 어떻게 구현되어 있는지를 알고 이를 재빨리 찾아서 사용하는 것으로 바뀌었다. 따라서 얼마나 다양하고 많은 라이브러리를 제공하느냐는 프로그래밍 언어의 성패를 정하게 되었다.
여기서 주목해야 할 것은 단순히 얼마나 많은 라이브러리가 있느냐가 아니라 얼마나 많은 라이브러리가 표준화(standard library)되어 있느냐다. 사실 C 언어의 경우 정말 엄청난 양의 라이브러리가 존재하고, 상당수가 오픈소스로 존재하지만 서로 다른 컨벤션(convention)과 스타일을 사용하는 라이브러리를 자신의 구미에 맞게 요리하여 사용하기란 쉬운 일이 아니다. 남의 코드를 가져다 쓴다는 게 얼마나 어려운 일인지는 조그마한 라이브러리를 가져오려고 시도해 본 적이 있는 사람은 모두 공감할 것이다.
결국 프로그래밍 언어 자체가 이미 표준화된 라이브러리를 매우 풍부하게 제공하여 대부분의 기능한 제3자를 통하지 않고서도 직접 프로그래밍할 수 있는 환경을 만들어 주는 것이 중요하다. MS와 썬은 이미 이런 방향으로 자신들의 언어를 개발하고 있고 많은 성공을 거두었다. 우리가 프로젝트에서 어떤 언어를 선택한다면 그 언어의 특징은 물론 그 언어의 표준 라이브러리가 얼마나 잘 정리되었는지도 반드시 고려해야 할 것이다.
절대 기준은 없다
앞에서 프로그래밍 언어의 선택 기준을 몇 가지 언급했지만, 사실 프로그래밍 언어를 선택하는 절대적인 기준이란 없다. 바꿔 말하면 거의 모든 요소를 고려해도 부족하다는 말이 될 수도 있다. 언어의 특징을 규정짓는 요소들, 예컨대 가독성, 이식성(portability) 등도 모두 언어를 선택할 때 고려해야 할 요소들이고 각각의 생산성에 어떤 식으로든 영향을 미친다. 중요한 점은 프로젝트 시작에 앞서 몇 가지 선택된 기준을 바탕으로 몇 개의 프로그래밍 언어를 평가한 후에 프로젝트 언어를 결정하는 신중함을 가지라는 점이다. 프로젝트 초반의 시간 절약은 나중에 더욱 큰 화살이 되어 돌아올 수 있음을 항상 명심해야 한다. [maso]
Posted by 홍반장