Logo

프로그래밍 언어의 분류 (레벨 / 타이핑 / 매커니즘)

C, C++, C#, Objective-C, Swift, Java, Kotlin, Scala, Go, Rust, Python, PHP, Ruby, JavaScript, HTML, CSS, SQL…

프로그래밍 언어의 과잉의 시대에서 여러분은 어떤 프로그래밍 언어로 소프트웨어 개발을 하고 계신가요? 이번 포스팅에서는 이렇게 수많은 프로그래밍 언어를 분류하는 몇 가지 기준에 대해서 얘기해보겠습니다.

레벨(level)에 따른 분류

프로그래밍 언어에 대해서 얘기할 때 흔히 얼마나 레벨 또는 수준(level)이 높으냐 낮으냐에 대해서 많이 얘기하는데요. 보통 저수준(low-level)의 언어가 있고 고수준(high-level)의 언어가 있다고들 많이 얘기를 하죠.

한국어에서 “수준”이라는 단어의 특유의 어감 때문에 프로그래밍에서 “수준”이라는 개념을 자칫 오해를 하기 쉬운데요. 여기서 말하는 수준은 프로그래밍 언어의 추상화(abstraction) 정도를 뜻합니다. 따라서 절대 수준이 높은 언어가 더 좋은 언어이고 수준이 낮은 언어가 더 나쁜 언어가 아니오니 주의바라겠습니다.

좀 더 쉽게 설명드리면 일반적으로 컴퓨터에 가까운 언어를 저수준(low-level) 언어라고 하고, 인간에 가까운 언어를 고수준(high-level) 언어라고 합니다. 하지만 모든 프로그래밍 언어을 이렇게 2가지로 양분하기보다는 스팩트럼으로 생각하는 것이 더 도움이 될 수 있습니다. 즉, 고수준 범주 안에서도 상대적으로 수준이 더 낮은 언어가 있을 수 있고, 상대적으로 수준이 더 높은 언어가 있을 수 있는 것이지요.

컴퓨터의 CPU(중앙 처리 장치)는 이진수, 즉 0과 1로 이루어진 바이너리의 형태의 데이터를 엄청난 속도로 처리하는 기계라고 볼 수 있습니다. 이렇게 컴퓨터가 있는 그대로 이해할 수 있는 코드를 소위 기계어(machine language)라고 하는데요. 0과 1로만 코딩을 해야한다고 상상을 해보시면 기계어를 쓰는 것이 얼마나 힘들지는 굳이 길게 말씀 안 드려도 되겠죠? 😣 게다가 기계어는 보통 CPU의 설계에 따라서 명령 체계가 완전히 다르기 때문에 이식성(portability)이 매우 떨어지는 특징을 가지고 있습니다. 쉽게 말해 Intel 프로세서에서 작동하는 기계어는 ARM 프로세서에서는 작동하지 않으며 반대의 경우도 마찬가지입니다.

어셈블리어(assembly language)는 여전히 기계어처럼 0과 1을 많이 써야하기 하지만 mov, add, sub, save와 같은 알파벳으로 이뤄진 명령어를 쓸 수 있는 프로그래밍 언어입니다. 그래서 기계어보다는 그나마 사람이 직접 읽고 쓰는 것이 수월하죠. 대신에 이셈블리어로 작성된 소스 코드는 어셈블러(assembler)라는 도구를 통해 0과 1로 이뤄진 기계어로 반드시 치환을 해줘야 컴퓨터가 처리할 수 있겠죠?

기계어나 어셈블리어와 같은 저수준 언어는 반도체 칩이나 임베디드(embedded) 개발, 시스템 해킹 분야와 같이 극히 제한된 분야에서 사용되고 있습니다. 사실 대학에서 컴퓨터를 전공하시지 않으셨다면 접해보기 쉽지 않은 프로그래밍 언어입니다. 사실상 응용 소프트웨어를 개발하는데는 저수준 언어가 거의 사용되지 않기 때문입니다.

C++, C#, Java, Python, JavaScript 등 인기 프로그래밍 언어 상위권에 랭크된 많은 언어들은 대부분 고수준 언어에 속하는데요. 고수준 언어는 인간의 언어(주로 영어)와 유사한 문법을 제공하기 때문에 사람이 쓰기 편하다는 장점이 있습니다. 하지만 그만큼 컴퓨터가 실행하기 위해서는 부수적인 과정이 거쳐야하는 단점도 있는데요. 보통 고수준 언어로 작성한 소스 코드는 컴파일러(compiler)나 인터프리터(interpreter)와 같은 도구를 통해서는 컴퓨터가 실행할 수 있는 형태로 변환이 필요합니다.

