Skip to content

프로그래밍 언어는 플랫폼의 저작도구다.

저는 그렇게 생각해요

Makonea
·2026년 7월 17일·116분

들어가며...

새로운 프로젝트를 구상하며 어떤 언어를 사용할지 결정할 때, 사람들은 종종 두 가지 환상 중 하나에 빠진다. 함수형 패러다임이나 타입 시스템의 순수성 같은 '학문적 계보'를 신성시하거나, 깃허브 별 개수와 프로그램 커뮤니티 화제성에 휩쓸려 '커뮤니티의 유행'을 맹종하는 것이다. 우아한 수학적 증명이나 트렌디한 문법이 프로덕트의 생존을 보장해 줄 것이라는 순진한 착각이지만, 사실 일면 이해가 되는 지점도 있다. 사람들은 자신의 커리어를 걸고 배운 언어와 그 세계관이 오래가길 원하니까. 나도 당장 C#이랑 TypeScript에 대한 점유율이 떨어지면 C#과 TypeScript가 얼마나 우수한지 프로그래밍 사이트 전체에 도배할 것이다.

어쨌건 모든 프로그램의 실행은 궁극적으로 기계가 수행하는 명령과 상태 전이로 귀결된다. 인터프리터가 소스나 바이트코드를 한 단계씩 읽는 경우에는 원본 프로그램 전체가 기계어로 번역되지 않을 수도 있다. 그래도 계산 가능성만 따지면 고수준 언어가 반드시 필요한 것은 아니다. 기계어나 어셈블리만으로도 같은 계산을 표현할 수 있다.

그런데 실제 역사는 우리가 아는 것처럼 흘러갔다. 사람들은 어셈블리와 C만으로 모든 프로그램을 작성하지 않았다. 서로 다른 문제와 플랫폼을 위한 수많은 고수준 언어와 실행 환경이 등장했다.

새로운 하드웨어가 나오고 운영체제가 생기며, 브라우저와 스마트폰이 보급될 때마다 그 표면을 다루는 언어와 도구가 따라붙었다. 기계가 계산할 수 있느냐보다, 사람이 그 기계 위에 얼마나 많은 프로그램을 만들 수 있느냐가 더 비싼 문제가 되었기 때문이다.

IBM이 Microsoft에 PC 플랫폼의 주도권을 내준 과정도 이 관점에서 읽을 수 있다.
IBM은 PC라는 하드웨어 표준을 만들었지만, 그 위에 쌓인 응용 프로그램과 개발자 생태계는 IBM의 기계보다 MS-DOS와 Windows API에 결속됐다. 호환 PC가 늘면서 하드웨어는 상품화됐고, 개발자가 프로그램을 작성하는 인터페이스를 장악한 Microsoft가 실질적인 플랫폼 주인이 되었다.1

빈 플랫폼은 아직 제품이 아니다.
그 위에 게임과 업무 프로그램, 콘텐츠가 쌓여야 비로소 사용자가 머무는 세계가 된다.

언어는 그 세계를 작성하는 도구다.

그리고 이 도구를 쥐고 빈 공간에 논리와 규칙을 부여하는 순간, 프로그래머는 단순한 기계 조작자를 넘어선다.

“The computer programmer is a creator of universes for which he alone is the lawgiver.”

“컴퓨터 프로그래머는 자신만이 입법자인 우주의 창조자다.”

ELIZA를 만든 조지프 와이젠바움(Joseph Weizenbaum)이 《Computer Power and Human Reason》에서 남긴 문장이다.

그러나 프로그래머가 언어로 세계를 만들 수 있다는 사실만으로, 그 언어의 성공까지 설명되지는 않는다. 표현력이 뛰어난 언어가 작은 연구 공동체에 머물기도 하고, 많은 비판을 받은 언어가 거대한 실행 환경을 만나 산업 표준이 되기도 한다.

프로그래밍 언어를 설명할 때에는 흔히 타입 체계, 의미론, 패러다임, 구현 방식 같은 학문적 분류에서 출발한다.
이런 분류는 중요하지만, 나는 그것만으로는 언어의 실제 역사를 설명할 수 없다고 생각한다.
왜 프로그래밍 언어를 배우는가에 대한 답은 나는 취업시장의 관점에서 보는게 더 맞다고 생각하기 때문이다. 그리고 취업시장의 관점 그리고 시장의 관점에서 언어의 역사를 한번 생각해보자 하고 이 글을 썼다.

프로그래밍의 학문적 계보를 나누는 글들이야 많기때문에 내가 써봤자 글의 경쟁력이 없기에 나 새로운 시각을 제시해본다.

내 기준으로 언어의 역사를 읽을 때에는 적어도 세 질문을 구분해야 한다고 생각한다.

Loading animated diagram...

왜 만들어졌는가?
왜 개발자가 채택했는가?
왜 오랫동안 지배적이 되었는가?
이 질문이 가장 근본적이라고 생각한다.
연구용으로 만든 언어가 거대한 산업 플랫폼을 만나 성공하기도 하고, 특정 플랫폼을 위해 만든 언어가 그 플랫폼 밖으로 빠져나가기도 한다.
언어의 탄생 동기 하나로 이후의 역사를 전부 설명하려 들면 곧잘 무리가 생긴다.

플랫폼과 보완재

운영체제, 브라우저, 게임기, 스마트폰의 가치는 그 위에 올라오는 프로그램과 함께 커진다. 사용자가 매일 마주하는 것은 시스템의 커널이나 설계적 우아함이 아니라, 업무 프로그램과 게임 같은 최종 결과물이기 때문이다. 할 게임이 없는 게임기는 그저 비싼 거실 장식품에 불과하다.

경제학에서는 이처럼 함께 소비될 때 가치가 극대화되는 상품을 '보완재(Complementary Goods)'라고 부른다. 플랫폼과 응용 프로그램은 완벽한 보완재 관계다.

인프라가 거대한 빈 무대라면, 응용 프로그램과 콘텐츠는 그 무대를 채우는 배우이자 극본이다. 아무리 최첨단 음향과 조명 설비를 갖춘 극장을 지어놓아도 무대에 올릴 공연이 없다면 관객은 오지 않는다. 즉, 플랫폼의 진정한 상업적 가치와 시장 지배력은 자체적인 연산 능력이 아니라, 그 생태계 위에서 끊임없이 생산되는 보완재(킬러 앱)의 총량에 의해 철저하게 종속적으로 결정된다.

개념적으로 정리하면 다음과 같다.

Loading animated diagram...

플랫폼 사업자에게 개발 도구는 공장 설비와 비슷하다. 컴파일러 판매가 수익의 중심이 아니어도 상관없다. 개발자가 프로그램을 더 싸고 빠르게 만들면 플랫폼의 보완재가 늘어난다. 그러면 플랫폼 자체의 가치도 오른다. Joel Spolsky가 "보완재를 상품화하라"라고 설명한 전략을 언어와 SDK에도 적용할 수 있다.2

따라서 "컴파일러를 팔려고 고수준 언어를 만들었다"는 설명이 과연 옳을까라는 건 내 기준에서는 약간 1차원적인 질문이라고 생각한다.
기업의 수익은 컴퓨터, 운영체제, 런타임, 브라우저, 스마트폰에서 나오는 경우가 많았다. 언어는 사람들이 그 상품을 계속 쓸 이유를 생산하는 것이다.

언어가 낮추는 비용

언어가 줄이는 비용은 타이핑 양에 그치지 않는다.

Loading animated diagram...

“어셈블리로 구현할 수 있다”는 말은 튜링 완전성, 즉 계산 표현력에 관한 답이다. 그러나 사업 일정 안에 만들 수 있는지, 몇 년 뒤 다른 팀이 고칠 수 있는지, 다음 기계로 옮길 수 있는지는 개발 비용과 언어의 실용성에 관한 전혀 다른 문제다.

튜링 완전한 언어들은 원칙적으로 같은 계산을 표현할 수 있지만, 그 계산을 작성하고 검증하고 유지하는 비용까지 같지는 않다는게 문제다.
언어 설계의 차이를 어떻게 볼 것인가?
일반적으로는 무엇을 계산할 수 있느냐보다, 그 비용과 책임을 인간·컴파일러·런타임 사이에 어떻게 배분하느냐에서 나타난다.

실제 현실 세계의 일반적으로 가장 많이 반복되는 질문은 무엇인가? 이것이 돈이 되는가이다. 그렇기에 계산의 가능성에서 비용의 배분으로 이동한다는 게 중요하다.

이 관점에서 보면 언어의 역사는 각 시대의 시스템에서 무엇이 가장 비싼 자원이었는지에 따라 변해온 것을 관측할 수 있다. 하드웨어가 희귀하고 비싸던 시기에는 실행 효율이 가장 큰 제약이었다. 그러나 소프트웨어가 커지고 개발 조직이 늘면서 작성·검증·유지보수에 들어가는 인적 비용도 중요해졌다.

고수준 언어는 인간의 기억과 주의력이 맡던 일부 작업을 컴파일러, 타입 시스템, 가비지 컬렉터와 라이브러리로 이전한다. 그런 의미에서 프로그래밍 언어는 기계에 명령을 전달하는 표기법인 동시에, 인간의 인지 비용과 조직의 개발 비용을 배분하는 인터페이스다.

(이를 프로그래밍의 Sapir-Whorf hypothesis 이라고 생각하면 된다.)

C로 구현했는데 왜 Python을 쓰는가

Alan Perlis는 1982년 Epigrams on Programming의 열아홉 번째 문장에서 이렇게 썼다.3

(Alan Perlis)

“A language that doesn't affect the way you think about programming, is not worth knowing.”

“프로그래밍을 생각하는 방식에 영향을 주지 않는 언어는 알 가치가 없다”

이를 가치관을 바꾸는 언어라고 옮겨도 뜻은 통한다.
다만 여기서 가치관은 정치적인 신념보다는 프로그램을 바라보는 습관에 가깝다. 무엇을 하나의 값으로 취급하는가, 어떤 연산에 이름을 주는가, 상태와 실패를 어디에 놓는가, 그리고 어떤 조합을 짧게 쓸 수 있게 만드는가. 언어는 이러한 것들을 결정한다. 언어란 기본적으로 트레이드 오프의 묶음이자 서브셋의 집합이다.
(여기서 서브셋이란, 기계가 가진 무한한 자유중에서 언어 설계자가 의도적으로 도려내고 남긴 '허용된 제약 공간'을 뜻한다)

즉 내가 어떤 분야에 집중하겠다. 내가 뭘 하겠다는 것이 언어의 선택으로 결정된다.

이 말을 Python에 대입하면 질문이 나올것이다.
CPython은 C로 구현됐고, 성능이 중요한 확장 모듈도 C나 C++로 작성되는 경우가 많다. 결국 C가 실행할 거라면 처음부터 전부 C로 쓰면 되지 않을까?

실행되는 재료만 보면 그럴듯하지만, 그러나 저자가 다루는 세계는 전혀 다르다. C에서 라이브러리를 직접 호출하는 사람은 포인터, 버퍼 길이, 소유권, 오류 코드, 자료형 변환과 링크 방법까지 상대해야 한다. Python 확장 모듈은 그 작업을 import 가능한 함수와 객체로 바꾼다.
공식 C 확장 API도 새 객체형을 만들거나 C 라이브러리와 시스템 호출을 Python에서 사용할 수 있게 하는 경계로 설명된다.4

가령 수치 계산의 안쪽 루프는 C·C++·Fortran과 BLAS가 처리하더라도, 사용자는 배열을 하나의 값처럼 넘기고 함수를 조립하며 결과를 노트북에서 바로 확인한다. 메모리 주소를 순회하는 구현 세계와 행렬·데이터프레임·모델을 만지는 저작 세계가 한 프로그램 안에 겹쳐 있는 셈이다. Python은 C의 대체품이라기보다, 이미 존재하는 네이티브 코드를 다른 크기의 단위로 다루게 해 주었다.

여기서 언어의 세계관이라는 말을 요약하면 이렇다.

언어가 정하는 것

C에 가까운 표면

Python에 가까운 표면

기본 작업 단위

주소, 버퍼, 구조체, 함수 호출

객체, iterable, module, callable

자원과 수명

명시적 할당·해제, 소유 규약

객체 수명과 context manager

실패 전달

반환값, errno, 별도 규약

예외와 traceback

조립 방식

헤더, ABI, 링크, 함수 포인터

import, protocol, 고차 함수

탐색 방식

빌드 후 실행과 디버거

REPL, introspection, notebook

언어가 정하는 것

기본 작업 단위

C에 가까운 표면

주소, 버퍼, 구조체, 함수 호출

Python에 가까운 표면

객체, iterable, module, callable

언어가 정하는 것

자원과 수명

C에 가까운 표면

명시적 할당·해제, 소유 규약

Python에 가까운 표면

객체 수명과 context manager

언어가 정하는 것

실패 전달

C에 가까운 표면

반환값, errno, 별도 규약

Python에 가까운 표면

예외와 traceback

언어가 정하는 것

조립 방식

C에 가까운 표면

헤더, ABI, 링크, 함수 포인터

Python에 가까운 표면

import, protocol, 고차 함수

언어가 정하는 것

탐색 방식

C에 가까운 표면

빌드 후 실행과 디버거

Python에 가까운 표면

REPL, introspection, notebook

보통 프로그래밍 커뮤니티에서는 C가 진짜 커뮤니티로 프로그래밍이라고 더 오래되고 더 기계에 가까운 쪽을 추종하지만, 프로그래머는 인지적 자원을 가지고, 인지적 자원을 어떻게 배분할 것인지 언제나 선택을 해야한다.(이렇게 말하면 실리콘 밸리식 테일러리즘 (Taylorism)이 될 수 있지만, 어쨌건 나는 이 부분에 한해서는 테일러리즘에 동의하는 바이다)

수치 계산에 투자한다면 당연히 기계에 직접 명령을 내리는 로우레벨 제어(Control)에 집중해야 한다.
반면, 데이터의 흐름과 비즈니스 도메인의 규칙을 모델링하는 데 인지력을 쏟아야 한다면, 메모리 관리나 스레드 동기화 같은 기계적인 세부 사항은 가비지 컬렉터와 런타임이라는 블랙박스에 기꺼이 위임(Delegation)해야 한다.

사람은 모든 걸 할 수 없다. 인지적 자원 내에서 얼마나 효율적인가에 대한 선택이 프로그래밍 언어의 선택이다.

같은 시스템에서도 안쪽은 기계에 가까워야 하고 바깥쪽은 사람이 자주 바꾸기 쉬워야 한다.
C가 계산 비용과 메모리 배치를 맡고 Python이 실험 순서와 조립을 맡는 분업은 그래서 오래 살아남았다는 것이다. Python 코드 아래에 C가 있다는 사실은 Python의 불필요함보다, 두 언어가 서로 다른 변경 속도를 담당한다는 점을 보여준다.

물론 경계에도 세금이 붙는다는 것은 누구나 안다. 객체 변환과 메모리 복사, ABI와 패키징, GIL, C에서 난 오류를 Python 예외로 옮기는 일, 네이티브 크래시를 Python traceback만 보고 찾기 어려운 문제가 남는다. 추상화가 구현 비용을 없애 주지는 않지만, 대신 그 비용을 소수의 확장 모듈 작성자에게 모으고, 훨씬 많은 사용자가 문제 영역의 단어로 코드를 쓰게 한다.

이 관점에서 새 언어를 배운다는 일은 문법 기호를 갈아 끼우는 데서 끝나지 않는다. LISP는 코드를 데이터처럼 다루게 했고, Prolog는 절차 대신 관계와 탐색을 앞세웠으며, Rust는 소유와 대여를 타입 검사에 끌어들였다. Python도 반복 가능한 객체, protocol, 동적 조립과 대화형 탐색을 일상적인 작업 단위로 만들었다. 새 언어가 이전 언어와 같은 생각을 세미콜론만 바꿔 적게 한다면 Perlis의 기준에서는 배울 이유가 약하다. 반대로 구현의 밑바닥이 같은 C와 기계어로 돌아가더라도, 저자가 보는 문제와 선택 가능한 문장이 달라졌다면 이미 다른 저작 도구다.

그렇기 때문에 언어의 선택은 내가 무엇을 하고 싶은가로 결정되어야한다. 그리고 언어를 선택하면 그 언어의 세계관에 들어가는 것이다.

플랫폼 경제로 다시 읽는 언어사

