모든 프로그래머가 알아야 할 Unicode, 문자 집합, 인코딩의 핵심을 역사적 배경부터 UTF-8, UTF-16, 코드 포인트, Content-Type까지 명확하게 설명하는 글입니다.
저는 뉴욕시에 사는 소프트웨어 개발자 Joel Spolsky입니다. 저에 대해 더 보기.
검색
종이책 형식으로 아카이브를 읽어보세요! 이 글들 중 많은 수가 네 권의 책으로 묶여 여러분이 좋아하는 서점에서 판매되고 있습니다. 욕조에서 사이트를 읽거나, 상사에게 집어던지기에도 아주 좋습니다.

한 단계 더 성장할 준비가 되셨나요? Stack Overflow Jobs는 개발자의 필요를 가장 먼저 생각하는 구직 사이트입니다. 직접 구직 활동을 주도하고 싶든, 아니면 고용주가 여러분을 발견하게 하고 싶든, 우리는 모든 개발자가 자신이 사랑하는 일을 찾도록 돕겠다는 사명을 가지고 있습니다.
일을 잘 해내는 똑똑한 프로그래머를 채용하고 싶으신가요? Stack Overflow Talent는 플랫폼에서 가장 신뢰받는 환경에서 개발자를 이해하고, 도달하고, 끌어들일 수 있도록 돕는 완전 맞춤형 소싱 솔루션입니다. 여러분의 채용 공고에 맞는 적합한 후보자를 찾아보세요. 더 알아보기.
제 본업에서 저는 프로그래머들이 배우고, 지식을 공유하고, 성장할 수 있는 가장 큰 온라인 커뮤니티인 Stack Overflow의 공동 창립자이자 CEO입니다. 매달 4천만 명이 넘는 현업 및 예비 프로그래머들이 Stack Overflow를 방문해 질문과 답변을 하고 더 나은 일자리를 찾습니다. Stack Overflow는 Stack Exchange network의 대표 사이트이기도 합니다. 이 네트워크는 요리부터 게임까지 온갖 주제를 다루는 160개 이상의 질문답변 사이트로 이루어져 있습니다. Quantcast에 따르면 Stack Overflow는 미국에서 30번째로 큰 웹 자산이며 전 세계 상위 100위 안에 듭니다.
또한 저는 세계에서 가장 영향력 있는 소규모 기술 회사 중 하나인 Fog Creek Software도 설립했습니다. 독립적인 비상장 기업으로서 우리는 세기가 바뀔 무렵부터 고객을 만족시켜 왔습니다. 우리는 훌륭한 소프트웨어를 만드는 방법에 대해 배운 것을 글로 공유하기도 하고, FogBugz, Trello, Gomix 같은 제품을 만들어 다른 이들이 훌륭한 기술을 만들도록 돕기도 합니다. 그 결과 Fog Creek가 개발자 세계에 미친 영향은 우리보다 천 배는 큰 회사들과 맞먹습니다.
2003년 10월 8일 2017년 11월 17일 작성자 Joel Spolsky
그 신비한 Content-Type 태그가 궁금했던 적이 있나요? HTML에 넣어야 한다는 건 아는데, 정확히 무엇이어야 하는지는 늘 잘 모르겠는 바로 그 태그 말입니다.
불가리아에 있는 친구들에게서 제목 줄이 “???? ?????? ??? ????”처럼 보이는 이메일을 받아본 적이 있나요?
문자 집합, 인코딩, Unicode, 그 모든 것들로 이루어진 이 신비한 세계에 대해 놀라울 정도로 많은 소프트웨어 개발자들이 실제로는 제대로 이해하고 있지 못하다는 사실을 알고 저는 실망했습니다. 몇 년 전 FogBUGZ의 베타 테스터 한 명이 일본어로 들어오는 이메일을 처리할 수 있는지 궁금해했습니다. 일본어? 일본어 이메일도 있단 말이야? 저는 전혀 몰랐습니다. 우리가 MIME 이메일 메시지를 파싱하는 데 사용하던 상용 ActiveX 컨트롤을 자세히 들여다보니, 문자 집합을 완전히 잘못 처리하고 있었습니다. 그래서 우리는 그 컨트롤이 잘못 수행한 변환을 되돌린 다음, 올바르게 다시 변환하기 위해 영웅적인 코드를 직접 작성해야 했습니다. 다른 상용 라이브러리도 살펴보았는데, 그것 역시 문자 코드 구현이 완전히 망가져 있었습니다. 저는 그 패키지의 개발자와 서신을 주고받았는데, 그는 어쩐지 “우리는 거기에 대해 할 수 있는 일이 없다”고 생각하는 듯했습니다. 많은 프로그래머들처럼, 그는 이 모든 문제가 어떻게든 그냥 사라지기만을 바랐습니다.
하지만 그럴 일은 없습니다. 인기 있는 웹 개발 도구 PHP가 문자 인코딩 문제를 거의 완전히 무시하고 있다는 사실을 발견했을 때, 즉 문자를 태연하게 8비트로 취급해서 제대로 된 국제화 웹 애플리케이션을 만드는 일을 거의 불가능하게 만든다는 걸 알았을 때, 저는 생각했습니다. 이제 정말 충분하다.
그래서 발표하겠습니다. 2003년에 프로그래머로 일하면서 문자, 문자 집합, 인코딩, Unicode의 기초를 모른다면, 그리고 제가 잡게 된다면, 저는 여러분에게 잠수함에서 6개월 동안 양파를 까게 하는 벌을 내릴 겁니다. 정말입니다.
그리고 한 가지 더:
그렇게 어려운 일도 아닙니다.
이 글에서 저는 정확히 무엇을 모든 현업 프로그래머가 알아야 하는지 설명하겠습니다. “일반 텍스트 = ascii = 문자는 8비트”라는 모든 이야기는 틀렸을 뿐만 아니라, 가망이 없을 정도로 틀렸습니다. 그런데도 여전히 그런 식으로 프로그래밍하고 있다면, 세균의 존재를 믿지 않는 의사보다 별로 나을 게 없습니다. 이 글을 다 읽기 전에는 코드를 한 줄도 더 쓰지 마세요.
시작하기 전에 경고 하나 하겠습니다. 국제화를 잘 아는 드문 분들 중 하나라면, 제 설명 전체가 약간 지나치게 단순화되어 있다고 느낄 것입니다. 저는 그저 모두가 무슨 일이 벌어지고 있는지 이해하고, 적어도 억양 부호가 없는 영어의 일부만이 아닌 다른 언어의 텍스트도 어느 정도는 다룰 희망이 있는 코드를 쓸 수 있도록 최소 기준선을 세우려는 것뿐입니다. 그리고 문자 처리는 국제적으로 동작하는 소프트웨어를 만드는 데 필요한 것의 아주 작은 일부일 뿐이라는 점도 말씀드려야겠습니다. 하지만 저는 한 번에 한 가지씩만 쓸 수 있으니 오늘은 문자 집합만 다루겠습니다.
역사적 관점
이 문제를 이해하는 가장 쉬운 방법은 시간 순서대로 보는 것입니다.
아마 저는 여기서 EBCDIC 같은 아주 오래된 문자 집합 이야기부터 할 거라고 생각하실지도 모릅니다. 하지만 그러지 않겠습니다. EBCDIC는 여러분의 삶과 관련이 없습니다. 그렇게까지 먼 과거로 갈 필요는 없습니다.
Unix가 만들어지고 K&R이 The C Programming Language를 쓰던 반쯤 옛날 시절로 돌아가 보면, 모든 것은 아주 단순했습니다. EBCDIC는 퇴장하는 중이었습니다. 중요했던 문자는 억양 부호가 없는 좋은 옛 영어 알파벳뿐이었고, 이를 위해 ASCII라는 코드가 있었습니다. 이 코드는 32에서 127 사이의 숫자로 모든 문자를 표현할 수 있었습니다. 공백은 32, 문자 “A”는 65 같은 식입니다. 이건 7비트에 편리하게 저장할 수 있었습니다. 당시 대부분의 컴퓨터는 8비트 바이트를 사용하고 있었기 때문에, 가능한 모든 ASCII 문자를 저장할 수 있었을 뿐 아니라 비트 하나가 남았습니다. 그리고 여러분이 사악하다면 그 비트를 자기만의 교활한 목적으로 쓸 수 있었습니다. WordStar의 멍청한 친구들은 단어의 마지막 글자를 표시하기 위해 상위 비트를 켜 두었고, 그 결과 WordStar는 영어 텍스트밖에 다루지 못하게 되었습니다. 32보다 작은 코드는 _출력 불가능한 문자_라고 불렸고 욕설용으로 쓰였습니다. 농담입니다. 실제로는 제어 문자로 쓰였습니다. 예를 들어 7은 컴퓨터가 삑 소리를 내게 했고, 12는 프린터에서 현재 종이를 휙 밀어내고 새 용지를 넣게 했습니다.
그리고 여러분이 영어 사용자라면 모든 것이 좋았습니다.
바이트에는 최대 8비트가 들어갈 여지가 있었기 때문에, 많은 사람들이 “이런, 128-255 코드를 우리 용도로 쓸 수 있겠네”라고 생각하기 시작했습니다. 문제는 아주 많은 사람들이 동시에 같은 생각을 했다는 것이고, 128에서 255 사이 공간에 무엇을 어디에 둘지에 대해 각자 다른 생각을 갖고 있었다는 점입니다. IBM-PC에는 나중에 OEM 문자 집합이라 불리게 된 것이 있었는데, 여기에는 유럽 언어용 억양 문자가 약간 들어 있었고 여러 가지 선 그리기 문자도 있었습니다. 수평선, 수직선, 오른쪽으로 조그만 삐죽이가 달린 수평선 같은 것들 말이죠. 이런 선 그리기 문자를 사용하면 화면에 멋진 상자와 선을 만들 수 있었고, 지금도 세탁소의 8088 컴퓨터에서 돌아가는 모습을 볼 수 있습니다. 실제로 사람들이 미국 밖에서 PC를 사기 시작하자마자 온갖 다른 OEM 문자 집합이 만들어졌고, 모두 상위 128개 문자를 자기 용도로 사용했습니다. 예를 들어 어떤 PC에서는 문자 코드 130이 é로 표시되었지만, 이스라엘에서 판매된 컴퓨터에서는 히브리 문자 Gimel (
)로 표시되었습니다. 그래서 미국인들이 이력서를 이스라엘로 보내면 r
sum
s처럼 도착하곤 했습니다. 러시아어 같은 경우에는 상위 128개 문자에 무엇을 할지에 대한 생각이 아주 많아서, 러시아어 문서조차도 서로 안정적으로 교환할 수 없었습니다.
결국 이 OEM 난장판은 ANSI 표준으로 어느 정도 정리되었습니다. ANSI 표준에서는 128 아래에서 무엇을 할지에 대해서는 모두가 합의했는데, 그건 거의 ASCII와 같았습니다. 하지만 128 이상 문자를 처리하는 방식은 사는 지역에 따라 매우 달랐습니다. 이런 서로 다른 시스템을 _코드 페이지_라고 불렀습니다. 예를 들어 이스라엘의 DOS는 862라는 코드 페이지를 사용했고, 그리스 사용자는 737을 사용했습니다. 128 아래에서는 같았지만, 재미있는 문자들이 있는 128 이상에서는 달랐습니다. 각국 버전의 MS-DOS에는 수십 개의 코드 페이지가 있었고, 영어부터 아이슬란드어까지 모든 것을 처리했습니다. 심지어 같은 컴퓨터에서 Esperanto와 Galician을 함께 다룰 수 있는 “다국어” 코드 페이지도 몇 개 있었습니다. 와우! 하지만 예를 들어 히브리어와 그리스어를 같은 컴퓨터에서 함께 쓰는 것은 완전히 불가능했습니다. 높은 숫자들에 대한 해석이 서로 다른 코드 페이지를 각각 필요로 했기 때문입니다. 모든 것을 비트맵 그래픽으로 직접 표시하는 사용자 정의 프로그램을 쓰지 않는 한 방법이 없었습니다.
한편 아시아에서는 더 미친 일이 벌어지고 있었습니다. 아시아 문자 체계에는 수천 개의 글자가 있기 때문에 8비트에는 절대 들어갈 수 없다는 사실을 반영해야 했기 때문입니다. 보통은 DBCS라는 지저분한 시스템으로 해결했는데, 이는 “double byte character set”의 약자입니다. 여기서는 어떤 글자는 1바이트에 저장되고 다른 글자는 2바이트가 필요했습니다. 문자열 안에서 앞으로 이동하는 건 쉬웠지만, 뒤로 이동하는 건 거의 불가능에 가까웠습니다. 프로그래머들은 앞으로 뒤로 움직일 때 s++와 s--를 쓰지 말고, 대신 Windows의 AnsiNext와 AnsiPrev 같은 함수를 호출하라는 권고를 받았습니다. 그런 함수들만이 이 난장판 전체를 처리하는 법을 알고 있었기 때문입니다.
하지만 여전히 대부분의 사람들은 바이트가 문자이고 문자는 8비트라고 가장했습니다. 그리고 문자열을 한 컴퓨터에서 다른 컴퓨터로 옮기지 않고, 두 개 이상의 언어를 사용하지 않는 한, 대충 항상 작동할 거라고 생각했습니다. 하지만 물론 인터넷이 등장하자 문자열을 한 컴퓨터에서 다른 컴퓨터로 옮기는 일이 매우 흔해졌고, 이 모든 혼란은 무너져 내렸습니다. 다행히도 Unicode가 이미 발명되어 있었습니다.
Unicode
Unicode는 지구상의 모든 그럴듯한 문자 체계를 하나의 문자 집합에 담아내려는 대담한 시도였습니다. 심지어 Klingon 같은 가상 문자 체계까지도요. 어떤 사람들은 Unicode가 단순히 16비트 코드라서 각 문자가 16비트를 차지하고, 따라서 가능한 문자가 65,536개라고 오해합니다. 사실 이건 맞지 않습니다. 이것이 Unicode에 대한 가장 흔한 신화이니, 그렇게 생각했다 해도 너무 자책하진 마세요.
사실 Unicode는 문자에 대해 완전히 다른 방식으로 생각합니다. 그리고 Unicode식 사고방식을 이해하지 못하면 아무것도 말이 되지 않습니다.
지금까지 우리는 문자가 디스크나 메모리에 저장할 수 있는 어떤 비트들에 대응한다고 가정했습니다.
A -> 0100 0001
Unicode에서 문자는 _코드 포인트_라고 불리는 것에 대응합니다. 이것도 여전히 이론적인 개념일 뿐입니다. 그 코드 포인트가 메모리나 디스크에서 어떻게 표현되는지는 전혀 다른 이야기입니다.
Unicode에서 문자 A는 플라톤적 이념입니다. 그냥 하늘에 둥둥 떠 있는 것이죠.
A
이 플라톤적 A는 B와 다르고, a와도 다르지만, A와 A 와 A와는 같습니다. Times New Roman 글꼴의 A가 Helvetica 글꼴의 A와는 같은 문자지만 소문자 “a”와는 다르다 는 생각은 별 논란이 없어 보이지만, 어떤 언어에서는 무엇이 문자 자체인지 를 따지는 것만으로도 논쟁이 벌어질 수 있습니다. 독일어 문자 ß는 진짜 문자일까요, 아니면 ss를 멋지게 적는 방법일 뿐일까요? 단어 끝에서 문자 모양이 바뀌면 그것은 다른 문자일까요? 히브리어는 그렇다고 하고, 아랍어는 아니라고 합니다. 어쨌든 Unicode 컨소시엄의 똑똑한 사람들은 지난 10년 정도 동안 이 문제를 해결해 왔고, 그 과정에는 상당히 정치적인 논쟁도 수반되었습니다. 하지만 여러분은 걱정할 필요가 없습니다. 그들은 이미 전부 해결해 두었습니다.
모든 알파벳의 모든 플라톤적 문자에는 Unicode 컨소시엄이 마법 같은 숫자를 부여했으며, 이것은 이렇게 씁니다: U+0639. 이 마법의 숫자를 _코드 포인트_라고 합니다. U+는 “Unicode”를 뜻하고, 숫자는 16진수입니다. U+0639는 아랍 문자 Ain입니다. 영어 문자 A는 U+0041입니다. Windows 2000/XP에서는 charmap 유틸리티를 사용하거나 Unicode 웹 사이트를 방문하면 이것들을 모두 확인할 수 있습니다.
Unicode가 정의할 수 있는 문자 수에는 실질적인 제한이 없으며, 실제로 그들은 65,536개를 넘어섰습니다. 따라서 모든 unicode 문자가 정말로 2바이트에 들어가는 것은 아닙니다. 하지만 애초에 그건 신화였습니다.
좋습니다. 그러면 이런 문자열이 있다고 해 봅시다.
Hello
이것은 Unicode에서 다음 다섯 개의 코드 포인트에 대응합니다.
U+0048 U+0065 U+006C U+006C U+006F.
그냥 코드 포인트들의 묶음입니다. 사실상 숫자들이죠. 아직 우리는 이것을 메모리에 어떻게 저장할지, 이메일 메시지에서 어떻게 표현할지에 대해서는 아무 말도 하지 않았습니다.
인코딩
바로 여기서 _인코딩_이 등장합니다.
Unicode 인코딩에 대한 가장 초기의 아이디어, 즉 2바이트 신화를 낳은 아이디어는 이거였습니다. 저 숫자들을 그냥 각각 2바이트에 저장하면 되잖아. 그래서 Hello는 이렇게 됩니다.
00 48 00 65 00 6C 00 6C 00 6F
맞죠? 그렇게 빠르진 않습니다! 이렇게일 수도 있지 않을까요?
48 00 65 00 6C 00 6C 00 6F 00 ?
엄밀히 말하면 그렇습니다. 실제로 초기 구현자들은 Unicode 코드 포인트를 자신들의 CPU에서 더 빠른 방식에 따라 빅엔디안이든 리틀엔디안이든 저장하고 싶어 했습니다. 그리하여 저녁이 되고 아침이 되자, Unicode를 저장하는 방법이 이미 두 가지 가 되어 있었습니다. 그래서 사람들은 모든 Unicode 문자열의 시작 부분에 FE FF를 저장하는 기묘한 관례를 만들어야 했습니다. 이것을 Unicode Byte Order Mark라고 부릅니다. 그리고 만약 상위 바이트와 하위 바이트를 뒤바꾸면 그것은 FF FE처럼 보일 것이고, 여러분의 문자열을 읽는 사람은 매 두 바이트마다 순서를 바꿔야 한다는 걸 알 수 있습니다. 후우. 야생의 모든 Unicode 문자열이 앞부분에 바이트 순서 표식을 갖고 있는 것은 아닙니다.