이러한 고수준 언어와 저수준 언어의 상반된 특징을 우리가 평소에 말할 때 쓰는 언어에 빗대어 생각해보면 어떨까요…?

먼저 우리 자신을 영어를 조금 할 줄 아는 한국인라고 가정하고, 컴퓨터를 한국어를 전혀 할 줄 모르는 미국인라고 가정을 해볼께요. 우리가 이 미국인에게 영어로 얘기를 한다면 어떨까요? 비록 내 영어가 서툴고 실수도 할 수 있겠지만 이 미국인 무슨 말인지 바로 바로 알아들을 수 있겠죠? 이 것을 개발자가 저수준의 언어로 컴퓨터와 대화하는 상황에 비유할 수 있는데요. 저수준의 언어는 개발자가 컴퓨터의 입장에서 프로그래밍을 해야되서 좀 어려운 편이에요. 하지만 언어의 수준이 낮아질수록 컴퓨터에게 내가 작성한 코드를 이해시키기 위한 비용이 적게 들어가는 이점이 있습니다.

반대로 우리가 이 미국인에게 한국어로 얘기를 하려면 어떻게 해야할까요? 중간에서 한국어를 영어로 번역해줄 사람이이나 기계가 필요하겠죠? 말하는 우리 자신은 편하겠지만 중간에 한 단계를 거쳐가야하니 당연히 의사소통이 오래 걸리겠죠? 이 것을 프로그래머가 고수준 언어를 사용하는 상황에 비유할 수 있는데요. 고수준의 언어는 개발자가 사람의 입장에서 프로그래밍을 할 수 있기 때문에 자연스럽고 편합니다. 하지만 언어의 수준이 높아질수록 내가 작성한 코드를 컴퓨터가 이해할 수 있는 형태로 변환하는 비용이 많이 발생하게 됩니다.

이렇게 저수준 언어와 고수준 언어의 장단점이 분명함에도 불구하고 왜 현대의 소프트웨어 개발이 대부분 고수준 언어로 이뤄지고 있을까요? 제가 생각하기에는 컴퓨터 하드웨어의 성능이 비약적으로 발전하는 동시에 소프트웨어 산업이 급격하게 성장하면서 시간이 지날수록 컴퓨터 보다는 프로그래머가 더 귀해졌기 때문인 것 같아요. 옛날에 컴퓨터 하드웨어가 귀해서 여러 개발자가 한 대의 메인프레임 컴퓨터에 줄을 서서 코딩을 하던 시절에는 제한된 시간 내에 조금이라도 컴퓨터의 리소스를 효율적으로 써서 많은 양의 작업을 처리를 하는 것이 매우 중요했습니다. 저수준 언어를 사용하면 컴퓨터가 실행할 수 있는 형태로 변환해야하는 과정이 적고 개발자가 메모리와 같은 하드웨어 리소스를 직접 제어할 수 있기 때문에 유리했던 것이지요.

하지만 요즘에는 개발자가 항상 부족하기 때문에 배우기 쉬운 프로그래밍 언어로 새로운 개발자를 대량 양성하여 빠르게 시장에 공급하는 것이 중요합니다. 그리고 소프트웨어의 규모가 커져서 어러 명으로 이뤄진 팀 단위로 개발이 이루어지기 때문에 개발자 간 협업이 늘어나고 내 코드 뿐만 아니라 다른 사람이 쓴 코드를 읽는 것도 중요해졌습니다. 이런 환경에서는 인간이 읽고 쓰기 용이한 고수준 언어의 이점이 부곽될 수 밖에 없는 것이지요. 뿐만 아니라 컴퓨터의 하드웨어가 워낙 강력해지고 저렴해지면서 고수준 언어로 작성된 소스 코드를 변환하는 과정이 더 이상 큰 부담이 되지 않는 것 같습니다.

한 마디로 정리하면 컴퓨터가 이해하기 좋은 코드보다는 사람이 이해하기 좋은 코드가 중요한 시대가 된 것이지요.