여기서는 Fortran을 출발점으로 삼는다.
Fortran 이전에도 Plankalkül, Short Code, A-0, Autocode처럼 중요한 선행 작업이 있었으므로 "최초의 고수준 언어"를 가리는 경주를 하려는 것은 아니다.
프로그래머라는 족속들은 맨날 언어 얘기만 나오면 "사실 진정한 최초는 OOO다"라며 죽은 언어들의 족보를 들먹이는데, 나는 지금 켄터키 더비에 출전할 혈통 좋은 경주마를 고르려는 게 아니다.(혈통 따질거면 우마무스메를 해라. 우마무스메 재밌다)
1950년대에 죽은 언어들의 제삿상을 차리는 일은 위키피디아 편집자들에게 맡겨두겠다. 내가 관심 있는 것은 오직 하나, 이 언어들이 어떻게 개발자들을 묶어두고 플랫폼의 권력이 되었는가라고 하는 것이다.
일단 잡설은 됐고, 1957년 Fortran 보고서부터 시작하면 언어, 컴파일러, 상용 컴퓨터와 사용자 시장이 한 문서 안에서 만나는 장면을 비교적 선명하게 볼 수 있기때문이다.

아 그리고 언어가 처음 구현된 해와 지금 우리가 읽는 대표 문헌의 발행 연도가 언제나 같지는 않다.
Smalltalk, C, C++, Prolog의 HOPL 논문은 탄생 당시의 소개 논문이 아니라 설계자가 훗날 정리한 회고이다. 반면 Fortran 보고서, COBOL 초기 명세, ALGOL 60 보고서와 Dartmouth BASIC 매뉴얼은 당시 문서에 가까운 지점이 있다.
우리가 프로그래밍 역사를 정리할 때 골머리를 앓는 지점이 바로 이것이다.
대학과 위원회 출신 언어들은 태어날 때부터 출생증명서와 혈통서(명세서)를 들고나온다.
반면 C나 C++ 같은 언어들은 지하실에서 일단 세상을 집어삼킨 다음, 10년쯤 지나 할 일이 없어질 때쯤에야 넥타이를 매고 학회에 나타나 과거를 미화하는 회고록을 썼다.

족보가 먼저 쓰인 언어와, 돈을 먼저 벌고 나중에 족보를 산 언어의 차이다.

시기

언어

초기 환경

여기서 읽을 문헌의 성격

1957

Fortran

IBM 704

컴파일러와 언어를 발표한 동시대 보고서

1960

COBOL

정부·기업의 이기종 데이터 처리

CODASYL 초기 명세

1960

LISP

MIT AI Group, IBM 704

McCarthy의 원 논문

1960

ALGOL 60

국제 알고리즘 표기

동시대 언어 보고서

1964

BASIC

Dartmouth Time-Sharing System

1964년 초기 공식 매뉴얼

1962-1967

Simula I·Simula 67

이산 사건 시뮬레이션

1966년 Simula I 소개 논문과 이후 Simula 67 계보

1972 이후

Smalltalk

Xerox PARC의 개인용 컴퓨팅

1993년 설계자 회고

1970년대

C·ML·Prolog

Unix·LCF·자연어 처리

동시대 논문과 HOPL 회고가 섞임

1979 이후

C++

C 시스템 생태계

1993년 설계자 회고

1989 이후

Python

Unix/C 확장과 빠른 스크립팅

1991년 첫 논문, 1995년 명세

1995 이후

Java·JavaScript

JVM·웹 브라우저

초기 백서와 이후 HOPL 회고

2000 이후

C#

.NET과 CLI

2001년 ECMA 최초 표준

2015 이후

Rust의 플랫폼 채택

Android·Windows·Linux

플랫폼 공식 문서와 보안 정책 보고서

시기

1957

언어

Fortran

초기 환경

IBM 704

여기서 읽을 문헌의 성격

컴파일러와 언어를 발표한 동시대 보고서

시기

1960

언어

COBOL

초기 환경

정부·기업의 이기종 데이터 처리

여기서 읽을 문헌의 성격

CODASYL 초기 명세

시기

1960

언어

LISP

초기 환경

MIT AI Group, IBM 704

여기서 읽을 문헌의 성격

McCarthy의 원 논문

시기

1960

언어

ALGOL 60

초기 환경

국제 알고리즘 표기

여기서 읽을 문헌의 성격

동시대 언어 보고서

시기

1964

언어

BASIC

초기 환경

Dartmouth Time-Sharing System

여기서 읽을 문헌의 성격

1964년 초기 공식 매뉴얼

시기

1962-1967

언어

Simula I·Simula 67

초기 환경

이산 사건 시뮬레이션

여기서 읽을 문헌의 성격

1966년 Simula I 소개 논문과 이후 Simula 67 계보

시기

1972 이후

언어

Smalltalk

초기 환경

Xerox PARC의 개인용 컴퓨팅

여기서 읽을 문헌의 성격

1993년 설계자 회고

시기

1970년대

언어

C·ML·Prolog

초기 환경

Unix·LCF·자연어 처리

여기서 읽을 문헌의 성격

동시대 논문과 HOPL 회고가 섞임

시기

1979 이후

언어

C++

초기 환경

C 시스템 생태계

여기서 읽을 문헌의 성격

1993년 설계자 회고

시기

1989 이후

언어

Python

초기 환경

Unix/C 확장과 빠른 스크립팅

여기서 읽을 문헌의 성격

1991년 첫 논문, 1995년 명세

시기

1995 이후

언어

Java·JavaScript

초기 환경

JVM·웹 브라우저

여기서 읽을 문헌의 성격

초기 백서와 이후 HOPL 회고

시기

2000 이후

언어

C#

초기 환경

.NET과 CLI

여기서 읽을 문헌의 성격

2001년 ECMA 최초 표준

시기

2015 이후

언어

Rust의 플랫폼 채택

초기 환경

Android·Windows·Linux

여기서 읽을 문헌의 성격

플랫폼 공식 문서와 보안 정책 보고서

He estimated that it might have taken three days to code this job by hand, plus an unknown time to debug it, and that no appreciable increase in speed of execution would have been achieved thereby.

그는 이 작업을 수작업(어셈블리)으로 코딩했다면 3일이 걸리고 디버깅에 미상의 시간이 추가로 들었을 것이며, 그렇게 손으로 짰다고 하더라도 실행 속도에서 뚜렷한 이점은 얻지 못했을 것이라고 추정했다.

1957: Fortran: 컴파일러를 팔아보자!

1957년 Backus와 IBM 팀이 발표한 제목은 The FORTRAN Automatic Coding System이다. 논문의 주인공은 문법만이 아니라 IBM 704에서 실제로 돌아가는 자동 코딩 시스템이었다. 당시 사용자가 두려워한 것은 고수준 표기가 낯설다는 사실보다, 컴파일러가 숙련자가 손으로 짠 코드만큼 빠른 결과를 만들지 못할 가능성이었다. 비싼 기계 시간을 절약하려다 느린 목적 코드를 얻으면 거래가 성립하지 않았다.

그래서 초기 Fortran 팀은 작성 시간을 줄이면서도 효율적인 기계어를 생성해야 했다. 언어의 추상화 비용을 컴파일러 최적화가 갚아야 했던 셈이다. 이 보고서를 읽으면 고수준 언어의 첫 산업적 설득이 "읽기 좋은 코드"만으로 이루어지지 않았음을 알 수 있다. 프로그래머의 시간을 줄이되 IBM 704의 계산 능력도 낭비하지 않는다는 글이 적혀 있었다.5

1960: 같은 해에 나온 세 가지 대답

1960년의 COBOL, LISP, ALGOL 60을 한 줄에 놓으면 "고수준 언어가 왜 필요한가"에 서로 다른 답이 나왔다는 것이 보인다.

COBOL 초기 명세의 제목에는 이미 Common Business Oriented Language가 들어 있다. 은행, 보험, 급여, 재고처럼 조직의 기록을 다루는 프로그램을 서로 다른 제조사의 컴퓨터에서도 옮겨 쓰려는 공동 언어였다. 특정 기계의 명령 체계보다 레코드와 보고서, 업무 절차가 전면에 나왔다. 그렇기에 COBOL은 수학 공식이 아닌 '영어 문장'의 형태를 띠어야 했다. 이는 개발자가 쓰기 편하도록 배려한 것이 아니라, 작성된 코드를 프로그래머가 아닌 경영진과 회계사가 직접 읽고 검증할 수 있도록 코드 자체가 하나의 '업무 매뉴얼'이 되기를 원했기 때문이다.
게다가 이는 단일 회사의 제품 명세가 아니라, 정부(국방부)와 산업계가 모인 CODASYL 위원회가 타협을 거쳐 만들어낸 정치적이고 행정적인 결과물이었다.6

Fortran이 과학 계산의 수식을 기계로 옮겼다면, COBOL은 조직의 장부와 관료적 업무 절차를 통째로 옮겼다. 실제로 COBOL은 글로벌 은행의 결제망부터 거대 유통망의 재고 처리까지 자본주의의 혈관을 구성하는 데 쓰였다.

이 시점부터 플랫폼의 의미는 '쇳덩어리로 된 컴퓨터 한 대'가 아니라 '기업의 거대한 데이터 처리 체계' 그 자체로 이동했다. 언어가 하드웨어 제조사들 사이의 문법 차이를 가려버리자, 기업들은 하드웨어 종속성(Vendor Lock-in)에서 해방되었다. 쇳덩어리는 수명이 다하면 버려졌지만, 조직이 COBOL 프로그램 작성과 인력 교육에 쏟아부은 막대한 인적 자본(비용)은 다음 세대의 기계로 고스란히 이식되어 그 투자금(ROI)을 영구히 회수할 수 있게 될 것이라 믿었다. 그리고 실제로 그 투자금은 완벽하게 보존되었다고 할 수 있다.

너무 완벽하게 보존된 나머지, 2020년대의 글로벌 은행들이 70대 은퇴자를 모셔 와 시간당 수백 달러씩 쥐여주며 시스템이 멈추지 않게 해달라고 빌어야 하는 기이한 코미디가 완성되었다. 코드는 불로장생을 얻었지만, 인적 자본은 늙는다는 사실을 빼먹은 것이다.

그리고 같은 해 McCarthy의 LISP 논문은 전혀 다른 문제를 다뤘다. MIT AI 그룹은 IBM 704에서 형식화된 선언문과 명령문을 표현하고, 그 표현을 대상으로 추론 실험을 하려 했다. 숫자 배열이 아니라 기호식과 리스트가 주된 데이터가 되었고, 논문은 재귀 함수와 조건식, cons, car, cdr, eval을 하나의 계산 체계로 묶었다.7

LISP의 플랫폼을 IBM 704라고만 보면 반쪽인데, 실제 기계는 704였지만, 연구자가 작성하려던 것은 숫자 계산 프로그램보다 기호를 조작하는 새로운 계산 세계였다. 코드와 데이터가 같은 리스트 표현을 공유하면서 언어 자체를 언어 안에서 다루는 경로도 열였다는 점이 핵심이다. 이후 여러 Lisp 머신과 Emacs 같은 환경이 언어, 런타임, 편집기를 한 묶음으로 만든 것은 우연한 방향 전환이 아니었다.

ALGOL60 보고서는 특정 제조사의 컴파일러에 얽매이는 대신, reference(참조), hardware(기계), publication(출판) 표현을 엄격하게 분리했다. 이전까지는 돌아가는 컴파일러가 곧 언어의 정체성이었지만, ALGOL 이후부터는 '명세서(Specification)'가 언어의 본체가 되었다. 8

상업 시장에서 ALGOL은 Fortran이나 COBOL처럼 계산서나 월급 명세서를 찍어내며 시장을 지배하지는 못했다. 하지만 블록 구조, 어휘적 범위(Lexical Scope), 재귀 호출 등 현대 언어의 뼈대가 되는 문법적 제약들을 하드코딩함으로써, C, Pascal, Java로 이어지는 모든 후대 언어들의 유전자(DNA)를 제공하는 '개념적 플랫폼'으로 군림했다.

1964: BASIC은 언어와 응답 시간을 함께 설계했다

Dartmouth의 1964년 BASIC 매뉴얼은 부제부터 "Dartmouth Time Sharing System을 위해 설계된 elementary algebraic language"라고 적었다. BASIC을 언어만 떼어 놓으면 왜 그 문법이 나왔는지 놓치기 쉽다. 학생이 터미널에서 프로그램을 입력하고 짧은 시간에 결과를 돌려받는 시분할 시스템(DTSS)가 옆에 있었다.9

Fortran이 전문 프로그래머를 거치는 비용을 줄였다면 BASIC은 컴퓨터를 전공하지 않은 학생도 직접 계산기를 사용할 수 있게 했다. 배치 작업을 제출하고 오래 기다리는 대신 대화하듯 수정하고 다시 실행했다. 여기서 저작 도구는 BASIC 문법과 시분할 운영체제, 터미널을 합친 전체 경험이었다.

이러한 '즉각적인 저작 경험'의 파괴력을 누구보다 정확히 이해하고 비즈니스로 무기화한 기업이 초기 마이크로소프트(Microsoft)였다. 1975년, 빌 게이츠와 폴 앨런은 스위치 조작만 가능하던 Altair 8800에 BASIC 인터프리터를 이식하며 회사를 창업했다. 그들에게 BASIC은 단순한 언어가 아니라, 파편화된 하드웨어를 덮어버리는 가장 강력한 추상화 레이어(Abstraction Layer)였다.

이후 마이크로소프트는 IBM, Apple, Commodore 등 수많은 초기 PC의 ROM에 자사의 BASIC을 구워 넣었다. 전원을 켜면 즉시 나타나는 프롬프트는 사실상 OS의 역할을 대신했고, 하드웨어 제조사들은 마이크로소프트가 정의한 언어 생태계에 종속되었다. Dartmouth에서 시작된 '언어와 인터랙티브 환경의 결합'이라는 철학은, 훗날 윈도우(Windows) 환경에서 누구나 드래그 앤 드롭으로 앱을 만들 수 있게 한 Visual Basic으로 이어지며 소프트웨어 생산성의 민주화를 완성했다. 물론 그 '민주주의'의 그늘 아래, 울산의 어느 공장 파견 나간 청년이 1998년에 만들어진 VB6 코드를 받아 '주석도 없고, 변수명은 a,b,c인데 이걸 어디서부터 수정해야하는가' 라는 실존적 명제 앞에 눈물을 흘리게 만들기도 했다.

어쨌건 이 결합은 나중에 개인용 컴퓨터에서 다시 나타났다. ROM에 BASIC 인터프리터가 들어간 기계는 전원을 켜면 곧바로 프로그래밍 표면을 보여 주었다. 별도 SDK를 설치하기 전에 사용자가 그 기계의 저자가 될 수 있었다.

1962-1972: 프로그램이 아니라 세계를 작성하기 시작하다

Simula라는 이름이 문서에 처음 등장한 때는 1962년이다. Dahl과 Nygaard가 1966년에 발표한 논문은 이산 사건 시스템을 간결하게 기술하기 위해 ALGOL 60을 확장한 Simula I을 소개한다. 뒤의 Simula 67 개념을 이 논문에 전부 소급해 넣으면 안 된다. 다만 시스템 안의 활동과 사건을 현실의 구성 요소에 가깝게 조직하려 했다는 방향은 이미 분명하다.
Simula 67에서 class, object, inheritance가 정리되면서 프로그램은 계산 절차뿐 아니라 서로 상호작용하는 세계의 모형이 되었다.10

Smalltalk은 이 생각을 개인용 컴퓨팅 환경 전체로 밀어붙였다. Alan Kay의 HOPL 회고는 Smalltalk의 배경을 시분할 컴퓨터, 그래픽 화면, 포인팅 장치, 인간과 컴퓨터의 공생에 관한 1960년대 연구에서 찾는다. 1972년의 Smalltalk는 문법 하나가 아니라 객체, 메시지, 화면, 브라우저, 디버거와 살아 있는 이미지가 연결된 매체였다.11

오늘날 언어와 IDE를 별도 제품처럼 생각하기 쉽지만, Smalltalk에서는 코드를 작성하고 실행하고 내부를 살펴보고 고치는 환경이 한 시스템 안에 있었다. 사용자가 완성된 프로그램만 만드는 것이 아니라 실행 중인 계산 매체 자체를 개조한다. "언어는 플랫폼의 저작 도구"라는 문장이 가장 문자 그대로 맞아떨어지는 사례 가운데 하나일 것이다.

1970년대: 언어가 다른 도구 안에 들어가다

ML은 처음부터 독립 범용 언어 시장을 겨냥한 상품이 아니었다. Edinburgh LCF에서 정리 증명 전술을 작성하는 metalanguage로 쓰였다. LCF는 작은 신뢰 커널과 외부에서 임의로 구성할 수 없는 theorem 추상화를 통해 증명 객체의 논리적 건전성을 지켰다. ML의 다형적 타입 추론과 추상 타입은 전술 코드의 일반적인 타입 오류를 일찍 잡고 내부 표현을 제한했다. Milner의 1978년 논문은 이 타입 규율과 Algorithm W가 ML에 구현되어 있다고 설명하지만, Algorithm W 자체가 증명의 참을 보증하는 것은 아니다.12

