합동회사 코무라소프트
6장

실무의 함정 — 선언과 실체를 일치시킨다

문자 깨짐의 정체는 「선언(라벨)」과 「실체(바이트 열)」의 불일치. charset 선언·Excel 과 BOM 있는 UTF-8·줄바꿈 코드·데이터베이스의 연결 설정이라는 현장의 단골을 정리한다.

「어제까지 정상이었는데」— 깨짐은 경계에서 일어난다

개발 환경에서는 정상인데 프로덕션에 올렸더니 페이지가 깨졌다. 배치가 출력한 CSV 를, 이용자가 Excel 에서 열면 깨진다. 앱에서는 올바르게 등록된 것처럼 보이는데, DB 를 직접 보면 다른 문자가 들어 있다. — 실무의 문자 깨짐은, 대개 시스템과 시스템의 경계, 즉 데이터가 손에서 손으로 넘겨지는 곳에서 일어납니다.

이 장의 도구는 1 가지뿐입니다. 경계를 넘는 데이터에는, 실체(바이트 열)와, 그것을 어떻게 읽어야 하는지 전하는 선언(라벨)이 있다. 그리고 선언은 바이트 열을 변환해 주지 않는다. 그저 표찰입니다. 표찰과 내용물이 어긋난 순간, 받은 쪽은 잘못된 대응표를 찾고, 제 1 장에서 본 문자 깨짐이 재생됩니다.

UTF-8 의 실체에 charset=Shift_JIS 라벨을 붙여 보내면, 브라우저가 라벨을 믿고 오독하여 문자가 깨지는 경로도

실체는 UTF-8, 라벨은 Shift_JIS. 브라우저는 라벨을 믿으므로 깨집니다. 고쳐야 할 것은 라벨인가, 실체인가 — 양쪽의 가능성을 의심하는 것이 조사의 틀입니다.

웹의 선언 — Content-Type 과 meta charset

웹 페이지의 인코딩 라벨은 2 곳에 있습니다. HTTP 응답 헤더의 Content-Type: text/html; charset=UTF-8 과, HTML 안의 <meta charset="UTF-8"> 입니다. 양쪽이 있을 때는 HTTP 헤더가 우선됩니다 — 서버의 설정이, 파일에 쓰인 meta 를 덮어쓸 수 있다는 것입니다.

「파일은 UTF-8 로 변환했고 meta 도 고쳤는데, 프로덕션에서만 깨진다」는 괴현상은, 대개 서버나 프레임워크가 낡은 charset 을 헤더에 붙이고 있습니다. 반대로, 선언이 어디에도 없으면 브라우저는 추측으로 읽습니다. 실체를 UTF-8 로 하고, 헤더와 meta 양쪽을 UTF-8 로 맞춘다 — 현대 웹의 기본형입니다.

Excel 과 CSV — 「BOM 있는 UTF-8」이 효력을 발휘하는 장면

CSV 에는 charset 헤더 같은 선언의 구조가 없습니다. 더블 클릭으로 열렸을 때, 종래의 Excel 은 지역 설정의 기본값(일본어 환경이라면 CP932)으로 읽으러 갑니다. UTF-8 로 출력한 CSV 가 깨지는 단골은 이것입니다.

여기서 제 3 장의 BOM 이 활약합니다. 파일 선두에 EF BB BF 가 붙어 있으면, Excel 은 그것을 표시로 삼아 UTF-8 로 읽어 줍니다. 「시스템에서 출력하는 CSV 는 BOM 있는 UTF-8 로 한다」는, Excel 이용자에게 열릴 파일의 정석입니다. 다만 만능은 아닙니다 — BOM 을 상정하지 않은 다른 시스템에 같은 파일을 건네면, 선두 3 바이트가 쓸데없는 데이터로서 오동작을 부르는 일이 있습니다. 누가 읽는 파일인지에 따라, BOM 의 필요 여부를 정하는 것이 실무의 판단입니다.

소문제 6-1 — 선언과 실체

「라벨은 바이트 열을 변환하지 않는다」는 원칙을, 웹과 Excel 의 단골 장면에서 확인합니다.

Q1. HTTP 의 Content-Type 헤더의 charset 이나 HTML 의 meta charset 의 역할로 올바른 것은 어느 것입니까.