이제 분야를 불문하고 프로그래밍이 필요한 시대가 되어 가고 있기 때문에 앞으로 프로그래밍 언어의 수준은 누구나 프로그래밍을 할 수 있을 정도로 높아질 수 있을 것이라 생각합니다. 예를 들어, 학생들을 상대로 코딩을 가리키기 위해서 고안된 스크래치(Scratch)와 같은 언어를 혹자는 프로그래밍 언어가 아니라고 하기도 하지만 저는 수준이 매우 높은 프로그래밍 언어로 충분히 볼 수 있다고 생각합니다. 컴퓨터 스크린을 보지 못하시거나 타이핑이 불가능한 분들을 위한 음성 기반 또는 뇌파 기반 프로그래밍 언어를 만드려는 시도도 계속되고 있습니다.

타이핑(typing)에 따른 분류

프로그래밍에서 자료형 또는 타입(type)은 데이터의 대략적인 형태를 결정하며 변수에 할당이 가능한 데이터를 제한하는데 사용됩니다. 또한 함수의 인자값이나 반환값의 형태를 제한하고 객체가 어떤 형태의 데이터를 읽을 수 있고 어떤 행위를 수행할 수 있는지도 결정합니다.

프로그래밍 언어 중에서는 자료형이 컴파일 시점에 결정이 되는 언어가 있고, 실행 시점에 결정이 되는 언어가 있는데요. 보통 전자를 정적 타이핑(static typing) 언어, 후자를 동적 타이핑(dynamic typing) 언어라고 합니다. 정적 타이핑 언어를 사용하면 개발자가 자료형을 스스로 명시해야줘야 하지만 동적 타이핑 언어를 사용하면 언어가 알아서 자료형을 유추해줍니다.