이 사례에서 호스트 플랫폼은 운영체제가 아니라 정리 증명기라고 할 수 있다. ML은 LCF를 확장하는 안전한 저작 언어였고, 그 안에서 검증 자동화 전략을 만들었다. 연구 도구의 내부 언어가 나중에 Standard ML, OCaml과 여러 타입 시스템으로 뻗어 나갔다. 사실 이 부분은 프로그래밍 언어의 역사는 '거대 플랫폼 장악'이라는 서사에서 벗어나, '특정 도메인을 해결하기 위해 만들어진 부산물(By-product)'의 역사로 궤도를 튼다라고 봐야할 것이다.

Prolog도 언어를 만들자는 선언에서 곧바로 시작하지 않았다. Colmerauer와 Roussel의 HOPL 회고에 따르면 출발점은 프랑스어 자연언어 처리 프로젝트였고, 1971년 말의 예비판을 거쳐 1972년 말 더 확정된 체계가 나왔다. 논리 관계를 기술하면 실행기가 해답을 탐색하는 방식은 파서와 추론 규칙을 작성하던 필요 속에서 자랐다.13

LISP, ML, Prolog은 단일 하드웨어 판매 전략으로는 설명하기 어렵다. 대신 AI 연구, 정리 증명, 자연언어 처리라는 작업 환경이 언어를 필요로 했고, 언어가 다시 그 연구 환경에서 가능한 실험의 범위를 넓혔다

즉 특정 플랫폼(도메인,기계,비즈니스)을 통제하려는 욕망에 따라서 저작 도구가 바뀌고 있는 것이다.

1970년대 이후: C와 Unix가 만든 호환성의 제국

C와 Unix의 관계는 언어가 플랫폼의 저작 도구라는 관점을 꽤 노골적으로 보여 준다. 한쪽이 다른 쪽을 일방적으로 성공시킨 것이 아니었다. 둘은 서로가 퍼질 조건을 만들었다.

Multics가 끝난 뒤, 작은 시스템으로 돌아가다

1960년대 후반 Bell Labs는 MIT, General Electric과 함께 만들던 Multics에서 빠져나오고 있었다. Dennis Ritchie의 회고를 보면 연구자들도 Multics가 쓸 수 있는 형태로 완성될 무렵이면 너무 늦고 비용도 지나치게 클 것이라 생각하기 시작했다. Multics에서 익힌 process, 계층형 file system, user-level command interpreter와 device 접근의 생각은 그 뒤에도 남았다.
Ken Thompson을 중심으로 한 작은 모임은 훨씬 작은 기계에서 자신들이 직접 통제할 수 있는 환경을 다시 만들려 했다.14

회사에 Unix라는 정식 프로젝트가 있던 시기도 아니다. Thompson, Ritchie와 J. F. Ossanna 등은 1969년 동안 PDP-10이나 Sigma 7을 사 달라고 여러 번 요청했지만 받아들여지지 않았다.

그 무렵 Thompson은 GE-635의 비싼 interactive batch 환경에서 실행하던 태양계 게임 Space Travel을 거의 쓰이지 않던 PDP-7로 옮겼다. Ritchie의 별도 회고에 따르면 게임은 Unix보다 먼저였고, 이 작업 덕분에 Thompson이 PDP-7을 익히고 마음껏 쓸 수 있는 자리를 찾았다. 훗날 Space Travel은 Unix의 탄생 신화처럼 자주 이야기됐지만, Ritchie가 부여한 역할은 더 제한적이다. 게임이 Thompson을 PDP-7로 데려갔고, 그 기계에서 이미 구상하던 file system과 운영체제 실험을 시작할 수 있었다.14

PDP-7의 첫 시스템은 assembly로 작성됐다. 초기에는 GECOS에서 program과 tool을 만들고 paper tape로 PDP-7에 옮겨야 했지만, assembler가 완성되면서 Unix가 자기 위에서 다음 도구를 만드는 self-supporting 환경으로 바뀌었다. file system과 process control, shell, I/O redirection도 완성된 설계도에서 한 번에 나온 것이 아니라 실제 사용 중에 고쳐졌다. Ritchie는 작은 팀이 I/O system과 shell을 함께 통제했기 때문에 Multics에서 조직 사이에 걸려 있던 변경도 Unix에서는 빠르게 시험할 수 있었다고 회고한다

1970년에 승인을 얻은 PDP-11도 순수한 운영체제 연구비로 산 장비는 아니었다. 이전보다 훨씬 싼 약 6만 5천 달러짜리 기계를 문서 편집과 조판에 사용하겠다는 구체적인 제안이 통했다. Bell Labs 특허 부서의 문서를 처리하는 일이 Unix에 첫 조직적 고객과 명분을 준 셈이다. disk가 늦게 도착하는 동안 Thompson은 PDP-7의 cross-assembler를 이용해 PDP-11용 Unix를 다시 적었고, 첫 버전은 여전히 assembly에 가까웠다.

Pipe도 최초 PDP-7 설계에 완성된 채 들어 있지 않았다. Doug McIlroy가 command의 출력을 다음 command의 입력으로 잇는 구상을 계속 밀었고, 1972년에 pipe와 pipeline이 Unix에 들어왔다. 운영체제, shell과 작은 utility를 가까운 사람들이 함께 고치고 있었기 때문에 새 연결 방식에 맞춰 각 층을 같이 바꿀 수 있었다. Unix의 강점으로 남은 조립 방식도 실제 사용과 동료의 요구 속에서 뒤늦게 자란 셈이다.

PDP-11이 B를 C로 밀어붙였다

초기 Unix의 고수준 언어는 Thompson이 BCPL을 줄여 만든 B였다. PDP-7과 PDP-11의 B compiler는 machine code 대신 threaded code를 만들었고, 결과가 assembly보다 너무 느려 kernel과 중심 utility를 다시 쓰기에는 부족했다. B가 사실상 하나의 machine word만 다루던 언어라는 점도 byte-addressed PDP-11에서 문제가 됐다. 문자와 byte, 앞으로 붙을 부동소수점 장치를 제대로 표현하려면 type이 필요했다.15

Ritchie는 1971년에 B에 char를 추가하고 PDP-11 machine instruction을 직접 생성하는 compiler를 만들기 시작했다. 잠시 NB, 즉 new B라고 불린 단계에 int, char, array와 pointer가 들어왔고, 1972년에는 structure와 더 일반적인 type 체계가 갖춰지면서 C라는 이름이 붙었다. Thompson이 B와 초기 Unix를 만들고 Ritchie가 1971~1973년에 B를 C로 바꾸었다는 역할 구분은 Ritchie 자신의 HOPL 회고에도 명시돼 있다.15

이제 compiler가 assembly와 경쟁할 만큼 작고 빠른 code를 만들 수 있었고, 1973년 여름 Unix kernel 대부분을 C로 다시 쓸 수 있었다. 이 과정을 언어와 운영체제 가운데 어느 하나가 먼저였다고 잘라 말하기는 어려울 것이다.
C compiler가 성숙할 때마다 더 많은 Unix code를 옮길 수 있었고, kernel과 utility를 작성하며 드러난 문제는 다시 type과 library 설계에 반영됐다. PDP-11의 성질과 B compiler의 한계, 실제 operating system code가 맞물리면서 C와 Unix가 함께 형태를 잡았다.

이 전환으로 새 기계에 Unix를 가져갈 때 운영체제 전체를 그 기계의 assembly로 다시 적기보다, 먼저 C compiler를 마련한 다음 device driver와 context switch처럼 기계에 붙어 있는 부분을 손보는 길이 열렸다.15

Loading animated diagram...

이식성이 저절로 생긴 것은 아니다. Johnson과 Ritchie가 Interdata 8/32로 Unix를 옮긴 보고서를 보면 컴파일러의 새 backend, 어셈블러, 로더, 디버거와 장치 드라이버를 준비해야 했다. 그럼에도 장치 드라이버와 어셈블리 부분을 제외한 운영체제 코드의 약 95%를 거의 그대로 가져갔고, 컴파일러·어셈블러·로더·디버거도 75~80%가 공통 코드로 남았다.15

"C로 썼으니 어디서나 컴파일된다"(이후에 이 개념은 꽤 중요하게 다뤄진다)는 식의 마법은 아니었으나, 이전에는 운영체제 재작성으로 치르던 비용을 포팅 계층 몇 군데로 몰아넣은 효과는 컸다.

C의 이식성을 한 덩어리로 부르면 이 과정에서 무엇을 재사용했고 무엇을 다시 만들었는지 놓치기 쉽다. 실제로 이동한 것은 몇 개의 층이었다.

비교적 그대로 옮긴 것

새 기계에서 다시 맞춘 것

언어와 소스

제어문, 함수, 구조체, 상당수 응용 코드

정수 폭, 정렬, endian, 구현 정의 동작

컴파일 도구

front-end와 optimizer의 공통 부분

code generator, assembler, relocation

Unix 인터페이스

file descriptor, process, pipe, C library 관습

system call 구현, driver, machine-dependent code

작업 환경

shell, make, debugger, text utility, manual

부팅, 장치 관리, 패키징과 배포

언어와 소스

비교적 그대로 옮긴 것

제어문, 함수, 구조체, 상당수 응용 코드

새 기계에서 다시 맞춘 것

정수 폭, 정렬, endian, 구현 정의 동작

컴파일 도구

비교적 그대로 옮긴 것

front-end와 optimizer의 공통 부분

새 기계에서 다시 맞춘 것

code generator, assembler, relocation

Unix 인터페이스

비교적 그대로 옮긴 것

file descriptor, process, pipe, C library 관습

새 기계에서 다시 맞춘 것

system call 구현, driver, machine-dependent code

작업 환경

비교적 그대로 옮긴 것

shell, make, debugger, text utility, manual

새 기계에서 다시 맞춘 것

부팅, 장치 관리, 패키징과 배포

여기서 C compiler는 이동용 관절에 가까웠다. 모든 응용 프로그램이 새 명령어 집합을 직접 상대하지 않게 하고, 기계 의존 코드를 compiler backend와 운영체제의 좁은 구간에 모았다. 이 경계 밖의 코드까지 완전히 동일했던 것은 아니지만, 다시 써야 할 양은 크게 줄었다.

함께 이동한 것도 소스 코드만은 아니었다. C 컴파일러와 셸, 링커, 디버거, 시스템 호출, 라이브러리, make와 텍스트 도구가 하나의 작업 환경을 이루었는데, 개발자는 C 문법을 배우면서 Unix가 파일과 프로세스를 다루고 작은 프로그램을 파이프로 조립하는 방식까지 익혔다. C는 Unix가 여러 기계에서 다시 태어날 수 있게 했고, Unix는 C가 장난감 언어가 아니라 운영체제 전체를 맡을 수 있다는 실물을 내놓았다.

1983-1992: Unix 호환성이 새로운 시스템의 유통망이 되다

GNU 프로젝트는 1983년 9월에 발표됐고, 실제 개발은 1984년 1월에 시작됐다. 목표는 AT&T Unix의 소스 코드를 복제하는 것이 아니라, Unix와 호환되면서도 누구나 사용·수정·배포할 수 있는 자유로운 운영체제를 새로 만드는 일이었다. Unix 계열의 설계가 이미 여러 기계에서 검증됐고, 기존 사용자가 자신의 지식과 프로그램을 비교적 쉽게 옮길 수 있다는 점도 Unix 호환성을 선택한 중요한 이유였다.16

여기서 호환성은 낡은 설계를 수동적으로 떠안는 것만을 뜻하지 않는다. 이미 존재하는 사용자의 지식, 셸 스크립트, 소스 코드와 작업 습관을 새로운 시스템으로 끌어오는 채택 전략이기도 하다. GNU는 GCC, GDB, Bash, Binutils, C 라이브러리와 여러 유틸리티를 차례로 만들었는데, 이 도구들은 완성된 GNU 운영체제를 기다리지 않고 기존 Unix에서도 사용됐고, 그 과정에서 각각 독자적인 사용자층을 확보했다. 1990년 무렵 GNU 시스템에 빠져 있던 가장 큰 조각은 실용적으로 사용할 수 있는 커널이었다.

그 빈자리에 들어온 것이 Linux였다. 1991년에 공개된 Linux는 386 PC에서 작동하는 Unix 계열 커널로 출발했다. Linux 0.01 릴리스 노트에는 GCC 1.40으로 커널을 빌드하는 방법과 Bash 실행 파일이 함께 들어 있으며, 커널만으로는 사용할 수 있는 시스템이 되지 않는다는 경고도 적혀 있다. 17

처음부터 Linux는 고립된 커널 프로젝트가 아니라, 이미 존재하던 GNU 컴파일러와 셸, 라이브러리 위에서 빌드되고 실행되는 소프트웨어였다.

이 프로젝트가 외부로 퍼진 과정은 이름의 유래에서도 드러난다. Torvalds는 원래 free, freak, Unix 계열 이름에 자주 붙던 x를 합친 Freax를 정식 명칭으로 생각했다. Linux는 Linus와 Minix를 결합한 임시 작업명에 가까웠다고 한다.

그러나 소스를 배포할 FTP 공간을 마련한 헬싱키 공대의 Ari Lemmke는 Freax 대신 서버 디렉터리를 pub/OS/Linux라고 만들었다. 외부 사용자는 그 경로에서 소스를 내려받았고, 배포 경로에 붙은 이름이 결국 프로젝트의 공식 명칭처럼 굳었다. Torvalds도 이듬해 Ari가 디렉터리를 Linux라고 불렀고 그 이름이 정착했다고 회고했다.18

이 일화가 흥미로운 이유는 단순히 누군가 프로젝트 이름을 임의로 바꿨기 때문이라기보다는 저자가 붙이려 했던 이름보다 사람들이 실제로 소프트웨어를 발견하고 내려받은 경로의 이름이 더 강한 힘을 가졌다는 것이다. 우리는 꽤 이런 사례를 많이 본다. 결국 Linux의 정체성은 코드 내부에서만 결정된 것이 아니라, FTP 서버와 인터넷이라는 유통망 속에서도 형성됐다라고 보는게 옳으리라.

하지만 FTP에 커널 소스를 올리는 것만으로는 완전한 운영체제가 만들어지지 않는다. Linux 커널은 이미 축적돼 있던 GNU의 컴파일러·셸·라이브러리·유틸리티와 결합했고, 여러 배포판은 이 부품들을 설치하고 부팅할 수 있는 하나의 시스템으로 묶었다. 이후에는 패키지 관리와 업데이트, 하드웨어 설정까지 배포판이 담당하면서 일반 사용자와 개발자가 실제로 사용할 수 있는 Unix 계열 환경이 형성됐다.

Loading animated diagram...

따라서 Linux 커널만 떼어 놓으면 이 시스템이 확산된 경로의 절반 이상이 사라진다. C와 Unix가 수십 년 동안 쌓아 놓은 인터페이스, GNU가 자유 소프트웨어로 다시 만든 도구 사슬, 가격이 내려간 386 PC, 인터넷을 통한 배포와 협업, 그리고 이를 하나의 제품으로 조립한 배포판이 한곳에서 맞물렸다.

C 역시 이 결합을 타고 특정 회사나 단일 운영체제의 언어에서 더욱 멀어졌다. Unix 계열 운영체제와 GNU 도구, 네트워크 서버와 임베디드 장치를 잇는 공통 기반이 됐고, 호환성과 도구 사슬은 단순한 기술적 특성을 넘어 새로운 시스템과 사용자를 끌어들이는 유통망으로 변했다.

이때 Torvalds는 회고들을 보면 그가 리눅스를 처음 개발했을때, 거창한 혁명가가 아니라 그저 자기 방 386 PC에서 Unix를 마음껏 돌려보고 싶었던 긱(Geek)이었다. 수천 달러짜리 상용 워크스테이션이나 교육용으로 기능이 제한된 Minix에 답답함을 느끼던 한 청년의 개인적인 프로젝트가, GNU의 라이선스를 만나 '자유(Free)' 소프트웨어로 풀리면서 일어난 가장 큰 변화는 생태계 규모의 폭발이었다.

전 세계의 프로그래머들은 더 이상 값비싼 라이선스나 기업의 눈치를 보지 않고도, PC 위에 완전한 Unix 환경을 구축하고 코드를 뜯어고칠 수 있게 됐다. 하지만 이 '자유로운 Unix'가 널리 보급되자 역설적인 상황이 벌어졌다. 터미널에서 단순한 텍스트를 처리하던 소규모 유틸리티의 시대를 지나, X 윈도우 기반의 거대한 GUI 데스크톱(KDE 등), 방대한 네트워크 데몬, 대규모 데이터베이스가 이 무료 운영체제 위로 쏟아져 들어오기 시작한 것이다.