한동안은 그 정도면 충분할 것 같았습니다. 하지만 프로그래머들이 불평하기 시작했습니다. “저 0들 좀 봐!”라고 그들은 말했습니다. 그들은 미국인이었고, U+00FF보다 큰 코드 포인트를 거의 쓰지 않는 영어 텍스트를 보고 있었기 때문입니다. 또 그들은 California의 자유주의 히피들이어서 절약(sneer) 하기를 원했습니다. Texas 사람들이었다면 바이트를 두 배나 먹어치우는 것도 개의치 않았을 겁니다. 하지만 그 California의 약골들은 문자열 저장 공간이 두 배 가 된다는 생각을 참을 수 없었습니다. 게다가 이미 밖에는 각종 ANSI와 DBCS 문자 집합을 사용하는 문서들이 잔뜩 있었는데, 그걸 누가 다 변환한단 말입니까? 내가? 이런 이유만으로도 대부분의 사람들은 몇 년 동안 Unicode를 무시하기로 했고, 그 사이 상황은 더 나빠졌습니다.
그래서 UTF-8이라는 기막힌 개념이 발명되었습니다. UTF-8은 Unicode 코드 포인트 문자열, 즉 그 마법 같은 U+ 숫자들을 8비트 바이트를 사용해 메모리에 저장하는 또 다른 시스템이었습니다. UTF-8에서는 0-127의 모든 코드 포인트가 한 바이트에 저장됩니다. 오직 128 이상 코드 포인트만 2, 3, 사실상 최대 6바이트까지 사용해 저장됩니다.

