Help us understand the problem. What is going on with this article?

私が遭遇した文字コードにまつわる7つの迷信

現在はUTF-8が文字コードのデファクトスタンダードになっていますが、かつては文字コード周りがややこしい時代がありました。

そのせいか、経験年数の長いプログラマーでさえ(むしろ経験年数が長い故に?)文字コード周りの誤った「ベストプラクティス」が信じていることがあります。

私も新人の頃、先輩に色々な「ベストプラクティス」を教えられ、後で「これはアンチプラクティスだったんだ・・・」と気づくことがなんどもありました。この記事ではそういった文字コード周りの「誤り」「迷信」を列挙していきたいと思います。

なお、文字コードには「お前の言うのShift_JISは、Shift_JISじゃなくCP932」といった、文字コードの名前やバージョンに関する迷信もありますがここで、この記事では文字コードの取り扱いについての迷信を扱います。

迷信

おことわり: ここで説明するのは原則論です。簡潔のために「誤りです」といった表現をしていますが、現場ではあえてバッドプラクティスをとらなければいけないこともあります。

迷信1: 「うちのLinuxサーバーはLANG=ja_JP.EUCJPだから、ソースコードもEUC-JPにしないと文字化けする」

Unixには LANG という環境変数があり、端末で使う文字コードを指定します。

多くのコマンドは LANG を参照して出力の文字コードを決めます。プログラムが間違った文字コードで出力してくると、端末上で文字化けが起こります。

それを誤解して:

「うちのLinuxサーバーはLANG=ja_JP.EUCJPだから、ソースコードもEUC-JPにする」

と言われるかもしれませんが、誤りです。

プログラムが意識するべきは出力の文字コードであって、ソースコードの文字コードは関係ありません。また、最近のプログラミング言語では、出力の処理は言語処理系が自動でやってくれることもあります。

なお、シェルスクリプトの場合は、「ソースコードの文字コード = 出力の文字コード = LANG」にせざるを得ない(別の文字コードを使う方法もあるが面倒)ので、このような誤解が生じるのかもしれません。

迷信2: 「vi だとLANGと違う文字コードのファイルは文字化けする」

LANGとソースコードの文字コードを合わせる別の理由として、

「vi だとLANGと違う文字コードのファイルは文字化けする」

といったものが挙がることがありますが、誤りです。

vi(などのエディタ)はファイルの内容をそのまま画面に表示しているわけではなく、LANGにしたがって変換したものを表示しています。文字化けするのは、ファイルが間違った文字コードで開かれているからです。

正しい文字コードを指定すれば、文字化けは起きないはずです。

ファイルを文字コード指定して開くとき:

vi -c ':e ++enc=sjis' sjis.txt

ファイルを文字コード指定して再読み込みするとき:

:e ++enc=sjis

迷信3: 「内部エンコーディングをOSに合わせるべき」

ご存知の通り、ソフトウェア内では文字列はバイト列として表現します。

プログラミング言語ごとに処理系内で文字列をバイト列として表現する際の文字コード(内部エンコーディング)が決まっており、言語によっては内部エンコーディングをオプションで切り替えられます。

先輩に

「内部エンコーディングは、OSのエンコーディングと合わせた方が良い」
「うちのサーバーは LANG=ja_JP.EUCJP だから、Rubyの内部エンコーディングも EUC-JP にしないといけない」

なんて言われたかもしれませんが、これは誤りです。

最近のプログラミング言語では、文字列型には文字通り「文字の列」としてのインターフェースが備わっていたり、ファイル読み込み時にエンコーディングを変換したりと、バイト列であることを意識しなくてもよい設計になっています。内部エンコーディングをあえて変更する機会はないはずです。

# Ruby
str = "⛄️雪だるま"

# 文字列に含まれる「文字」にアクセスできる
char = str.chars.first
puts char # => ⛄

# 文字の「コードポイント」を取得できる。コードポイントはUnicodeの文字に割り当てあられた通し番号で、UTF-8エンコードした時の表現(=3バイトのバイト列)ではないことに注意
puts char.ord # => 9924

# もちろん、内部表現(UTF-8のバイト列)を取り出すこともできる
puts str.encoding.to_s # => "UTF-8"
puts str.bytes.inspect # => [226, 155, 132, 239, 184, 143, 233, 155, 170, 227, 129, 160, 227, 130, 139, 227, 129, 190]

迷信4: 「文字コードを自動判定しよう」

filenkf といったツールにはテキストファイルの文字コードを判別する機能があります。これは人間が調査をするときには、便利です。

しかし、テキストファイルを処理するバッチ処理やCLIツールなどについても

「このツールでは、ファイルの文字コードを自動判定して処理しよう」
「自動判別すれば、文字コードの指定が不要になる」
「文字コードを意識しなくても使えるようにするべきだ」

といったことを言われるかもしれませんが、誤りです。

そもそも、文字コードを完璧に自動判定することは原理的に不可能です:

  • ある文字コードのバイト列が、たまたま別の文字コードとしても解釈できることがある
  • 特にASCII文字しか含まれていない場合は、どの文字コードなのか分からない