그리고 이렇게 폭발적인 호응에 의해 시스템의 복잡도(Complexity)가 임계점을 넘는다. C 언어는 커널을 작성하고 '한 가지 일만 잘하는' 작은 Unix 도구들을 만드는 데는 날카로웠지만, 수십만 줄로 팽창하는 사용자 영역(User-space)의 거대 애플리케이션 상태(State)를 전역 변수와 절차적 호출만으로 통제하는 것은 불가능에 가까워지기 시작했다.(물론 순수 C 주의자들은 절대 동의하지 않을 것이다. 그들은 당신의 C 실력이 부족하기 때문에 그런 일이 발생했다고 할 것이다. 쿨하게 인정하고 넘어가는게 정신건강에 좋다)

새로운 시대의 프로그래머들에게는 C 언어가 가진 메모리 통제력과 기존 Unix 생태계와의 완벽한 호환성은 그대로 둔 채, 비대해진 소프트웨어의 구조를 캡슐화하고 조립할 수 있는 상위 차원의 설계 도구가 필요했다. 자유로워진 Unix 환경이 불러온 '애플리케이션의 거대화', 그것이 바로 C++가 시스템 프로그래밍의 역사를 이어받게 된 가장 강력한 공학적 명분이었다.

C++와 Python: 같은 기반 위에 들어가는 두 가지 방법

C++는 이 생태계 안에서 더 큰 시스템을 작성할 방법을 찾았다.
Stroustrup은 1979년의 C with Classes에서 출발해 Simula에서 본 추상화와 C가 제공하던 시스템 접근을 함께 가져오려 했다. 초기 C++ 구현인 Cfront가 C 코드를 출력한 이유도 여기에 닿아 있다. 당시 여러 기계에 이미 깔려 있던 C 컴파일러와 assembler·linker를 backend로 삼으면 새 언어를 위한 기계별 코드 생성기를 처음부터 모두 만들지 않아도 됐다. Stroustrup이 C를 "가장 이식성 높은 어셈블러"로 썼다고 회고한 대목이다.19

소프트웨어의 규모가 커지면서 전역 상태와 함수 목록만으로 전체를 관리하기 어려워졌다. 여러 팀이 같은 프로그램을 오랫동안 고치려면 내부 표현을 감추고, 문제 영역의 개념을 사용자 정의 type으로 옮기며, 구현이 달라도 같은 interface로 다룰 방법이 필요했다.

소프트웨어가 대형화되면서 복잡성을 작은 단위로 나누고, 표현과 구현을 감추며, 부품을 재사용할 수 있는 추상화가 필요해졌다. C++는 당시 그 요구를 객체지향과 추상 자료형으로 해결하면서도 기존 C 생태계를 버리지 않았기 때문에 확산될 수 있었다.

물론 객체지향만이 유일한 답은 아니다. module system과 함수형 프로그래밍도 같은 조직 문제를 다뤘다. C++가 산업에서 유리했던 대목은 class와 generic programming을 C의 native 배포망 안으로 가져왔다는 데 있다.
Cfront는 C code를 출력했고, GNU의 G++는 1987년부터 GCC·assembler·linker·debugger와 함께 여러 Unix 계열에 배포됐다.20

Stroustrup은 Simula로 큰 simulator를 작성하면서 class와 class hierarchy가 프로그램을 작고 이해하기 쉬운 부분들의 집합처럼 나누어 준다고 느꼈다. 반면 당시 Simula 구현의 느린 linking과 garbage collection은 규모가 커질수록 부담이 됐다. C++의 초기 방향은 그 조직 능력을 가져오면서도 별도 컴파일, 실행 속도와 저수준 접근에서는 C와 BCPL의 감각을 남기는 쪽이었다.19

Cfront가 C를 출력했다는 사실도 단순한 구현 일화로 끝나지 않는데, 새 CPU마다 C++용 code generator를 먼저 완성하지 않고 그 기계의 C compiler와 assembler를 이용할 수 있었다는 것이다.

생성자 호출, virtual function table과 name mangling은 기존 native 도구가 처리할 형태로 내려갔고, 결과물은 평소 쓰던 object file과 library였다.

Loading animated diagram...

C와의 연결은 source 수준에서 끝나지 않았다. extern "C"와 C linkage는 운영체제 API와 오래된 library를 호출할 좁은 경계를 남겼다.
반면에 C++ ABI는 클래스 레이아웃(class layout), 네임 맹글링(name mangling), 예외(exception), RTTI와 virtual table까지 맞아야 해서 compiler와 version 사이의 binary compatibility가 오래된 골칫거리가 됐다.21

1989년 시작된 표준화와 1998년 첫 ISO 표준, STL은 source와 library가 이동할 공통 표면을 넓혔다. 쓰지 않는 기능의 비용은 내지 않는다는 zero-overhead 원칙도 C 사용자가 새 추상화를 받아들일 수 있게 한 계약이었다.22

그 대가로 header 기반 build, 복잡한 객체 수명, 미정의 동작(undefined behavior)과 template code bloat가 남았다. 그래도 새 VM과 운영체제로 전면 이주하는 비용보다는 기존 네이티브 툴체인(native toolchain) 위에 C++를 얹는 편이 싼 조직이 많았다.

시간이 지나자 게임 엔진, 브라우저, 데이터베이스와 GUI framework가 다시 C++의 보완재가 되었다. 처음에는 C의 도구 사슬을 빌려 시장에 들어갔지만, 나중에는 C++로 쌓인 library와 engine 때문에 다음 프로젝트가 C++ 경계로 들어왔다. 호환성은 과거를 끌고 가는 짐이면서 동시에 새 사용자를 부르는 유통망이었다.

하지만 사람들은 이렇게 말할 것이다. DOOM은 C로 쓰여있는데? C도 충분히 복잡한 걸 잘 처리할 수 있어!

Doom에서 Doom 3까지

1993년의 Doom은 이 글에 꽤 불편한 사례처럼 보인다. renderer와 game engine은 대부분 C였고 일부 어셈블리(assembly)가 섞였는데, 당시 기준으로는 대단히 복잡한 상용 프로그램이었다.
복잡한 software가 곧 C++를 요구했다면 Doom부터 설명하기 어려워진다. 그래서 여기서는 복잡하다는 말부터 나눠볼 생각이다.

원작 Doom의 기술적 난도는 높았다. BSP renderer, fixed-point 연산, network play와 여러 플랫폼 경계가 한꺼번에 들어 있었다.
다만 engine의 기본 윤곽은 John Carmack 한 사람의 머리와 code에 강하게 집중돼 있었다. 다른 프로그래머가 보조하고 디자이너와 아티스트가 붙었지만, 여러 팀이 독립된 서브 시스템(subsystem)을 소유하며 인터페이스(interface)를 장기간 협상하는 형태와는 거리가 있었다. 작은 인원이 공유하던 단단한 멘탈 모델(mental model)이 C 코드베이스(codebase)를 붙들고 있었다.23

제작상의 복잡성은 code 한곳에 몰아넣지 않고 옆으로 분리했다. id Software는 NeXTSTEP workstation에서 작업했고, DoomEd와 BSP node builder 같은 저작 도구는 Objective-C로 만들었다. engine과 game logic은 C로 유지한 뒤 Watcom C compiler를 통해 MS-DOS 실행물로 내보보냈는데, map과 sprite, sound, texture는 WAD에 묶었다. engine, 제작 도구와 content가 서로 다른 언어와 파일 경계에 놓인 셈이다.24

Loading animated diagram...

NeXTSTEP의 Objective-C framework는 editor와 내부 application을 빨리 만드는 쪽을 맡았고, C는 작은 runtime과 직접적인 hardware 접근을 맡았다. 하나의 거대한 C++ codebase 안에서 모든 문제를 풀기 전, 언어와 process 경계를 이용해 복잡성을 잘라 둔 방식이었다. Carmack이 게임 개발 도구가 게임 자체보다 커지기도 한다고 말한 이유도 여기에 있다. 도구까지 DOS C로 밀어 넣었다면 원작 Doom의 C code가 보여 주는 단순함은 유지되기 어려웠을 것이다.

1997년 공개된 Linux source가 수많은 port로 이어진 것도 C core와 platform 경계가 비교적 분명했던 덕이 컸다. 반대로 DOS판의 sound library는 저작권 문제로 공개하지 못했다. portable C가 code의 상당 부분을 옮겨 줬어도 외부 library와 장치 의존성은 port마다 다시 해결해야 했다. Doom이 별별 기계에서 돌아가는 오랜 농담은 작은 C core만큼이나 그 주변을 갈아 끼울 수 있었던 구조에서 나왔다.24

십여 년 뒤 Doom 3에서는 사정이 달라졌다. 렌더러(renderer) 외에도 sound, physics, animation, networking과 scripting이 서로 맞물렸고, Carmack의 회고로는 한때 다섯 명가량의 프로그래머가 큰 서브 시스템(subsystem)을 각자 처음부터 작성했다. 한 사람이 engine 전체의 전제를 머릿속에 넣고 바로 고치던 방식도 점점 통하지 않았다. 이 시기의 id Tech 4는 C++로 옮겨 갔다고 한다..23

Carmack에게는 이미 C와 NeXTSTEP의 Objective-C 경험이 있었다. 그는 동료들의 C++ code를 보며 언어에 들어갔고, 초기 renderer는 C로 만든 것을 C++ object 안에 감싼 흔적도 남았다고 말했다. 그럼에도 여러 개발자가 참여하면서 성능이 중요한 대형 프로젝트에는 C++가 적합하다고 평가했다. class가 renderer를 더 빠르게 만들어서라기보다, code의 소유권과 수명, subsystem 사이의 계약을 네이티브 코드베이스(native codebase) 안에서 표현할 필요가 커졌던 시기였다.

Loading animated diagram...

따라서 C++의 확산을 설명할 때 software 규모를 source line 수나 알고리즘 난도로만 재면 Doom이라는 예시로 사람들은 반박하지만,
C++가 강하게 필요해진 지점은 복잡성이 사람과 팀 사이로 퍼지고, 서로 다른 부품을 독립적으로 바꾸면서도 전체를 오래 유지해야 했던 곳이다. 게임 업계에서 engine과 editor, exporter, asset pipeline, platform SDK가 묶이기 시작하자 C++는 실행 파일을 만드는 언어를 넘어 제작 기반의 공용 경계가 됐다.
C의 native 유통망을 그대로 쓰면서 그 위에 조직용 추상화를 얹을 수 있었기 때문이다.

(저는 리눅스씨의 말을 믿는 편입니다)

In general, I'd say that anybody who designs his kernel modules for C++ is either

(a) looking for problems

(b) a C++ bigot that can't see what he is writing is really just C anyway

(c) was given an assignment in CS class to do so.

Feel free to make up (d).

일반적으로 말해서, 커널 모듈을 C++로 설계하는 사람은 대개 다음 중 하나라고 본다.

(a) 스스로 문제를 찾아다니는 사람
(b) 자기가 쓰는 게 사실상 C라는 걸 깨닫지 못하는 C++ 광신도
(c) 컴퓨터과학 수업 과제로 어쩔 수 없이 그렇게 한 사람

(d)는 자유롭게 추가해도 좋다.

그렇다면 Linux kernel은 왜 C에 남았나

여기까지 C++가 대규모 코드베이스 작성에 더 좋다고 말했지만, 사람들은 반례를 말할 것이다. Linux kernel은 수천 명이 오랫동안 고치는 대형 software인데도 여전히 중심 언어가 C다.
규모가 커지면 C++로 이동한다는 설명을 그대로 적용하면 맞지 않는다. 우선 Linux kernel과 Linux 사용자 공간을 나눠야 한다. browser, desktop application, database와 개발 도구가 돌아가는 사용자 공간에서는 C++와 Python, Java, Go, Rust를 얼마든지 쓴다. C에 강하게 남아 있는 중심은 kernel 쪽이다.

물론 리눅스씨의 C++가 좋지 않다는 개인적 취향도 있으시겠지만은, kernel은 일반 application이 기대하는 runtime 아래에 있다. memory allocator와 thread, interrupt, scheduler와 device를 자신이 제공한다. Linux 문서도 kernel을 standard C library에 의존하지 않는 freestanding 환경으로 설명하며, 실제 source는 ISO C 그대로가 아니라 GNU C11 dialect와 compiler extension을 사용한다.25

malloc()이나 thread runtime을 가져다 쓰는 프로그램과 그 기반부터 만드는 프로그램은 언어에 기대할 수 있는 조건이 다르다.

C++로 커널을 작성하거나 C++ 컴파일러로 커널 모듈을 빌드하는 일 자체가 불가능한 것은 아니다. 표준 라이브러리, 예외 처리, RTTI, 동적 초기화와 전역 객체의 생성·소멸 등에 필요한 런타임을 사용하지 않거나, 커널 환경에 맞는 구현을 직접 제공하면 된다. 실제로 C++로 작성된 커널과 커널 구성 요소도 존재한다.

다만 이렇게 제한된 C++는 일반 애플리케이션에서 사용하는 C++와 상당히 달라진다. 프로젝트는 예외, RTTI, 동적 할당, 전역 생성자, 특정 표준 라이브러리 기능, 가상 함수와 템플릿 사용 범위 등을 별도로 정해야 하며, 컴파일러가 생성하는 코드와 ABI까지 지속적으로 확인해야 한다. 문제는 C++를 커널에서 사용할 수 있느냐가 아니라, 허용된 C++의 경계를 누가 정의하고 어떻게 강제할 것이냐에 있다.

왜 이러한 문제가 생길까? C++는 저수준 메모리 조작부터 객체 지향, 제네릭 프로그래밍, 함수형 표현, 예외 처리와 대규모 애플리케이션 개발까지 서로 다른 요구를 하나의 언어 안에 수용해 왔다. 그 결과 동일한 문법 체계 안에서도 비용과 실행 모델이 크게 다른 기능들이 공존한다. 애플리케이션에서는 유용한 추상화가 커널에서는 숨은 메모리 할당, 예외 처리 메타데이터, 정적 초기화 순서, 코드 크기 증가 또는 예측하기 어려운 제어 흐름으로 이어질 수 있다.

물론 팀이 “우리는 C++의 일부 기능만 사용한다”고 정할 수는 있다. 그러나 규모가 큰 프로젝트에서는 규칙을 문서화하는 것만으로 충분하지 않다. 새로운 기여자와 서브시스템이 계속 들어오고, 컴파일러와 언어 표준도 변하며, 겉보기에는 단순한 코드가 금지된 런타임 기능을 간접적으로 끌어들일 수도 있다. 결국 언어가 제공하는 선택지를 다시 프로젝트 차원에서 금지하고, 정적 분석과 빌드 설정, 코드 리뷰로 그 금지를 계속 집행해야 한다. 언어의 복잡성이 조직의 통제 비용으로 이전되는 것이다.

이것이 리누스 토르발즈가 C++를 비판하면서 강조했던 핵심에 가깝다고 생각된다. 그의 주장은 C++로 커널을 만드는 것이 물리적으로 불가능하다는 것이 아니었으나, 효율적이고 이식 가능한 시스템 코드를 작성하기 위해 C++의 여러 기능을 포기해야 한다면, C++를 선택해서 얻는 장점도 상당 부분 사라진다는 것이었다.
또한 C를 선택하는 것 자체가 사용할 수 있는 추상화와 실행 모델을 좁혀, 프로젝트 규율을 언어 외부의 약속이 아니라 비교적 명시적인 제약으로 만든다고 보았다.26

혹자는 이를 ‘구속복 아키텍처’라고도 부른다. (물론 그 혹자는 나다.)

너무 많은 선택지를 제공한 뒤 개발자에게 매번 올바른 판단을 요구하는 대신, 애초에 선택 가능한 범위를 좁혀 시스템이 잘하는 방식으로만 코드를 작성하게 만드는 접근이다. 구속복은 표현력을 줄이지만, 그만큼 잘못된 추상화와 예측하기 어려운 실행 비용, 팀 규율의 이탈 가능성도 함께 줄인다.

compiler와 architecture마다 exception unwinding, object initialization, ABI를 검증하는 비용도 생긴다. Linux가 이미 해결한 문제에 새로운 runtime 계약을 하나 더 얹는 셈이다.