이 방식의 멋진 부수 효과는 영어 텍스트가 UTF-8에서도 ASCII와 정확히 똑같이 보인다 는 점입니다. 그래서 미국인들은 뭔가 잘못됐다는 사실조차 눈치채지 못합니다. 고생은 나머지 세계가 다 해야 하죠. 구체적으로 Hello는 U+0048 U+0065 U+006C U+006C U+006F였는데, 이것은 48 65 6C 6C 6F로 저장됩니다. 보십시오! ASCII, ANSI, 그리고 지구상의 모든 OEM 문자 집합에서 저장되던 방식과 같습니다. 이제 여러분이 무모하게도 억양 문자나 그리스 문자나 Klingon 문자를 쓰기로 한다면, 하나의 코드 포인트를 저장하는 데 여러 바이트를 써야 합니다. 하지만 미국인들은 영원히 눈치채지 못할 겁니다. (UTF-8은 또 하나 좋은 성질이 있는데, 문자열 끝을 나타내기 위해 단일 0 바이트를 사용하는 무지한 옛 문자열 처리 코드가 문자열을 잘라먹지 않는다는 점입니다.)
지금까지 저는 Unicode를 인코딩하는 세 가지 방법을 말씀드렸습니다. 전통적인 2바이트 저장 방식은 UCS-2(2바이트니까) 또는 UTF-16(16비트니까)라고 부르며, 여전히 그것이 빅엔디안 UCS-2인지 리틀엔디안 UCS-2인지 알아야 합니다. 그리고 영어 텍스트와 ASCII밖에 세상에 없다고 믿는 멍청한 프로그램이라는 행복한 우연이 겹칠 경우에도 제법 점잖게 동작하는 멋진 성질을 가진 최신 UTF-8 표준도 있습니다.
사실 Unicode를 인코딩하는 방법은 이 외에도 많이 있습니다. UTF-7이라는 것도 있는데, UTF-8과 꽤 비슷하지만 상위 비트가 항상 0이 되도록 보장합니다. 그래서 7비트면 충분하고도 남는다, 고맙다 고 생각하는 어떤 엄격한 경찰국가 이메일 시스템을 통해 Unicode를 통과시켜야 하더라도 무사히 비집고 지나갈 수 있습니다. UCS-4도 있는데, 이건 각 코드 포인트를 4바이트에 저장합니다. 그래서 모든 코드 포인트를 같은 바이트 수로 저장할 수 있다는 좋은 점이 있지만, 세상에, Texas 사람들조차 그 정도로 메모리를 낭비 하지는 않을 겁니다.
그리고 이제 여러분이 사물을 Unicode 코드 포인트로 표현되는 플라톤적 이상 문자라는 관점에서 보고 있으니, 그 Unicode 코드 포인트들은 옛날식 인코딩 방식으로도 인코딩될 수 있습니다! 예를 들어 Hello의 Unicode 문자열(U+0048 U+0065 U+006C U+006C U+006F)을 ASCII나, 옛 OEM Greek Encoding이나, Hebrew ANSI Encoding이나, 지금까지 발명된 수백 가지 인코딩 중 하나로 인코딩할 수 있습니다. 단 한 가지 조건이 있는데: 어떤 글자들은 나타나지 않을 수도 있습니다! 표현하려는 Unicode 코드 포인트에 해당하는 문자가, 그걸 표현하려는 인코딩 안에 없다면, 보통은 작은 물음표 하나를 얻게 됩니다: ? 혹은 여러분이 정말 잘하면 네모 상자 하나를 얻습니다. 어느 쪽이 나왔나요? -> �
전통적인 인코딩은 수백 가지가 있고, 이들은 일부 코드 포인트만 올바르게 저장할 수 있으며 나머지는 전부 물음표로 바꿔버립니다. 영어 텍스트에서 인기 있는 인코딩으로는 Windows-1252(서유럽 언어용 Windows 9x 표준)와 ISO-8859-1, 즉 Latin-1(다른 서유럽 언어에도 유용함)이 있습니다. 하지만 이 인코딩들에 러시아어나 히브리어 문자를 저장하려 하면 물음표만 잔뜩 얻게 됩니다. UTF 7, 8, 16, 32는 어떤 코드 포인트든 올바르게 저장할 수 있다는 좋은 성질을 가지고 있습니다.
인코딩에 관한 가장 중요한 단 하나의 사실
제가 방금 설명한 모든 걸 완전히 잊어버리더라도, 제발 이것 하나만은 기억하세요. 어떤 인코딩을 사용하는지 모르는 문자열이라는 것은 말이 되지 않습니다. 이제 더는 “일반” 텍스트가 ASCII라고 모래 속에 머리를 박고 가장할 수 없습니다.
순수한 텍스트라는 것은 존재하지 않습니다.
메모리 안에 있든, 파일에 있든, 이메일 메시지 안에 있든, 문자열이 있다면 그것이 어떤 인코딩인지 알아야 합니다. 그렇지 않으면 그 문자열을 올바르게 해석할 수도, 사용자에게 제대로 표시할 수도 없습니다.
“내 웹사이트가 깨져 보여요” 혹은 “내가 억양 문자를 쓰면 그녀가 내 이메일을 못 읽어요” 같은 거의 모든 멍청한 문제는, 특정 문자열이 UTF-8로 인코딩된 것인지 ASCII인지 ISO 8859-1(Latin 1)인지 Windows 1252(서유럽)인지 알려주지 않으면 그것을 올바르게 표시할 수 없고, 심지어 어디서 끝나는지조차 알 수 없다는 단순한 사실을 이해하지 못한 순진한 프로그래머 한 명 때문에 발생합니다. 인코딩은 100개가 넘고, 코드 포인트 127을 넘는 순간 아무 보장도 없습니다.
문자열이 어떤 인코딩을 쓰는지에 대한 정보를 어떻게 보존할까요? 물론 표준적인 방법들이 있습니다. 이메일 메시지의 경우, 헤더에 다음과 같은 문자열이 있어야 합니다.
Content-Type: text/plain;charset="UTF-8"
웹 페이지의 경우 원래 생각은 웹 서버가 페이지 자체와 함께 비슷한 Content-Type http 헤더를 반환하는 것이었습니다. HTML 안이 아니라, HTML 페이지보다 먼저 전송되는 응답 헤더 중 하나로 말이죠.
이것은 문제를 일으킵니다. 수많은 사이트와 수백 개의 페이지를 가진 거대한 웹 서버가 있고, 거기에 여러 사람이 여러 언어로 기여했으며, 각자 자기 Microsoft FrontPage가 내키는 대로 생성한 인코딩을 사용했다고 해 봅시다. 웹 서버 자체는 각 파일이 어떤 인코딩으로 작성되었는지 실제로 알지 못할 테니, Content-Type 헤더를 보낼 수가 없습니다.
HTML 파일의 Content-Type을 HTML 파일 자체 안에, 어떤 특수 태그를 이용해 넣을 수 있다면 편리할 것입니다. 물론 이것은 순수주의자들을 미치게 만들었습니다. 어떤 인코딩인지 알기 전에는 HTML 파일을 읽을 수조차 없는데, 어떻게 그 안에 그 정보를 넣느냐는 거죠! 다행히도 일반적으로 사용되는 거의 모든 인코딩은 32에서 127 사이 문자에 대해서는 같은 동작을 하므로, 재미있는 문자들을 쓰기 시작하기 전까지는 HTML 페이지에서 적어도 여기까지는 항상 읽을 수 있습니다.
**<html>
<head>****<meta http-equiv="Content-Type"content="text/html; charset=utf-8">**
하지만 그 meta 태그는 정말로 <head> 구역에서 가장 첫 번째여야 합니다. 웹 브라우저가 이 태그를 보는 순간 페이지 파싱을 멈추고, 지정한 인코딩을 사용해 전체 페이지를 다시 해석하면서 처음부터 다시 시작할 것이기 때문입니다.
웹 브라우저는 http 헤더에도, meta 태그에도 Content-Type이 없으면 어떻게 할까요? Internet Explorer는 꽤 흥미로운 일을 합니다. 여러 언어의 여러 인코딩으로 쓰인 일반적인 텍스트에서 다양한 바이트가 얼마나 자주 나타나는지를 바탕으로, 어떤 언어와 어떤 인코딩이 사용되었는지 추측하려고 시도합니다. 옛날 8비트 코드 페이지들은 각국 문자를 128에서 255 사이의 서로 다른 영역에 배치하는 경향이 있었고, 인간의 각 언어는 글자 사용 빈도 히스토그램이 서로 다르기 때문에, 이 방법은 실제로 어느 정도 성공할 가능성이 있습니다. 정말 기이한 일이지만, 충분히 자주 작동하는 듯합니다. 그래서 Content-Type 헤더가 필요하다는 사실조차 몰랐던 순진한 웹 페이지 작성자들이 자신의 페이지를 웹 브라우저에서 보면 멀쩡해 보입니다. 그러다 어느 날 모국어의 문자 빈도 분포에 정확히 들어맞지 않는 무언가를 쓰는 순간, Internet Explorer는 그것을 한국어라고 판단하고 그렇게 표시해 버립니다. 제 생각엔 이것이야말로 “내보낼 때는 보수적으로, 받을 때는 관대하게” 하라는 Postel의 법칙이 솔직히 말해 좋은 공학 원칙이 아니라는 점을 보여줍니다. 어쨌든 불가리아어로 쓰였지만 한국어처럼 보이는(그것도 일관성 없는 한국어) 이 웹사이트의 불쌍한 독자는 어떻게 해야 할까요? 그는 View | Encoding 메뉴를 사용해 여러 인코딩을 이것저것 시도해 봅니다. 동유럽 언어용 인코딩만 해도 적어도 열두 개는 있습니다. 그러다 어느 순간 그림이 더 선명해질 때까지요. 물론 대부분의 사람들은 그런 방법이 있다는 것조차 모릅니다.