そのため、ツールを作るときは、

  • ファイルを開くときにユーザーが文字コードを指定できるようにする
  • 文字コードを自動判定するときは、判定結果を画面に表示した上で、ユーザーが判定結果を上書きできるようにする

とするべきです。

迷信5: 「Windowsだから文字コードはShift_JISのはずだ」

Unicode(特にUTF-8)が普及する前は、OSごとに標準的な文字コードが異なっていました。

Unix系ではEUC-JP、Windows系ではShift_JISなど。特にWindowsではメモ帳などの標準アプリで、ファイルがShift_JISで保存される(保存時に文字コードを選択できない)ため、Windowsで使える文字コードはShift_JISに決まっているという印象が強かった。

しかし、こういった「OSごとの標準的な文字コード」はあくまで、標準「的」なもので、他の文字コードのテキストファイルを扱うのは普通のことです1

デフォルト設定としては「WindowsではShift_JIS、MacではUTF-8にする」とかでもいいかもしれませんが、決め打ちにするのは誤りです。

迷信6: 「DBの文字コードがShift_JISだから、接続時にShift_JISを指定しなければならない」

DBの内部エンコーディングには文字種が多いUTF-8を使うのが無難ですが、古いDBではShift_JISなどを使っていることがあります。

ここで、

「うちのDBはShift_JISだから、プログラム側の文字コードもShift_JISにしなければならない」
「NLS_LANG=JAPANESE_JAPAN.JA16SJISTILDE を指定しなければならない」

と言われるかもしれませんが、誤りかもしれません。「プログラム側の文字コード」が何を指すのか見極めるべきです。

例えばOracle DBでは、クライアントライブラリに環境変数 NLS_LANG で文字コードを指定できますが、NLS_LANGはサーバーとクライアントライブラリの間の通信でどの文字コードを使うかを指定するものです。クライアントライブラリがプログラムに返すのはバイト型ではなく、文字列型なので、プログラム側の処理はNLS_LANGが何であるかは関係ありません。

Oracleサーバー(Shift_JIS)
  ↓
(クライアント/DBサーバー間の通信)(UTF-8)
  ↓
クライアントライブラリ
  ↓
(StringやStringのリスト)
  ↓
プログラム(UTF-16)

もちろん、Shift_JISのDBに「⛄️」を扱うことはできません。そういった意味ではプログラム側も文字コードを意識しなければいけないこともあります。

迷信7: 「デフォルトの外部エンコーディングをUTF-8にしよう」

各種プログラミング言語では、open()などでファイルを開くとき、encodingを指定しなければ何らかのデフォルト値が使用されます。

例:
- RubyではEncoding.default_external
- Pythonではlocale.getpreferredencoding()

「このプロジェクトのテキストファイルは全てShift_JISだから、Encoding.default_external = 'Shift_JIS' に変更しよう」
「デフォルトエンコーディングで読み書きすれば、OSが変わっても動作する」

と言われるかもしれませんが、考えものです。

他の項でも述べたように、OSの標準とは別の文字コードを扱うことは普通にあることで、文字コードを決め打ちにするのは後々苦労の元です。また、デフォルトの文字コードを決めるとしても、それはアプリケーションの仕様なので、プログラミング言語の仕組みに乗っかるべきではないと思います(例えばEncoding.default_externalは環境変数で変更できてしまう)。

個人的には、

  • 「外部エンコーディング」という概念は混乱の元
  • open() 関数は encoding 引数を必須にするべき
  • デフォルトのエンコーディングを決めたければ、DefaultEncodingといったグローバル変数を自前で定義したりすればよい。

と思っています。

文字コードをどう考えるか?

文字コード周りは個別の条件を細かくつつき出せばキリがありません。結局、文字コードの扱いはどのように理解すればよいのでしょうか?

色々な意見・色々な言い方があると思います。「内部の文字コードは統一せよ」「文字コードはIO境界で変換せよ」なんて、よく言われます。

私が重要だと思うのはプログラムの「内部」は文字コードが登場しない世界。文字コードはプログラムの外側の問題だということです。

「文字列型」で文字コードが隠蔽されていたりするので、プログラムの「内部」の処理には文字コードを意識する必要がありません。「内部」で文字コードの問題が起きたとしたら、何かがおかしい。

一方、「ファイル」「WEB API」「別のプログラム」などは、プログラムの「外側」にあるので文字コードを『必ず』意識しなければなりません。ここで「OS標準のエンコーディングを使う」などと横着すると後で痛い目を見るかもしれません。

また、「プログラム」同士が連携するとき:

  • ターミナルと vi
  • パイプで通信する2つのプログラム
  • DBサーバーとクライアント

文字コードは、2つのプログラムの「外側」の「間の部分」の問題なので、それぞれが内部でどんな文字コードを使っていても問題は起きません。「間の部分」で、エンコーディングを適切に指定したり、変換層を咬ませたりすれば、良いのです。


  1. OS標準ツールが文字コードを決め打ちしていて、ムキー! ヽ(`Д´#)ノ となることはあります。 

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away