1991년부터 쌓인 경로 의존성도 문제가 될 것이다. architecture port와 driver, debugger, static analysis, build system과 review 관습이 모두 GNU C source를 중심으로 자랐는데, kernel 내부 API는 계속 바뀌더라도 그 변경을 다루는 사람들의 공통 언어는 C였다. 수천만 line을 C++로 다시 쓰지 않더라도 두 언어를 함께 유지하는 순간 build matrix와 reviewer가 확인할 규칙은 늘어난다. 이는 리뷰어에게도 부담이 될 것이다. C++가 제공할 편익이 이 전환 비용을 꾸준히 넘어야 하는데, Linux에서는 오랫동안 그 계산이 성립하지 않았다.(그렇기때문에 최근 리눅스 커널의 Rust 도입이 놀라운 것이다)

언어 선택이 완전히 얼어붙은 것은 아니다. Rust support는 Linux 6.1에서 mainline에 들어왔고, 현재 kernel 문서는 C 쪽 binding을 생성한 뒤 no_std 환경에서 driver와 file system, 공용 abstraction을 작성하는 경로를 제공한다.25Rust가 들어온 이유도 새 문법 자체보다 memory safety가 driver와 동시성 code에서 치르는 비용을 줄일 가능성이 있었기 때문일 것이다. 기존 C 전체를 갈아엎지 않고 새 code가 들어오는 경계부터 시험하는 방식으로 진행되는 것도 그때문이다.

결국 C++와 Linux의 갈림길은 규모 하나로 정해지지 않았다. game engine에서는 runtime과 도구가 이미 마련된 상태에서 여러 subsystem과 사람을 조직하는 추상화의 편익이 컸다. Linux kernel은 그 runtime을 직접 제공했고, 필요한 추상화도 오랜 C 관습과 GNU extension 안에 쌓아 두었다. 같은 복잡성이라도 어디에 비용이 몰려 있는지, 기존 플랫폼을 바꿀 때 얼마를 다시 검증해야 하는지가 달랐다.

그런 상황에서 Python은 다른 높이에서 같은 생태계를 이용했다. van Rossum은 1989년 말 ABC에서 배운 간결함을 이어받으면서도 Unix와 C 프로그래머가 쉽게 확장할 수 있는 스크립트 언어를 만들기 시작했고, 1991년 인터넷에 공개했다. 1995년 참조 매뉴얼은 Python을 C와 셸 사이를 잇고 C로 확장하기 쉬운 빠른 프로토타이핑 언어로 설명한다.27

Loading animated diagram...

두 언어의 문법과 실행 모델은 크게 다르다. 그래도 채택 경로에는 공통점이 있었는데, 이미 쌓인 C 코드와 운영체제 API를 버리고 새 세계로 이주하라고 요구하지 않았다.
C++는 네이티브 코드와 기존 도구 사슬의 연속성을 택했고, Python은 네이티브 라이브러리를 더 큰 단위로 조립하는 표면을 제공했다. 과거 자산과 연결되는 능력이 새 언어의 유통 비용을 낮춘 것이다.

상단 왼쪽: 70년대 구로동 공장지대
상단 오른쪽: 2004년 구로동 벤처타운

하단:Java 2명 타세요

1995-2001: 언어, 런타임, 배포망이 한 상품이 되다

프로그래밍 언어의 역사에서 Java를 빼먹으면 섭섭할 것이다.

Java의 1996년 5월 백서는 단순한 객체지향 문법보다 architecture-neutral bytecode, 가비지 컬렉션, 보안, 동적 로딩과 분산 환경을 한꺼번에 제시했다. Java 자체의 공개와 웹을 통한 확산은 1995년부터 본격화됐다. Oak가 처음에는 임베디드 소비자 기기를 겨냥했더라도, 공개 당시의 Java는 네트워크를 통한 코드 배포와 JVM을 새로운 소프트웨어 플랫폼으로 내세웠다.28

C#과 .NET도 같은 시기에 관리형 실행 환경을 하나의 개발 상품으로 묶었다. 다만 Java와 출발한 회사, 운영체제와의 관계, 표준화 경로가 달랐으므로 뒤에서 별도 절로 다룬다.

이 시기부터 언어를 선택한다는 말은 런타임, 표준 라이브러리, 보안 모델, 패키지와 배포 도구까지 고른다는 뜻에 더 가까워졌다. 언어 문법은 사용자가 보는 앞면이었고, 경제적 전환 비용은 그 뒤의 생태계에 쌓였다.

1995 이후: Java는 숙주를 두 번 갈아탔다

Java의 첫 사업 계획은 데이터센터가 아니라 셋톱박스와 가전제품을 향했다. 1991년 Sun의 Green 프로젝트가 상대하던 기계는 processor가 제각각이고 제품 수명은 길었으며, 현장에 깔린 뒤에는 함부로 고치기도 어려웠다. Oak는 작은 실행계와 이식 가능한 code를 그 환경에 넣으려던 시도였다. Java 언어 명세와 JVM 명세도 이 소비자 기기 프로젝트를 Java 이전의 역사로 기록한다.

The Java Story에서 Green 팀 관계자들은 Time Warner의 양방향 케이블 TV 사업에 Oak를 제안했다가 계약을 따내지 못했고, 프로젝트 존속도 불투명해졌던 시기를 회고한다. Bill Joy의 개입으로 팀 해체를 피했다는 대목도 나온다.
이 부분은 제품 명세나 판결문이 아니라 당사자 구술이다. 정확한 계약 연표보다는 실패한 시장을 떠나 다음 실행 표면을 찾던 팀의 기억으로 읽는 편이 맞다.29

Loading animated diagram...

웹에서 다시 쓸모를 찾다

1990년대 중반의 browser는 여러 운영체제에 이미 퍼지고 있었다. network에서 code를 받아 검증하고, 같은 class file을 서로 다른 기계에 전달한다는 Oak의 설계가 여기서는 쓸모가 있었다. Sun은 WebRunner, 뒤의 HotJava에서 움직이는 콘텐츠를 시연했고 Java applet 지원은 Netscape Navigator로 들어갔다. 셋톱박스 안에 넣으려던 작은 실행 플랫폼이 browser를 통해 PC에 배포된 셈이다.29

이때 자바 개발자들은 매우 영리한 방식을 썼는데, 표면 문법을 C와 C++ 개발자에게 익숙하게 설계하는 것이다. Gosling은 다큐멘터리에서 이를 "양의 탈을 쓴 늑대"라고 회고한다. 중괄호와 class 뒤에는 garbage collection, bytecode verification, thread, 동적 class loading과 JVM이 들어 있었다. 개발자는 익숙한 모양으로 시작했지만 메모리와 배포를 맡는 주체는 native executable에서 runtime 쪽으로 이동했다.

Loading animated diagram...

Write Once, Run Anywhere는 자바의 광고 문구였지만 실제로 지켜야 프로그래머에게 약속하고, 또 지켜야할 것이 있었다. class file 형식과 JVM instruction, 표준 API, compatibility test가 함께 유지돼야 했다. bytecode만 같고 library가 갈라지면 응용 프로그램은 다시 특정 운영체제에 묶인다.
Java의 이식성은 문법에서 저절로 나온 성질이 아니라 여러 vendor가 같은 실행 규약을 지키게 만든 플랫폼 운영의 결과였다.

그리고 이것은 과장해서 말하면 동아시아 여러 국가를 ‘Java의 식민지’로 만들 만큼 파급력이 컸다. 정부, 대기업, 금융기관, 대학, 자격증 시장과 SI 기업이 모두 Java를 기준으로 움직이기 시작하면서, 다른 언어를 선택하기 어려운 제도적 경로가 형성되었다는 뜻이다.

핵심 구호였던 Write Once, Run Anywhere는 프로그램을 특정 운영체제와 하드웨어에 직접 결박하지 않고 JVM 위에서 실행한다는 자바의 약속은 정보화를 빠르게 추진하던 국가에 특히 매력적이었다.

빠르게 성장하는 국가의 전산 환경은 처음부터 통일되어 있지 않다. 중앙정부에는 Unix 서버가 있고, 지방기관에는 Windows가 있으며, 은행에는 메인프레임이 남아 있고, 각 부처와 기업은 서로 다른 제조사의 데이터베이스와 미들웨어를 사용한다. 이런 상황에서 특정 운영체제에 종속되지 않는다는 Java의 약속은 단순한 개발 편의가 아니라 조달 전략이 되는 것이다. 실제로 당시 J2EE 진영은 Windows, Unix와 레거시 시스템을 포함한 여러 환경에 같은 애플리케이션을 배포할 수 있다는 점을 기업 시장의 장점으로 내세웠다.

후발 IT 국가가 필요로 하는 것은 반드시 가장 표현력이 뛰어난 언어가 아니다. 수천 명의 개발자를 교육할 수 있고, 서로 다른 회사가 같은 규격으로 납품할 수 있으며, 담당 업체가 바뀌어도 시스템을 유지할 수 있는 언어가 더 중요하다. Java는 C++보다 메모리 오류를 일으키기 어려웠고, 비교적 강한 정적 타입 체계를 제공했으며, 운영체제별 빌드 차이를 JVM 뒤로 숨겼다. 여기에 표준 API, 애플리케이션 서버, 데이터베이스 드라이버, 인증 체계, 트랜잭션과 메시징 기술이 묶이면서 Java는 하나의 언어를 넘어 기업 정보 시스템을 건설하는 산업 규격에 가까워졌다.

이때 Java의 장점은 개발자 개인의 생산성만으로 설명되지 않는다. 발주자는 “어느 천재 개발자가 이 시스템을 만들 수 있는가”보다 “어느 업체에 맡겨도 비슷한 방식으로 만들 수 있는가”를 중요하게 생각한다. 즉 평범한 사람들이 얼마나 구직시장에 밀어넣을 수 있는가를 중요시하는 것이다.
대학은 산업계가 요구하는 언어를 가르치고, 교육기관은 Java 개발자를 대량으로 배출하며, 기업은 Java 경력자를 기준으로 채용한다. 공공기관은 Java 경력을 입찰 조건에 넣고, SI 기업은 확보한 Java 인력 수를 수행 능력으로 제시한다. 기술 선택이 노동시장과 조달 제도에 들어가는 순간, 언어의 보급은 순수한 기술 경쟁이 아니라 자기강화적인 순환이 되는 것이다. (그리고 후발주자인 나는 이 시장에 끼지 못하고...)

한국은 이 현상을 가장 선명하게 보여주는 사례다. 한국 정부가 만든 전자정부 표준프레임워크 eGovFrame은 공공 정보화 사업을 위한 표준화된 개발 환경, 실행 환경, 운영 도구와 재사용 가능한 공통 컴포넌트를 제공한다. 서로 다른 부처가 각자 프레임워크를 만드는 대신 하나의 공통 아키텍처에 시스템을 맞추도록 한 것이다. 세계은행도 이를 개별 기관의 시스템을 사후에 연결하는 방식이 아니라, 먼저 중앙의 공통 아키텍처를 제공하고 각 시스템이 그 안으로 들어오게 한 사례로 설명한다.

이 선택은 당시에는 합리적이었다. 중복 개발을 줄이고, 공통 기능을 재사용하며, 중소 업체도 일정한 규격에 따라 공공 프로젝트에 참여할 수 있게 했다. 그러나 표준화가 오래 지속되면 표준은 권장이 아니라 사실상의 진입 조건이 된다. 공공사업 경험을 얻으려면 Java를 알아야 하고, Java 개발자가 많으니 다시 Java 사업이 발주되며, 기존 시스템이 Java로 작성되어 있으므로 다음 시스템도 Java와 연동하기 쉬운 방향으로 결정된다. 처음에는 호환성을 위한 선택이었지만, 시간이 지나면 생태계 자체가 선택을 재생산하게 되는 것이다.

(한국 대기업 네이버에 다니는 친구도 회사서 자바 쓴다고 하고, 내 주변 프로그래머 친구들도 다 자바 쓴다고 하는데 나는 여전히 자바에 익숙하지 않다.)

호환성 또한 시장의 경계가 되었다

위기감을 느낀 Microsoft는 Java를 라이선스하여 Visual J++와 Windows용 JVM을 배포했다. 그러나 Windows 전용 인터페이스를 추가하고 일부 표준 API를 누락하면서, Java 프로그램이 Windows에 종속되도록 유도했다. 미국 반독점 사건의 사실인정 역시 이러한 구현이 Java의 이식성을 훼손하고 개발자를 Windows 전용 환경으로 끌어들이려는 시도였다고 보았다.30

Sun이 별도로 제기한 Java 라이선스 소송은 2001년 Microsoft의 2천만 달러 지급과 Microsoft JVM의 단계적 종료로 마무리되었다.
반면 2004년 Microsoft가 Sun에 지급하기로 한 19억 5천만 달러는 Java 소송의 단일 배상액이 아니라, 반독점·특허 분쟁의 종결과 특허 사용 계약, 기술 협력을 묶은 별도의 포괄 합의였다.31

JVM이 여러 운영체제에서 동일한 API를 제공하면 응용 프로그램은 Windows를 우회할 수 있었다. Microsoft에는 운영체제가 가진 응용 프로그램 장벽이 낮아지는 문제였고, Sun에는 하나의 독립 플랫폼이 Windows 전용 구현으로 분열되는 문제였다.

applet은 사라지고 서버가 남다

Applet은 Java를 알린 전시장 구실을 했지만 설치와 보안, 시작 시간, browser 통합 문제를 오래 안고 갔다고 한다. 서버에서는 사정이 달랐다. 조직이 JVM 설치와 version을 통제할 수 있었고, Servlet은 HTTP request와 Java object 세계를 잇는 공통 API가 됐다. application server는 transaction, 보안, connection 관리와 배포를 맡게 되었으며 자연스레 Java가 돈을 버는 장소도 browser에서 기업 server로 옮겨 갔다.

J2EE의 확장은 곧 다른 종류의 비용을 만들었다. 특히 EJB 2.x 시기의 container 중심 programming은 deployment descriptor, remote interface와 lifecycle 규칙을 많이 요구했다. Rod Johnson의 2002년 책에 딸린 code는 Spring Framework로 발전했고, Gavin King은 Entity Bean에 대한 불만에서 Hibernate를 시작했다. 두 프로젝트가 Java EE를 밀어내고 빈 땅을 차지한 것은 아니다.

현장에서 채택된 IoC·POJO·ORM 접근이 기존 표준에 압력을 주었고, Hibernate의 설계는 뒤의 JPA에도 영향을 주었다.32

Loading animated diagram...

Spring과 Hibernate가 Java를 "구원했다"는 표현을 하는 사람들도 있으나, JVM을 새로 살려낸 구조대라기보다, 이미 큰 Java 시장에서 개발자가 겪어야할 어려움의 위치를 다른 곳으로 바꾸었다라고 보는게 정확할 것이며, 위원회 표준과 현장의 open source가 서로를 서로를 수정하기 시작했다.

느려진 릴리스와 다시 짧아진 릴리스

Java 5가 나온 2004년부터 Java 8이 나온 2014년까지를 통째로 "잃어버린 10년"이라 부르면 Java 6과 7, HotSpot과 library의 변화를 지우게 된다. 다만 언어 표면의 큰 변화가 늦었고 주요 release 간격이 길어진 시기였다는 평가는 가능하다. Sun의 경영난, 2010년 Oracle 인수와 JCP의 느린 합의 구조가 겹쳤다.

Java 8은 lambda expression과 Stream API를 넣었다. 2017년에는 시간 기준 release가 제안됐고 JDK 10부터 6개월 주기가 적용됐다. Java 21에서 정식 기능이 된 virtual thread는 기존 thread-per-request 코드를 크게 바꾸지 않으면서 blocking I/O 동시성을 더 싸게 다루려는 기능이다.
CPU 연산을 공짜로 만들거나 모든 server를 빠르게 하는 장치라는 지적도 있으나 이 흐름은 OpenJDK 개발자와 여러 JVM vendor, framework와 tool 생태계가 엮인 산업계 자체의 요구 변경에 따른 환경의 변화로 보는 것이 정확할 것이다.33

Java의 최초 계획은 거의 남지 않았다. 셋톱박스 사업은 실패했고 applet의 시대도 끝났다. 그 사업을 위해 만든 이식성, 관리형 메모리, 동적 loading과 표준 실행 규약은 server에서 더 오래 살았다. Java는 숙주를 갈아탈 때마다 최초 목적보다 JVM과 library를 더 오래 가져갔다.

1995 이후: JavaScript는 매체 자체에 행동을 붙였다

이름은 Java를 카피하였지만, JavaScript는 JVM과 경쟁하는 작은 Java가 아니었다. Netscape가 원한 것은 HTML 작성자가 페이지와 browser object에 곧바로 행동을 붙일 scripting language였다. Java applet이 별도 compile한 프로그램을 페이지 안에 삽입했다면, JavaScript는 문서의 button과 form, window state를 그 자리에서 조작했다. 초기 역사를 정리한 Wirfs-Brock과 Eich는 이 역할을 Java의 "sidekick scripting language"라고 표현한다 34