Q2. UTF-8 로 저장된 HTML 이 charset=Shift_JIS 로 선언되어 배포되어, 문자가 깨졌습니다. 문자 깨짐의 「정체」의 설명으로 가장 정확한 것은 어느 것입니까.

Q3. UTF-8(BOM 없음)로 출력한 CSV 를 이용자가 더블 클릭으로 Excel 에서 열면 일본어가 깨진다, 는 단골 트러블이 있습니다. 파일 쪽에서의 대표적인 대책은 어느 것입니까.

줄바꿈 코드 — 또 하나의 「보이지 않는 바이트」

인코딩 상담을 받으면, 종종 범인은 줄바꿈 코드였다, 는 일이 있습니다. 줄바꿈도 바이트입니다. Windows 는 CRLF(0D 0A)의 2 바이트, Unix/Linux·macOS 는 LF(0A)의 1 바이트가 관습으로, 같은 「겉모습」의 텍스트라도 바이트 열이 다릅니다.

CRLF 의 파일을 LF 전제의 프로그램에 건네면, 각 행의 끝에 CR(0x0D)이 남아, 「값의 끝에 보이지 않는 1 문자가 붙어 비교가 실패한다」 「행 수를 세는 방식이 어긋난다」 같은, 문자 깨짐보다도 알아차리기 어려운 버그가 됩니다. CSV 연계의 약속에서는, 인코딩과 줄바꿈 코드를 세트로 지정하는(예: UTF-8·BOM 없음·LF) 것이 정석입니다.

데이터베이스 — 선언은 경로의 전 구간에 필요

데이터베이스에도 인코딩 설정은 여럿 있습니다. 대표적인 것은 테이블이나 열의 인코딩(바이트 열을 어떻게 저장·해석하는가)과, 연결의 인코딩(앱과 DB 의 대화가 무엇으로 이루어지는가의 선언)입니다. 앱 → 연결 → 테이블이라는 경로의 어느 1 구간이라도 선언과 실체가 어긋나면, 저장 시나 조회 시에 오변환·오해석이 일어나, 깨진 데이터가 저장되어 버립니다.

성가신 것은, 웹 페이지와 달리 깨진 결과가 영속화된다는 점입니다. 제 1 장의 철칙 「깨진 채로 저장하지 않는다」의 최악형이, DB 에서 조용히 진행되는 깨진 데이터의 축적입니다. 신규 구축에서는 처음에 전 구간을 UTF-8 계로 맞추고, 기존 시스템에서는 「어느 구간의 설정이 실체와 어긋나 있는가」를 1 구간씩 확인한다 — 조사의 틀은 여기서도 같습니다.

이 강좌의 구호, 실무판: 깨지면 「어느 경계에서, 선언과 실체가 어긋났는가」. 쓴 인코딩과 읽은 인코딩의 어긋남은, 경계의 수만큼 일어날 수 있다.

소문제 6-2 — 보이지 않는 바이트와 연결 설정

줄바꿈 코드와 데이터베이스 — 「보이지 않는 부분」의 불일치를 마무리합니다.

Q4. 줄바꿈 코드의 설명으로 올바른 것은 어느 것입니까.

Q5. 데이터베이스에서 문자 깨짐이 일어나는 전형적인 원인으로 올바른 것은 어느 것입니까.

Q6. 받은 텍스트 파일의 인코딩을, 읽기 전에 100% 확실하게 아는 방법이 있을까요.

이 장에서 가져갈 것

  • charset 은 라벨. 바이트 열은 변환되지 않는다. HTTP 헤더는 meta 보다 우선된다
  • Excel 에서 열릴 CSV 는 BOM 있는 UTF-8 이 정석. 다만 BOM 의 필요 여부는 「누가 읽는가」로 정한다
  • 줄바꿈 코드(CRLF 0D 0A / LF 0A)는 인코딩과 다른 축의 「보이지 않는 바이트」 문제. 약속은 세트로
  • DB 는 테이블의 인코딩과 연결의 인코딩의 전 구간 일치가 필요. 깨짐의 영속화가 최악의 패턴
  • 인코딩을 확실하게 판정하는 방법은 없다. 사양으로 정하고, 선언과 실체를 일치시킨다가 본진

도구는 모두 갖추어졌습니다. 마지막 장에서는, 깨진 화면 그 자체를 증거로 삼아, 오독의 조합을 추리하는 종합 연습입니다.