우리 회사가 만든 웹 사이트 관리 소프트웨어 CityDesk의 최신 버전에서는 내부적으로 모든 것을 UCS-2(2바이트) Unicode로 처리하기로 결정했습니다. 이것은 Visual Basic, COM, 그리고 Windows NT/2000/XP가 기본 문자열 형식으로 사용하는 방식입니다. C++ 코드에서는 문자열을 char 대신 wchar_t(“wide char”)로 선언하고, str 함수 대신 wcs 함수를 사용합니다(예를 들어 strcat 대신 wcscat, strlen 대신 wcslen). C 코드에서 리터럴 UCS-2 문자열을 만들려면 앞에 L만 붙이면 됩니다. 이렇게요: L"Hello".
CityDesk가 웹 페이지를 게시할 때는 그것을 UTF-8 인코딩으로 변환합니다. 이 방식은 웹 브라우저에서 수년간 잘 지원되어 왔습니다. _Joel on Software_의 29개 언어 버전도 모두 이 방식으로 인코딩되어 있으며, 지금까지 그것들을 보는 데 문제가 있었다는 사람의 이야기를 단 한 번도 듣지 못했습니다.
이 글은 꽤 길어졌고, 문자 인코딩과 Unicode에 대해 알아야 할 모든 것을 제가 다 다룰 수는 없습니다. 하지만 여기까지 읽으셨다면, 이제 다시 프로그래밍으로 돌아가기에 충분한 지식은 얻으셨기를 바랍니다. 거머리와 주문 대신 항생제를 사용하는 쪽으로 말이죠. 그럼 이제 그 일은 여러분께 맡기겠습니다.
여러분은 지금 Joel on Software를 읽고 있습니다. 이곳은 소프트웨어 개발, 소프트웨어 팀 관리, 사용자 인터페이스 설계, 성공적인 소프트웨어 회사 운영, 그리고 고무 오리에 대한 완전히 제정신이 아닌 글들이 수년치 가득 들어찬 곳입니다.
제가 새 글을 올릴 때 알고 싶다면, NewsBlur 같은 RSS 리더를 사용하고 제 RSS 피드를 구독하시는 것을 권합니다.
2000년에 저는 Fog Creek Software를 공동 창립했고, 그곳에서 FogBugz 버그 추적기, Trello, Glitch 같은 멋진 것들을 많이 만들었습니다. 또한 Jeff Atwood와 함께 Stack Overflow를 만들었고 2010년부터 2019년까지 Stack Overflow의 CEO를 맡았습니다. 오늘날 저는 Stack Overflow, Glitch, HASH의 이사회 의장을 맡고 있습니다.