Loading animated diagram...

작은 입력 검증 하나를 위해 class와 applet 배포 절차를 거칠 필요가 없었다. source를 페이지에 붙이면 이미 설치된 browser가 실행했다. 이 작업 단위의 차이가 컸다. 사람들은 자바 스크립트의 문법이 아무리 구리다라고 욕해도 사용자는 별도 runtime을 설치하지 않았고, 간편했기에 웹 문서가 늘수록 JavaScript가 실행될 자리도 함께 늘었다. JavaScript의 예시는 내 전체 글을 가장 강력하게 보충해주는 예시일 것이다.

초기의 JavaScript는 VBA나 HyperTalk처럼 host application 안의 object를 연결하는 언어에 가까웠으나, browser가 세계적인 배포 표면이 되자 DOM 조작에서 network와 복잡한 UI로, 다시 Node.js의 server와 desktop tool로 영역이 넓어졌다.
즉 배포 플랫폼이 커짐에 따라 시장이 넓어짐에 따라서 사용처가 넓어진 것이다.
2012년 이후 TypeScript가 이 실행 표면을 그대로 둔 채 대형 codebase용 type과 language service를 덧붙일 수 있었던 것도 먼저 깔린 JavaScript 시장 덕분이다.

2000-2001: C#과 .NET. Windows 위에 관리형 개발 표면

C# 최초 ECMA 표준은 첫 광범위 배포판이 2000년 .NET Framework 계획의 일부였다고 기록한다. 같은 시기에 C# 명세와 CLI 명세가 별도 작업반에서 표준화되었다. C#은 .NET의 대표 언어로 출발했지만, 표준 문서상 모든 C# 구현이 Microsoft의 CLI만 써야 하는 것은 아니었다.35

Java의 이식 가능한 관리형 플랫폼은 Windows 중심 개발 생태계에도 경쟁 압력을 주었다. 그러나 C#을 Java 대응책 하나로만 읽으면 생산성과 type safety, Windows API 통합, server product를 위한 관리형 실행 환경이라는 Microsoft 내부의 요구가 빠진다. 경쟁은 계기 가운데 하나였고, 언어와 runtime을 한 제품으로 묶을 이유는 그보다 넓었다.

Loading animated diagram...

개발자가 .NET용 프로그램을 쉽게 만들수록 Windows와 Microsoft server product의 보완재도 늘었다. Visual Studio, base class library, ASP.NET과 deployment tool은 C# 문법 바깥에서 채택 비용을 낮췄다. 처음에는 Windows가 C#에 사용자를 주었고, 이후 .NET이 open source와 cross-platform으로 이동하면서 C# 생태계가 Windows 밖에서 다시 사용자를 데려오는 방향도 생겼다.

여기서도 언어의 시장과 runtime의 시장을 떼기 어려울 것이다. C# code가 늘면 CLR과 .NET API의 가치가 올라가고, .NET library가 늘면 다음 개발자가 C#을 고를 이유가 생긴다.
Java와 마찬가지로 언어, 실행 규약, library와 도구가 서로의 보완재가 된 경우다.

2004-2006: Ruby는 Rails를 만나 뒤늦게 세계로 나가다

Ruby 자체는 이 시기에 태어난 언어가 아니다. Matz는 1993년에 구현을 시작했고 1995년 12월 일본의 국내 newsgroup에 Ruby 0.95를 공개했다. Java와 같은 해에 공개됐지만 세계적인 확산 속도는 전혀 달랐다. Java에는 Sun, Netscape와 JVM이라는 배포망이 있었고, Ruby에는 일본어권 mailing list와 작은 사용자 공동체가 먼저 있었다.36

이것이 바로 언어의 시장성은 언어의 완성도만으로 설명되지 않는다는 점을 잘 설명해준다. 프로그래밍 언어는 아무리 정교하게 설계되어 있어도 사람들이 그것을 발견하고, 배우고, 실제 업무에 투입할 경로가 없으면 지역 공동체 안에 오래 머물 수 있다. 어느 시기에 태어났는지, 어느 언어권에서 처음 성장했는지, 어떤 기업이 배포를 지원했는지, 그리고 당시 시장이 어떤 종류의 소프트웨어를 요구했는지가 확산 속도에 직접 영향을 준다.

Java는 기업과 브라우저, 운영체제를 통해 위에서 아래로 보급됐다. 반면 Ruby는 오랫동안 언어 자체의 매력에 의존해야 했다. 객체지향의 일관성, 블록과 반복자, 메타프로그래밍과 간결한 문법은 이미 존재했지만, 이것만으로는 영어권 개발자에게 Ruby를 배워야 할 긴급한 이유를 만들지 못했다. 언어가 제공하는 가능성과 시장이 요구하는 구체적인 결과 사이에 번역 계층이 빠져 있었던 것이다.

전환점은 2004년 공개된 Ruby on Rails였다. Rails는 Ruby 문법을 소개하는 데서 그치지 않고 데이터베이스를 사용하는 웹 애플리케이션을 빠르게 만드는 전체 경로를 내놓았다. Active Record, routing, template, migration과 개발 서버가 한 framework 안에 들어갔고, convention over configuration은 선택할 설정의 수를 줄였다. 2004년 12월의 Rails 기록에는 첫 공개 뒤 다섯 달 동안 1만 회 이상 내려받았다는 수치가 남아 있다.37

Loading animated diagram...

Rails가 바꾼 것은 Ruby의 실행 속도보다 제작 경로였다. “이 언어로 무엇을 만들 것인가”에 분명한 답이 붙었고, 언어보다 늦게 등장한 framework가 언어의 시장을 다시 만들었다.

시기도 맞았다. 웹은 더 이상 대기업만 구축할 수 있는 정보 시스템이 아니었고, 작은 팀이 짧은 시간 안에 서비스를 만들어 시장을 시험하는 공간이 되고 있었다. 이들에게 중요한 것은 언어의 이론적 완결성이나 최대 실행 성능보다, 아이디어를 데이터 모델과 화면으로 얼마나 빨리 바꿀 수 있느냐는데, Rails는 Ruby의 유연한 객체 모델과 메타프로그래밍을 이용해 반복적인 설정과 연결 코드를 프레임워크 안으로 숨길 수 있었다. Ruby가 이전부터 가지고 있던 특성이 웹 스타트업이라는 구체적인 시장을 만난 순간이었다.

2006-2015: Rust는 C와 C++ 사이에 안전한 경계를 만들려 하다.

Rust는 2006년 무렵 Graydon Hoare의 개인 프로젝트에서 출발해 Mozilla의 지원과 Servo 브라우저 엔진 연구를 거쳤고, 2015년 5월 1.0을 발표했다. 1.0 발표문이 내세운 조합은 low-level control, 메모리 안전, 그리고 garbage collector가 없는 실행 모델이었다. C 라이브러리를 대체할 수 있는 시스템 언어를 지향하면서도 C ABI와 native toolchain을 버리라고 요구하지 않았다. 안전성의 대가를 ownership과 borrow checking, 컴파일 시간 쪽으로 옮겼다.38

초기의 Rust에는 언어 변화가 잦아 실제 프로그램을 오래 유지하기 어렵다는 문제가 있었다. 그래서 1.0은 기능 목록보다 안정성 약속이 중요했다. Cargo와 crates.io, 6주 release train이 함께 자리 잡으면서 compiler만 배포하는 언어에서 package와 documentation까지 묶인 개발 환경으로 넘어갔다.

Rust는 별도의 운영체제 시장을 먼저 만들지 않았지만, 플랫폼 시장의 새로운 요구의 형태를 만든 것이다. 즉 기존의 인프라가 가지는 한계성을 보완하는 것이다.
Firefox와 Servo에서 구현 기술을 시험한 뒤 C와 C++가 있던 시스템 경계로 들어갔다. 이후 Android, Windows, Linux가 새 구성 요소와 driver부터 Rust를 받아들인 과정은 뒤의 메모리 안전 절에서 다시 다룬다.

2007-2012: Go는 대형 서버 개발의 공정을 줄이려 하다.

Go의 설계는 2007년 Google에서 시작됐고 2009년 11월 오픈소스로 공개됐으며, 2012년 3월 Go 1에 도달했다. Rob Pike의 회고에는 당시 문제가 꽤 구체적으로 적혀 있다. 수천 명의 개발자가 수천만 줄의 C++, Java, Python 코드를 다루고 있었고, build cluster를 써도 컴파일에 분이나 시간이 걸렸다. multicore CPU와 network service는 늘었는데 코드와 dependency를 관리하는 방식은 그 규모를 따라가지 못했다.39

Go가 줄이려 한 것은 실행 시간만이 아니었으며, 빠른 compiler, 명시적인 package dependency, 단일 formatter인 gofmt, 문서 도구와 test 명령을 언어 배포판에 함께 넣었다. goroutine과 channel은 network service의 동시성을 표현했고, garbage collection은 서버 코드의 자원 관리 일부를 runtime으로 옮겼다. 화려한 타입 기능을 늘리기보다 팀 전체가 같은 도구와 관습을 쓰게 하는 쪽을 택했다.

Loading animated diagram...

Go 1의 호환성 약속이 생기자 외부 조직도 장기 코드를 맡길 수 있게 됐다. 뒤에는 Docker와 Kubernetes 같은 cloud-native 도구가 Go로 작성되며 새로운 유통망이 생겼고, 그리고 선풍적인 열풍을 일으킨 TikTok의 ByteDance 같은 대형 서비스 회사는 내부 RPC와 network framework까지 Go 생태계에 보탰다. Google의 문제를 풀던 언어가 cloud 운영 도구의 공용 언어로 옮겨 간 셈이다.39

2010년대: 새 플랫폼을 만들기보다 기존 플랫폼에 올라타다

2010년대에는 새로운 runtime을 세우는 대신 이미 퍼진 실행 환경과 library를 유지한 채 저작 경험을 바꾼 언어도 여럿 나왔다. 기존 사용자를 데려오는 데 이쪽이 훨씬 쌌다.

새 플랫폼 하나를 세우려면 compiler만으로는 부족하다. runtime과 표준 library, debugger, package repository, IDE와 build system, native library를 부르는 FFI, 배포 형식과 보안 update 정책까지 마련해야 한다. 사실 이미 산업 표준이 존재하는 이상 그 전부를 바꾸는 것은 거의 불가능하다.
기존 프로그래머의 취직 자리도 있지만, 모든 인프라를 전부 갈아엎는다는 것이 얼마나 많은 비용이 들지 아무도 예측할 수 없기때문이다.
그뿐만 아니라 단순 언어를 만든다고 한다면 그 뒤에는 문서와 교육, 채용 시장, 몇 년 동안 깨지지 않을 호환성(compatibility) 약속까지 병행되야한다.
언어 구현은 시작일 뿐이고, 실제 비용은 사람들이 안심하고 프로그램을 맡길 때까지 계속해서 호환성을 유지해야한다.

그래서 최근 언어는 기존 플랫폼을 대체품으로 돌리기보다 그 위에 올라가는 경우가 많다.
새 언어가 더 나은 type system이나 concurrency model을 제공하더라도, 아래쪽에서는 이미 검증된 runtime·OS API·browser·mobile SDK를 쓴다. 기존 code와 package를 곧바로 호출할 수 있어야 조직도 일부 module부터 바꿔 볼 수 있다. 전면 이주를 요구하는 언어는 첫 프로그램을 실행하기도 전에 플랫폼 구축 비용부터 떠안는다.

그런 의미에서 TypeScript가 좋은 예이다. TypeScript는 2012년 10월 공개됐고 2014년 1.0에 도달했다. JavaScript 코드를 다른 runtime으로 옮기지 않고, 정적 type과 language service를 개발 단계에 추가한 뒤 다시 표준 JavaScript로 내보냈다. 브라우저와 Node.js, npm package를 그대로 이용했으므로 도입자는 실행 플랫폼을 교체할 필요가 없었다. DefinitelyTyped의 declaration file은 기존 JavaScript library를 버리지 않고 type 정보를 덧씌우는 공동체 장치가 됐다.40

Kotlin도 비슷한 경로를 택했다. JetBrains가 2010년에 시작한 프로젝트는 2016년 1.0을 발표했고, JVM bytecode와 Java 상호운용성을 전면에 두었다. 기존 Java codebase를 한 번에 옮기지 않아도 됐고 IntelliJ의 refactoring과 build 도구를 이어 쓸 수 있었다. 2019년 Google이 Android 개발을 Kotlin-first로 전환하면서 Android Studio, Jetpack과 교육 자료가 Kotlin을 우선하는 유통망이 되었다.41

Swift는 2014년 Apple이 공개했고 Xcode와 Apple SDK 안으로 들어갔다. Objective-C runtime과 framework를 단번에 없애기보다 상호운용하면서 새 문법, type safety와 memory model을 제공했다. TypeScript가 JavaScript를, Kotlin이 Java/JVM을 이어받았듯 Swift도 이미 설치된 Apple 개발 경로 위에서 다음 언어가 되었다.42

세 언어는 설계도 용도도 다르지만은 그러나 채택 비용을 줄인 방법에는 공통점이 있다. 새 언어를 사용하려고 기존 platform API와 library, 사용자 시장을 포기하지 않아도 됐다는 점이다. 그리고 성공적인 플랫폼 위에 동화되는 선택을 한 것이다. 2010년대의 언어 경쟁은 새 runtime끼리 빈 땅에서 벌인 경주보다, 이미 성공한 플랫폼의 저작 도구를 누가 더 잘 고치느냐에 가까운 경우가 많았다.

Go와 Rust도 정도의 차이는 있지만 이 경제에서 크게 벗어나지 않는다. Go는 compiler와 runtime, 표준 도구를 직접 묶었으나 배포 장소는 기존 운영체제와 cloud였다. Rust는 Cargo와 자체 생태계를 만들면서도 LLVM, native linker와 C ABI를 통해 기존 system software 안으로 들어갔다.
최근 언어가 아무것도 새로 만들지 않는다는 뜻은 아니지만, 대체로 새로 만들어야 할 층을 줄이고, 이미 비싸게 깔린 실행 표면을 재사용한다는 쪽이 기조일 것이다.

기존 플랫폼에 메모리 안전을 끼워 넣다

앞의 Rust 연대기에서는 메모리 안전을 기존 시스템 안으로 들여보내는 채택 경로를 짧게 살폈다. 여기서는 그 경로가 정책 문서와 Windows, Android, Linux의 실제 전환에서 어떤 모양을 띠었는지 조금 더 내려가 보려고 한다. C++의 다큐멘터리부터 수많은 언어 개발자, 그리고 권위있는 앤더스 하일스버그(Anders Hejlsberg)까지 주시하는 슈퍼스타 Rust에 대해서는 충분히 그럴 가치가 있다고 생각한다.

언어 선택은 오랫동안 개발 조직의 기술 취향이나 생산성 문제로 다뤄졌다. 메모리 안전 문제가 운영체제, 브라우저, 통신 장비의 반복적인 취약점으로 이어지고 점점 IT가 현실 산업과 연동됨에 따라 중요 정보들을 유출하는 보안이 중요하게되었다.

백악관 보고서가 언어를 언급한 이유

미국 백악관 국가 사이버국장실(ONCD)은 2024년 Back to the Building Blocks라는 기술 보고서를 냈다. 보고서는 C와 C++처럼 메모리 안전 특성이 없고 중요 시스템에 널리 퍼진 언어를 직접 거론하며, 새 제품에서는 메모리 안전 언어를 초기 아키텍처 결정으로 고려하라고 권고한다. 업계 자료에서 CVE가 부여된 취약점의 최대 70%가 메모리 안전 문제였다는 수치도 인용했다.43

이 문서를 "백악관이 C와 C++를 금지했다"고 요약하면 잘못된 것이지만, 사실 크게 틀린 말은 아니다. 우주·임베디드처럼 도구 사슬, 결정적 실행 시간, 하드웨어 접근이 제약되는 분야도 따로 다루고 있으며, 오래된 코드를 한 번에 다시 쓰라고 하지도 않는다. 신규 코드는 안전한 언어로 시작하고, 기존 시스템은 위험도가 높은 함수와 라이브러리를 먼저 옮기는 혼합 전략을 제시한다. CISA·NSA·FBI와 여러 국가 기관이 함께 낸 2023년 문서도 제조사가 제품별 메모리 안전 이행 계획을 작성하고 공개하라고 권고한다.44