대표적인 정적 타이핑 언어로는 C 계열 언어(C, C++, Objective-C, C#)와 Java 계열 언어(Java, Kotlin, Scala)를 들 수 있는데요. 정적 타이핑 언어를 사용하면 동적 타이핑 언어 대비 좀 더 버그가 적은 안정적인 소프트웨어를 개발하는데 유리한 것으로 알려져있습니다. 왜냐하면 정적 타이핑 언어로 작성된 코드는 컴파일(compile)이나 빌드(build) 과정에서 변수의 자료형과 실제 저장된 데이터가 부합하는지를 확인하는 작업, 즉 타입 체크(type check)를 할 수 있기 때문입니다. 하지만 개발자가 모든 변수나 함수, 클래스에 반드시 자료형을 명시해주려면 상대적으로 많은 양의 코드를 작성해야 되기 때문에 개발 생선성에서는 불리한 면이 있습니다.

90년대에 웹(Web)과 함께 등장한 Python과 Ruby, JavaScript, PHP와 같은 프로그래밍 언어들은 동적 타이핑 언어로 분류되는데요. 동적 타이핑 언어를 사용하면 상대적으로 적은 양의 코드로 빠르게 애플리케이션을 개발할 수 있다는 이점이 있습니다. 자료형을 명시해줄 필요없으니 코드를 빠르게 작성할 수 있고, 별도의 타입 체크를 하지 않으니 작성한 코드를 빠르게 빌드/배포할 수 있는 것이지요. 하지만 타입 체크를 하지 않기 때문에 프로그램 실행 시점에서 오류가 발생할 위험이 상대적으로 높습니다. 이러한 특징 때문에 일회성 스크립트나 시제품을 프로토타이핑(prototyping)하는데 사용하기 적합합니다.

항상 그런 것은 아니지만 동적 타이핑 언어들은 비교적 타이핑 정도가 약한 경향이 있고, 정적 타이핑 언어들은 타이핑 정도가 강한 경향이 있습니다. 예를 들어, 동적 타이핑 언어에서는 숫자형 타입이 number 하나인 경우가 많은데, 정적 타이핑 언에서는 같은 숫자형에서도 정수형과 실수형이 구분하고 최대한 담을 수 있는 메모리의 크기에 따라 byte, short, int, long, float, double 등으로 좀 더 세분화가 됩니다. 따라서 정적 타이핑 언어의 장점을 극대화하려면 개발자가 해당 언어의 타입 시스템을 어느 정도 숙지하는 것이 좋습니다. 이 부분은 장점이 될 수도 있겠지만 단점이 될 수도 있겠지요.

최근에 나타나고 있는 재미있는 트랜드는 동적 타이핑 언어도 정적 타이핑을 선택적으로 지원하기 시작했다는 것입니다. 예를 들어, JavaScript에서 정적 타이핑을 하고 싶다면 TypeScript라는 확장된 언어를 사용하면 되고, Python에서는 Type Hints라는 기능이 언어에 추가되어 정적 타이핑이 가능해졌습니다. 이러한 추세에 따라 기존에 동적 타이핑 언어를 기피하던 대규모의 소프트웨어 프로젝트에서도 동적 타이핑 언어를 채택하는 경우가 점점 많아지고 있는 것 같습니다.

패러다임(paradigm)에 따른 분류

역사적으로 소프트웨어 개발 패러다임은 계속해서 변해왔고 많은 프로그래밍 언어들이 이러한 패러다임에 영감을 받아 탄생했습니다. 그리고 이렇게 탄생한 프로그래밍 언어는 다시 개발자들이 소프트웨어 개발에 접근하는 방식에 큰 영향을 주고 있습니다.

COBOL이나 BASIC과 같이 초창기에 등장한 프로그래밍 언어들은 컴퓨터가 수행할 명령어를 순서대로 나열하는 수준인 절차적(procedural) 패러다임을 따르고 있습니다. 절차적 프로그래밍 언어는 간단한 소프트웨어를 개발하는데는 문제가 없었지만 복잡한 소프트웨어를 개발하는데는 한계가 있었습니다. 따라서 복잡한 실제 세상을 모델링(modeling)하여 소프트웨어 설계에 반영할 수 있는 객체지향(object-oriented) 패러다임이 대두하였고, Java나 C#과 같은 객체지향 언어가 소프트웨어 프로젝트의 규모가 커짐에 따라 큰 인기를 누렸습니다. 최근에는 빅데이터가 뜨면서 상태 관리와 가변 데이터를 피하는 Scala나 Erlang과 같이 함수형(functional) 프로그래밍이 좀 더 각광을 받고 있는 것 같습니다.

현대에는 프로그래밍 언어 간에 경쟁이 치열해지면서 하나의 프로그래밍 언어에서 여러 패러다임이 공존하는 경우를 어렵지 않게 볼 수 있습니다. 예를 들어, 전통적으로 객체지향 언어로 알려졌던 Java는 람다식과 같은 함수형 패러다임의 요소를 적극 차용하였으며, JavaScript는 매년 객체지향 문법에 대한 지원을 강화하면서 이제는 원한다면 충분히 객체지향 패러다임으로 소프트웨어 개발이 가능한 수준에 이르렀습니다. 이렇게 프로그래밍 언어들이 서로 영향을 받으며 각자 진화하면서 상황과 맥락에 따라 패러다임 간 장점만을 취하려는 시도가 계속되고 있습니다.

마치면서

이 밖에도 프로그래밍 언어를 나눌 수 있는 여러가지 방법이 있겠지만 개인적으로 중요하게 느껴지는 3가지 기준으로 나름대로 분류를 해보았습니다. 요즘에 나오는 프로그래밍 언어들을 보면 이러한 전통적인 기준으로 분류를 하는 게 점점 큰 의미가 없어지는 것 같기도 합니다. 아무래도 프로그래밍 언어의 개성이 너무 뚜렷하면 기존에 있던 언어 대비 새로운 사용자 확보 측면에서 경쟁력을 갖기 어렵기 때문이 아닐까 싶습니다.

첫 프로그래밍 언어는 처음으로 프로그래밍을 배우는 환경(학교, 강의, 책)에 의해 결정이 되는 경우가 많은 것 같죠? 하지만 두 번째, 세 번째 배우는 프로그래밍 언어는 여러분이 스스로 선택할 수 있을 거에요. 본 포스팅에서 다룬 프로그래밍 언어의 분류 방법을 참고하셔서 가급적이며 기존에 하시던 프로그래밍 언어와 성격이 많이 다른 언어를 공부해보시라고 추천드리고 싶습니다.

예를 들어, C#을 하시는 분이 같은 객체지향 패러다임을 따르는 Java를 해보면 두 언어가 그렇게 크게 다르지는 않다는 것을 느끼게 되실 거에요. 이 말은 익히는기는 쉬울지 몰라도 그 만큼 새롭게 배울 점이 없다는 뜻이기도 합니다. 하지만 Python이나 Ruby, JavaScript와 같은 고수준 언어를 주로 쓰시던 분이, C나 Go, Rust와 같이 저수준 언어를 배워보신다면 완전히 다른 시각으로 프로그래밍을 바라보시게 되실 수도 있을 것입니다.