여기서 언어는 프로그래머 개인을 위한 도구를 넘어 조달과 공급망 정책의 단위가 된다. 정적 분석과 코드 리뷰로 매번 같은 종류의 결함을 찾는 비용이 크다면, 애초에 그 결함을 표현하기 어렵게 만든 언어로 기본값을 바꾸자는 계산이다. 실제로 모던 C++로 문제를 해결 할 수 있거나 충분히 주의를 기울이면 이러한 보안 버그를 막을 수 있다 하지만, 프로그래밍이 복잡해질 수록 사실 사람의 주의만으로 막을 수 없어지기 때문에 산업적으로 당연한 선택인 것이다.

전기톱에 안전장치 없이 안전하게 쓰면 모든 문제를 해결할 수 있다고 주장하는 것이 얼마나 잘못된 주장인가? 인간이 항상 안전하게 할 수 없다는 점을 인정하고, 안전장치를 다는 것은 산업적으로 맞는 판단인 것이다.

Rust: 가비지 컬렉터 없이 안전 경계를 옮기다

Rust 1.0은 2015년에 나왔다. 소유권과 빌림 검사를 통해 수명과 별칭 관계의 상당 부분을 컴파일 시간에 확인하며, 전통적인 가비지 컬렉터 없이 시스템 프로그래밍에 필요한 메모리 제어를 남겼다.38 운영체제처럼 예측 가능한 성능과 하드웨어 접근을 요구하는 영역에서 Rust가 자주 호출되는 이유도 이 조합에 있다.

그렇다고 Rust 코드 전체가 자동으로 안전해지는 것은 아니다. unsafe 블록, C ABI를 통한 FFI, 잘못 설계한 안전 래퍼, 논리 오류와 공급망 취약점은 남는다. Microsoft의 초기 도입 기록에서도 C/C++ 경계는 unsafe로 들어오므로 작은 래퍼 안에 가두고 별도 검토해야 한다고 적었다. 오래된 빌드 시스템이 Cargo와 맞지 않는 문제, 복잡한 C++ 추상화가 FFI 경계를 넘어 새어 나오는 문제도 실제로 존재했다.45

Rust가 많은 보안 문제를 해결하지만 여전히 사람의 사고가 프로그래밍에 개입해야한다. 다만, 검토해야 할 위험한 연산의 범위를 표시하고 줄이며, 나머지 코드에서는 컴파일러가 반복적인 검사를 맡는 쪽에 가깝다. 사람에게서 컴파일러로 옮길 수 있는 책임이 늘어난 것이다.

연대기에서 반복된 확산 유형

같은 언어도 시기와 숙주가 바뀌면 여러 칸을 오간다. Java는 새 runtime을 시장으로 만들었지만 browser와 server라는 배포 표면에도 올라탔다.
Rust는 package 생태계를 따로 만들면서 C ABI와 기존 운영체제의 부품 경계를 이용한다.

확산 유형

줄이려 한 비용

대표 사례

새로 생긴 비용

비싼 기계를 더 많은 사람이 쓰게 한다

기계어 작성과 programming 시간

Fortran과 IBM 704

compiler 품질, vendor별 이식성

플랫폼과 언어가 서로를 운반한다

운영체제 포팅과 응용 프로그램 이동

C와 Unix, GNU/Linux

backend, driver, 구현 정의 동작

기존 플랫폼의 천장을 높인다

새 runtime과 배포망을 처음부터 만드는 비용

C++, Python

ABI, FFI, 과거 호환성

새 runtime을 응용 프로그램 시장으로 만든다

OS와 CPU 차이를 응용 code가 직접 처리하는 비용

JVM, .NET

runtime 설치, 표준 API와 호환성 통제

이미 배포된 매체에 행동을 붙인다

사용자의 설치와 배포 절차

JavaScript와 browser

host API 종속, 매체 사업자의 통제

framework가 만들 결과물을 정해 준다

조립과 설정, 팀별 관습의 차이

Ruby on Rails

framework 수명과 관습에 대한 종속

반복되는 결함을 compiler가 막는다

검토와 보안 사고 수습 비용

Rust의 memory safety

학습, compile time, FFI와 unsafe 경계

표준 도구로 개발 공정을 줄인다

build·format·test·dependency 관리의 조직 마찰

Go

언어가 일부 선택을 미리 닫는 비용

기존 실행 표면을 유지한 채 저작 경험을 바꾼다

전면 migration과 생태계 재구축

TypeScript, Kotlin, Swift

아래 플랫폼의 제약을 함께 상속

확산 유형

비싼 기계를 더 많은 사람이 쓰게 한다

줄이려 한 비용

기계어 작성과 programming 시간

대표 사례

Fortran과 IBM 704

새로 생긴 비용

compiler 품질, vendor별 이식성

확산 유형

플랫폼과 언어가 서로를 운반한다

줄이려 한 비용

운영체제 포팅과 응용 프로그램 이동

대표 사례

C와 Unix, GNU/Linux

새로 생긴 비용

backend, driver, 구현 정의 동작

확산 유형

기존 플랫폼의 천장을 높인다

줄이려 한 비용

새 runtime과 배포망을 처음부터 만드는 비용

대표 사례

C++, Python

새로 생긴 비용

ABI, FFI, 과거 호환성

확산 유형

새 runtime을 응용 프로그램 시장으로 만든다

줄이려 한 비용

OS와 CPU 차이를 응용 code가 직접 처리하는 비용

대표 사례

JVM, .NET

새로 생긴 비용

runtime 설치, 표준 API와 호환성 통제

확산 유형

이미 배포된 매체에 행동을 붙인다

줄이려 한 비용

사용자의 설치와 배포 절차

대표 사례

JavaScript와 browser

새로 생긴 비용

host API 종속, 매체 사업자의 통제

확산 유형

framework가 만들 결과물을 정해 준다

줄이려 한 비용

조립과 설정, 팀별 관습의 차이

대표 사례

Ruby on Rails

새로 생긴 비용

framework 수명과 관습에 대한 종속

확산 유형

반복되는 결함을 compiler가 막는다

줄이려 한 비용

검토와 보안 사고 수습 비용

대표 사례

Rust의 memory safety

새로 생긴 비용

학습, compile time, FFI와 unsafe 경계

확산 유형

표준 도구로 개발 공정을 줄인다

줄이려 한 비용

build·format·test·dependency 관리의 조직 마찰

대표 사례

Go

새로 생긴 비용

언어가 일부 선택을 미리 닫는 비용

확산 유형

기존 실행 표면을 유지한 채 저작 경험을 바꾼다

줄이려 한 비용

전면 migration과 생태계 재구축

대표 사례

TypeScript, Kotlin, Swift

새로 생긴 비용

아래 플랫폼의 제약을 함께 상속

물론 한 언어가 하나의 유형에만 걸쳐있지 않다는 것에 주의해야한다.

필자가 단순하게 이해하기 쉽게 구분해둔 것이고 엄밀한 정의는 아니다.

C++는 기존 C toolchain을 빌렸지만, 뒤에는 게임 엔진과 데이터베이스, GUI 프레임워크가 새 사용자를 끌어오는 독자적인 생태계를 만들었다. 시작은 기존 플랫폼의 천장을 높이는 방식이었고, 시간이 지나자 C++ 자체가 다음 언어와 도구가 맞춰야 할 플랫폼이 되었다.

Java도 한 칸에 머물지 않았다. Oak의 가전제품 실행계, browser applet, 기업 server는 서로 다른 시장이었다. JVM이 가운데서 class file과 library 계약을 유지했기에 응용 프로그램 시장은 숙주를 바꾸면서도 이어질 수 있었다.

Rust는 컴파일러(compiler)가 memory safety의 일부를 집행하지만 실제 채택은 C/C++ system의 경계를 따라간다. Windows 전체를 다시 쓰지 않고 component와 driver, library부터 들어간 과정이 그 예다. 보안 사고 비용을 줄이는 유형과 기존 플랫폼에 올라타는 유형이 함께 나타난다.

Go는 compiler와 런타임(runtime), 포매터(formatter), 테스트(test)와 문서(documentation) 도구를 한 묶음으로 만들었다. 새 운영체제를 보급하는 대신 기존 OS와 cloud를 배포 표면으로 썼고, Docker와 Kubernetes, 대형 서비스 회사의 middleware가 다시 Go의 유통망을 넓혔다.

유형만으로 언어의 탄생 원인을 확정할 수는 없으며, 연구용 언어는 상업 플랫폼보다 정리 증명기나 AI 연구실, 논문 공동체를 숙주로 삼기도 한다. 정치, license, 표준화 위원회, 교육 제도와 우연한 실패등이 남을 것이다.

왜 나는 "저작 도구"라고 부르는가

이 표에서 매체는 일반적인 미디어라기보다는, 프로그램이 실행되고 사용자를 만나는 표면을 가리킨다.

실행 매체

대표적인 저작 도구

생산되는 것

IBM 메인프레임

Fortran compiler

과학 계산 프로그램

Unix

C compiler, shell, toolchain

운영체제와 시스템 도구

JVM

Java compiler와 API

이식 가능한 응용 프로그램

.NET

C#, CLR, SDK

관리형 데스크톱과 서버 프로그램

브라우저

HTML, CSS, JavaScript

웹 문서와 웹 애플리케이션

Apple OS

Swift, Xcode, SDK

App Store 응용 프로그램

실행 매체

IBM 메인프레임

대표적인 저작 도구

Fortran compiler

생산되는 것

과학 계산 프로그램

실행 매체

Unix

대표적인 저작 도구

C compiler, shell, toolchain

생산되는 것

운영체제와 시스템 도구

실행 매체

JVM

대표적인 저작 도구

Java compiler와 API

생산되는 것

이식 가능한 응용 프로그램

실행 매체

.NET

대표적인 저작 도구

C#, CLR, SDK

생산되는 것

관리형 데스크톱과 서버 프로그램

실행 매체

브라우저

대표적인 저작 도구

HTML, CSS, JavaScript

생산되는 것

웹 문서와 웹 애플리케이션

실행 매체

Apple OS

대표적인 저작 도구

Swift, Xcode, SDK

생산되는 것

App Store 응용 프로그램

언어는 트레이드 오프의 묶음이며, 인프라가 딸려오는 상품이다. API, 디버거, 패키지 형식, 문서, 배포 경로가 함께 있어야 실제로 무언가를 계속 생산할 수 있다. 그런 의미에서 언어는 표현 체계이면서 저작 도구 묶음의 중심이라고 할 수 있다.

(언젠가 완성하고 싶은 내 언어의 마스코트 Jyri)

결론: 언어는 혼자서 세계를 만들지 못한다

프로그래밍 언어의 역사를 문법과 패러다임의 진보로만 읽으면, 중요한 장면들이 설명되지 않는다. 왜 더 정교한 언어가 사라지고, 많은 비판을 받은 언어가 수십 년 동안 살아남았는가. 왜 비슷한 시기에 공개된 Java와 Ruby의 확산 속도는 달랐으며, 왜 Ruby는 Rails를 만난 뒤에야 세계적인 언어가 되었는가. 왜 C는 Unix와 함께 퍼졌고, JavaScript는 설계상의 결함을 수없이 지적받으면서도 웹의 유일한 기본 언어가 되었는가.

그 답은 언어 내부에만 있지 않다.

언어는 특정 시대에 가장 비싼 비용을 다른 곳으로 옮기는 장치였다.
Fortran은 기계어 작성 비용을 compiler에 넘겼고,
C는 기계별 운영체제 재작성 비용을 컴파일러 백엔드(compiler backend)와 좁은 이식 계층으로 모았다.
Java는 운영체제와 프로세서(processor)의 차이를 JVM에 맡겼으며, Python은 네이티브 라이브러리(native library)를 조립하는 비용을 더 높은 추상화로 옮겼다.
Rust는 반복되는 memory safety 검사를 프로그래머의 주의에서 컴파일러로 이전하려 한다.

어떤 비용도 완전히 사라지지는 않았다. 비용의 위치가 바뀌었을 뿐이다. Garbage collection은 memory 해제 부담을 줄이는 대신 런타임과 pause를 만들고, virtual machine은 배포의 차이를 숨기는 대신 런타임 호환성을 관리해야 한다. 강한 type system은 실행 전 오류를 줄이는 대신 더 많은 표현과 컴파일 타임을 요구한다. 프레임워크는 설정을 줄이지만 그 프레임워크의 관습과 수명에 종속시킨다.

따라서 언어 설계는 자유를 무한히 늘리는 작업이 아니다. 어떤 자유를 프로그래머에게 남기고, 어떤 선택을 컴파일러(compiler)와 런타임(runtime)이 대신하며, 무엇을 아예 표현하지 못하게 할지를 정하는 일이라고 할 수 있다.
모든 언어는 기계가 허용하는 가능성에서 일부만 잘라 만든 제약 공간이다.
언어의 세계관이란 결국 프로그래머의 한정된 인지 자원을 어디에 쓰게 할 것인가에 대한 설계자의 답이다.

그러나 기술적으로 훌륭한 비용 배분만으로도 성공은 보장되지 않는다. 언어에는 그것을 운반할 숙주가 필요하다.
C에는 Unix가 있었고, Java에는 JVM과 기업 정보 시스템이 있었으며, JavaScript에는 브라우저(browser)가 있었다.
C#에는 Windows와 Visual Studio가, Swift에는 Apple의 device와 App Store가, Kotlin에는 JVM과 Android가 있었다.
Ruby는 Rails를 만나고 나서야 웹 애플리케이션이라는 명확한 시장을 얻었다.

언어를 배우는 사람은 문법만 선택하지 않는다. 그 언어가 연결된 운영체제, 런타임(runtime), 라이브러리(library), 프레임워크(framework), IDE, package repository와 취업 시장까지 함께 선택한다. 조직도 마찬가지다. 한 언어를 도입한다는 것은 소스코드(source code)를 고르는 일이 아니라, 앞으로 사람을 어떻게 교육하고 채용하며, 어떤 업체에 유지보수를 맡기고, 기존 자산과 어떻게 연결할지를 결정하는 일이다.

이 단계에 이르면 호환성은 단순한 기술적 미덕이 아니라 시장의 필수재가 된다. 이미 존재하는 code와 지식, 도구와 인력을 끌어올 수 있는 언어는 새로운 언어보다 덜 아름다워도 유리하다.
반대로 기존 생태계를 모두 버리고 처음부터 새로운 런타임(runtime)과 라이브러리(library), 디버거(debugger)와 배포망을 만들어야 하는 언어는 첫 번째 유용한 프로그램을 내놓기 전부터 거대한 부채를 안고 시작한다.

그래서 언어의 역사를 읽을 때에는 세 질문을 끝까지 구분해야 한다.

첫째, 언어는 어떤 문제를 해결하기 위해 만들어졌는가.

둘째, 개발자와 조직은 왜 그 언어를 실제로 채택했는가.

셋째, 최초의 문제가 사라진 뒤에도 무엇이 그 언어를 계속 지배적으로 만들었는가.

세 질문의 답은 대개 서로 다르다.
Java는 셋톱박스에서 출발했지만 기업 server에서 제국을 만들었고, JavaScript는 작은 browser script로 시작했지만 web application의 실행 표면이 되었다. C는 Unix를 작성하기 위한 언어였지만, Unix와 GNU toolchain이 퍼지면서 수많은 system의 공통 기반이 되었다.
최초의 목적은 언어를 태어나게 하지만, 이후의 숙주와 보완재가 그 언어의 운명을 결정한다.

결국 성공한 프로그래밍 언어란 가장 완벽한 언어가 아니다. 충분히 유용한 문제를 해결하고, 기존 세계와 연결되며, 사람들이 실제 결과물을 만들 수 있는 짧은 경로를 제공하고, 오랫동안 깨지지 않을 것이라는 신뢰를 얻은 언어다. 문법의 아름다움은 중요하지만, 혼자서는 시장을 만들지 못한다.

언어는 세계를 작성하는 도구지만, 언어 혼자서 그 세계를 채우지는 못한다. 컴파일러(Compiler)와 런타임(runtime), 프레임워크(framework)와 라이브러리(library), 운영체제와 하드웨어(hardware), 교육과 채용, 기업과 정부, 그리고 때로는 우연한 실패가 함께 그 세계를 만든다.

그렇기에 언젠가 내가 새로운 언어를 완성한다면 문법만 고민해서는 안 될 것이다. 그 언어가 어떤 문제를 다른 언어보다 잘 해결하는지, 어떤 기존 생태계에 올라탈 수 있는지, 사용자가 첫날 무엇을 만들 수 있는지, 그리고 왜 다음 사람도 그 언어를 배워야 하는지를 함께 설계해야 한다.

언어를 만드는 일은 새로운 문법을 발명하는 일이 아니다.

사람들이 들어와 살 수 있는 세계와, 그 세계로 들어오는 길을 동시에 만드는 일이다.

그렇지만 그럼에도 사람들은 늘 새로움을 찾는다. 그리고 그 안에 자신이 겪은 어려움을 투사한다.

모든 언어는 어느 정도 창조자의 상처를 닮는다. 반복되는 memory corruption을 견디지 못한 사람은 안전한 소유권을 만들고, 끝없는 구성(configuration)에 지친 사람은 관례(convention)를 만들며, 거대한 클래스 계층(class hierarchy)에 질린 사람은 데이터(data)와 함수(function)를 다시 분리한다. 누군가에게는 자유였던 기능이 다른 누군가에게는 제거해야 할 위험이 되는 것이다.

그래서 새 언어의 문법을 읽는 일은 설계자의 불만을 무엇인지 찾는 추리 소설을 읽는 것과도 같다. 무엇을 짧게 쓸 수 있는지보다 무엇을 쓰지 못하게 했는지, 무엇을 컴파일러가 대신 검사하는지, 어떤 오류 메시지에 특별한 공을 들였는지를 보면 그 사람이 이전 세계에서 무엇과 싸웠는지 드러난다.

그러나 상처만으로는 도시를 세울 수 없다. 개인에게 완벽한 피난처가 다른 사람에게는 지나치게 좁은 방일 수 있고, 한 문제를 없애기 위해 세운 벽이 새로운 시장으로 들어가는 길까지 막을 수도 있다. 언어가 세계가 되려면 설계자의 고통을 넘어 타인의 작업과 만나야 한다.

나는 언젠가 내 언어를 완성하고 싶다. 아마 그 안에도 내가 C++, C#, Python과 TypeScript를 사용하며 겪었던 불만과 욕망이 남을 것이다. 무엇을 증명하고 싶었는지, 무엇을 지우고 싶었는지, 프로그래머가 어떤 책임에서 해방되기를 바랐는지가 문법과 컴파일러(compiler) 안에 새겨질 것이다.

하지만 그 언어가 내 세계에만 머문다면 그것은 정교한 자서전에 불과하겠지.

그리고 나 말고 누군가도 자신의 세계를 타인에게 보여주려고 안간힘을 쓸 것이다.

프로그래머는 자신만의 세계의 입법자이지만, 또 자신만의 세계를 남에게 보여주기위해 기를 쓰는 과시욕 강한 외로운 독재자이기도 하다.

통치할 백성이 없는 세계의 법은 아무런 권력을 갖지 못하니까.

각주

  1. IBM의 OS/2가 기술적으로 작동했음에도, 응용 프로그램 부족 때문에 OEM과 사용자를 끌어들이지 못했다고 정리했다고 말하는 미국 법무부의 재판 기록https://www.justice.gov/atr/us-v-microsoft-proposed-findings-fact
  2. Joel Spolsky. Strategy Letter V. 보완재 가격을 낮춰 주상품의 수요를 높이는 전략을 설명한다.
  3. Alan J. Perlis. Special Feature: Epigrams on Programming. ACM SIGPLAN Notices 17(9), 1982, pp. 7-13. “프로그래밍을 생각하는 방식에 영향을 주지 않는 언어는 알 가치가 없다”는 문장은 19번 경구다. 원문 열람본.
  4. Python Software Foundation. Extending Python with C or C++. C 확장 모듈이 새 내장 객체형을 구현하고 C 라이브러리 및 시스템 호출을 Python에 노출하는 구조를 설명한다. 이 인터페이스는 CPython 전용이며 다른 Python 구현체와 그대로 호환되지는 않는다.
  5. John W. Backus et al. The FORTRAN Automatic Coding System. Proceedings of the Western Joint Computer Conference, 1957, pp. 188-198. IBM 704용 Fortran 언어와 컴파일러를 발표한 동시대 보고서다.
  6. CODASYL. COBOL: Initial Specifications for a Common Business Oriented Language, 1960. 정부와 산업계 위원회가 작성한 COBOL 최초 명세다.
  7. John McCarthy. Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I. Communications of the ACM 3(4), 1960, pp. 184-195. DOI: 10.1145/367177.367199.
  8. J. W. Backus et al., Peter Naur 편집. Report on the Algorithmic Language ALGOL 60. Communications of the ACM 3(5), 1960, pp. 299-314; Revised Report, 1963.
  9. John G. Kemeny, Thomas E. Kurtz. BASIC: A Manual for BASIC, the Elementary Algebraic Language Designed for Use with the Dartmouth Time Sharing System. Dartmouth College Computation Center, 1964.
  10. Computer History Museum. The First Reference to Simula in Writing Is Made, 1962년의 첫 문서상 언급; Ole-Johan Dahl, Kristen Nygaard. SIMULA: An ALGOL-Based Simulation Language. Communications of the ACM 9(9), 1966, pp. 671-678. 이 논문은 Simula I 소개 자료이며, 완성된 Simula 67과 구분해서 읽어야 한다. 두 언어의 연결과 Simula 67에서 객체지향 개념이 정리된 과정은 Ole-Johan Dahl의 회고 The Birth of Object Orientation: The Simula Languages, 2001도 함께 참고했다.
  11. Alan C. Kay. The Early History of Smalltalk. ACM SIGPLAN Notices 28(3), HOPL-II, 1993, pp. 69-95. 1970년대 개발을 설계자가 뒤에 정리한 회고 논문이다.
  12. Robin Milner. A Theory of Type Polymorphism in Programming. Journal of Computer and System Sciences 17(3), 1978, pp. 348-375; Michael Gordon, Robin Milner, Christopher Wadsworth. Edinburgh LCF, 1979.
  13. Alain Colmerauer, Philippe Roussel. The Birth of Prolog. ACM SIGPLAN Notices 28(3), HOPL-II, 1993, pp. 37-52. 1971-1972년 자연언어 처리 프로젝트에서 Prolog가 형성된 과정을 기록한 회고다.
  14. Dennis M. Ritchie. The Evolution of the Unix Time-sharing System. 1979년 발표 후 AT&T Bell Laboratories Technical Journal 63(6), 1984에 재수록된 회고다. Multics 철수, PDP-7 초기 시스템, PDP-11 구매와 문서 처리 명분, file·process·shell·I/O 설계가 발전한 과정을 기록한다; Dennis M. Ritchie. The Space Travel game. 게임이 PDP-7 발견과 초기 Unix 작업으로 이어진 관계를 과장 없이 설명한 별도 회고다.
  15. Dennis M. Ritchie. The Development of the C Language. HOPL II, 1993. B의 threaded code와 PDP-11 byte addressing이 type과 native-code compiler를 요구한 과정, 1973년 Unix kernel의 C 재작성과 이후 이식성 작업을 설명한다; S. C. Johnson, D. M. Ritchie. Portability of C Programs and the UNIX System. Interdata 8/32 포팅에서 공통으로 유지한 code와 새로 작성한 machine-dependent 부분을 기록했다.
  16. Free Software Foundation. Overview of the GNU System; Richard Stallman. The GNU Project. GNU 발표와 개발 시점, Unix 호환성을 택한 이유, GCC·Bash·C library 등 사용자 공간 도구가 먼저 만들어진 과정을 확인했다.
  17. Linus Torvalds. Linux 0.01 Release Notes, 1991. 386용 초기 커널의 빌드 환경, GCC와 Bash 의존, 커널만으로는 사용할 수 있는 시스템이 되지 않는다는 설명이 들어 있다.
  18. Linus Torvalds. “How to pronounce ‘Linux’?”, comp.os.linux, 1992-04-23. Torvalds는 원래 Freax라는 이름을 생각했으나, Ari Lemmke가 FUNET FTP 서버의 디렉터리를 pub/OS/Linux로 만들었고 그 이름이 정착했다고 회고한다.
  19. Bjarne Stroustrup. A History of C++: 1979-1991. ACM SIGPLAN Notices 28(3), HOPL-II, 1993, pp. 271-297. C with Classes에서 표준화 직전 C++까지의 설계 제약을 다룬다.
  20. GNU Project. GCC Releases. g++ 1.15.3의 1987년 12월 공개를 포함한 초기 GNU C++ compiler 릴리스 기록이다; GCC. G++ and GCC.
  21. Bjarne Stroustrup. Evolving a language in and for the real world: C++ 1991-2006. HOPL III, 2007. 기존 linker와 object-file model을 유지한 선택, ABI 문제, STL과 표준화 이후의 변화를 다룬다.
  22. ISO/IEC JTC 1/SC 22/WG 21. Current Status and Published C++ Standards; Bjarne Stroustrup. An Interview with Bjarne Stroustrup. 1989년 미국 위원회, 1991년 ISO 공동 작업, 1998년 첫 표준과 STL 채택의 배경을 확인했다.
  23. Fabien Sanglard. Doom 3 Source Code Review: Interviews, 2012. John Carmack은 C와 Objective-C 경험에서 C++로 들어간 과정, id Tech 4의 C++ 전환, C에서 출발한 renderer와 여러 개발자가 subsystem을 나눠 맡으면서 생긴 조직상의 변화를 회고한다.
  24. id Software. DOOM Open Source Release, John Carmack의 1997년 12월 23일 공개문. Linux source만 공개한 이유, DOS판의 외부 sound library 문제와 code의 이식 가능성을 설명한다; John Carmack. Id's John Carmack on: NEXTSTEP, 1994. NeXTSTEP을 저작 환경으로 택한 이유와 game tool의 규모에 관한 당사자 기록이다; Apple. What Is Cocoa? - A Bit of History. NeXTSTEP의 운영체제 범위와 Application Kit·Sound Kit·Music Kit의 Objective-C class library 계보를 설명한다; Fabien Sanglard. Doom Engine Source Code Review, 2010. DoomEd·BSP builder의 Objective-C 구현, NeXTSTEP에서의 제작과 Watcom C를 통한 DOS build, WAD와 renderer의 연결을 source 기준으로 해설한다.
  25. Linux Kernel Documentation. Programming LanguageHOWTO do Linux kernel development. kernel이 GNU C11 dialect와 compiler extension을 사용하며 standard C library에 의존하지 않는 freestanding 환경이라는 점을 설명한다; Linked Lists in LinuxDevice Driver Design Patterns. intrusive list, container_of와 C 구조체 기반 driver 관습의 실제 형태를 보여 준다; Rust in Linux 6.9, RustGeneral Information. Linux 6.1부터 들어온 Rust support, no_std 제약과 C binding 위에 driver·file system용 abstraction을 구성하는 방식을 설명한다.
  26. 참고: Linus Torvalds, “Re: Compiling C++ kernel module + Makefile,” Linux Kernel Mailing List, 2004-01-20.
  27. Guido van Rossum, Jelke de Boer. Interactively Testing Remote Servers Using the Python Programming Language. CWI Quarterly 4(4), 1991, pp. 283-303. Python을 다룬 첫 논문이다; Guido van Rossum. Python Reference Manual, CWI Report CS-R9525, 1995; Python의 기원에 관한 회고, 1996.
  28. James Gosling, Henry McGilton. The Java Language Environment. Sun Microsystems가 1996년 5월에 낸 Java 백서의 Oracle 보존본이다; Oracle. Java Language Specification 서문; JVM Specification 서문. Oak의 embedded consumer electronics 기원과 JVM으로 이어진 계보를 확인했다.
  29. CultRepo. The Java Story | The Official Documentary. Oak와 셋톱박스(02:04), 웹과 Netscape(06:05), Gosling의 "wolf in sheep's clothing" 회고(12:27), servlet(17:46), J2EE와 오픈소스 framework(31:53)를 다룬 인터뷰 다큐멘터리다. 영상 제목에는 Official Documentary라고 적혀 있지만, 본문에서는 이를 Oracle의 단독 공식 기록으로 간주하지 않고 Oracle 문서와 법정·프로젝트 기록을 함께 확인했다.
  30. U.S. Department of Justice. U.S. v. Microsoft: Court's Findings of Fact, 1999. Microsoft의 Windows용 Java 구현과 개발 도구가 Sun의 크로스플랫폼 API를 약화하고 Windows 전용 인터페이스 사용을 유도한 과정을 다룬다; James Gosling. Statement in U.S. v. Microsoft, 1998.
  31. Microsoft. Microsoft Reaches Agreement to Settle Contract Dispute With Sun Microsystems, 2001. Java 기술 라이선스 분쟁의 2천만 달러 합의를 기록한다; Microsoft. Microsoft and Sun Microsystems Enter Broad Cooperation Agreement, 2004; Microsoft 2004 Annual Report. Financial Review, Note 17. 2004년 19억 5천만 달러 지급은 Java 계약 사건만의 배상액이 아니라 여러 분쟁과 특허·협력 계약을 묶은 금액이다.
  32. Rod Johnson. Spring Framework: The Origins of a Project and a Name, 2006; Spring Framework. Framework Overview: History; Hibernate ORM. A Short Guide to Hibernate 7: The early history of Hibernate and JPA. Spring이 초기 J2EE 명세의 복잡성에 대한 대응으로 형성된 과정과 Hibernate가 Entity Bean에 대한 불만에서 시작해 JPA에 영향을 준 계보를 확인했다.
  33. Oracle. What's New in JDK 8; Mark Reinhold. Accelerating the JDK release cadence, 2017; OpenJDK. JDK 10 General Availability, 2018; Ron Pressler, Alan Bateman. JEP 444: Virtual Threads, delivered in JDK 21. Lambda, 6개월 릴리스 주기와 virtual thread의 목표 및 범위를 확인했다.
  34. Allen Wirfs-Brock, Brendan Eich. JavaScript: The First 20 Years. Proceedings of the ACM on Programming Languages, 4(HOPL), 2020.
  35. Ecma International. ECMA-334, C# Language Specification, 1st edition, 2001; Microsoft. C# language specification: Introduction.
  36. Ruby. Official Ruby FAQ: What is the history of Ruby?; About Ruby. 1993년 언어의 시작, 1995년 일본 국내 newsgroup의 Ruby 0.95 공개, 언어 설계에 섞인 계보와 Rails가 국제적 확산에 미친 영향을 확인했다.
  37. Ruby on Rails. Rails celebrates more than 10,000 downloads, 2004년 12월 29일. 첫 버전 공개 후 약 다섯 달의 누적 수치다; Rails 0.9: Fast development, breakpoints, validations, 2004년 12월 16일. 초기 framework가 개발 server, reload, validation과 web application 제작 흐름을 함께 제공한 기록이다.
  38. The Rust Core Team. Announcing Rust 1.0, 2015; Five Years of Rust, 2020; Rust Standard Library. `unsafe` keyword; Microsoft Security Response Center. Why Rust for Safe Systems Programming, 2019.
  39. Rob Pike. Go at Google: Language Design in the Service of Software Engineering, SPLASH 2012; Rob Pike, Andrew Gerrand. The Path to Go 1, 2012; The Go Project. Go FAQ: Origins; Go for Cloud & Network Services. 2007년 설계 시작, 2009년 공개와 2012년 Go 1, Google의 대규모 codebase·build time·multicore·network service 문제, Docker와 Kubernetes를 통한 후속 유통 경로를 확인했다.
  40. Microsoft TypeScript Team. Ten Years of TypeScript, 2022; Jonathan Turner. Announcing TypeScript 1.0, 2014. 2012년 최초 공개, JavaScript 호환성 원칙, 대형 응용 프로그램을 위한 type과 language service, Visual Studio·npm·source 배포를 확인했다.
  41. Kotlin. FAQ. 2010년 프로젝트 시작과 2016년 1.0, JVM·Java 상호운용성을 설명한다; Android Developers. Android's Kotlin-first approach. 2019년부터 Android 도구·문서·Jetpack API를 Kotlin 중심으로 제공한 정책을 설명한다.
  42. Joe Groff. Evolving Swift On Apple Platforms After ABI Stability. Swift.org, 2019; Swift.org. Platform Support; Create Embedded Software with Swift; Swift 공식 홈페이지.
  43. White House Office of the National Cyber Director. Back to the Building Blocks: A Path Toward Secure and Measurable Software, 2024. 메모리 안전 언어, 하드웨어 보호와 형식 기법을 함께 다룬 정책 기술 보고서다.
  44. CISA, NSA, FBI et al. The Case for Memory Safe Roadmaps, 2023. 소프트웨어 제조사가 메모리 안전 이행 계획을 마련하고 공개해야 하는 이유와 절차를 제시한다.
  45. Microsoft Security Response Center. Using Rust in Windows, 2019. 저수준 Windows 구성 요소를 시험적으로 옮기면서 확인한 FFI, unsafe, 빌드 시스템과 조직적 검토 문제를 기록했다.
프로그래밍 언어는 플랫폼의 저작도구다.