名前・番号・ハッシュ値——モノに識別子をつける手法

どうも、テツです。

何かと何かを、別々のものとして分けて扱いたくなったときは、それぞれに違う識別子をつけるものです。
識別子をつけなくても、自分の中で区別できていればよいということもありますが、それだと自分以外の誰とも共有できなくて不便なわけです。

モノに識別子をつけるには、以下の三つの手法があるのではないか、というのが当記事のテーマです。
・「名前」
・「番号」
・「ハッシュ値」


まずは個別に見ていきましょう。


「名前」

自然言語によって名前をつける。これは、もっとも身近な行為といえるでしょう。ここまで「識別子を……」というもったいぶった表現をしてきましたが気をぬくと普通に「名前を……」と書いてしまいそうになる、それくらい身近なものです。

パソコンを使う上でも、ファイルやフォルダに名前をつけるのは日常的な行為です。

名前といえば避けて通れないのが「衝突」です。
違うものに対して同じ名前をつけてしまう。
たとえば絵画において、これまでいくつの「無題」という作品が作られてきたことでしょう。
人が生まれてはじめて意識する識別子といえば自分の名前でしょうけれど、よほど珍しい苗字でない限り、同姓同名の人がいますよね。

また、他に避けられない弱点としては「表記ゆれ」というものがあります。
同じものを指しているのに、使っている文字が微妙に違う。
これは、人間の脳によって無意識のうちに訂正されてしまうことが多いです。簡潔に言うと「誰も気にしねえ」。
コンピューターで処理する妨げになるもので、検索機能の開発者は、曖昧検索やサジェストを出すなど工夫をしています。

Wikiのシステムは「名前」を中心に据えています。名前でもってページを識別し、URLにやリンクにも名前がそのまま使われます。


「番号」

これもまた日常的に使われる手法です。
たとえば漫画のシリーズで、複数の単行本が出る際は、第1巻、第2巻……となり、各巻の違いを番号によって区別します。
番号には自然数を使うのが文字通り「自然」ですが、記号を使うこともありますね。「A,B,C...」や「甲乙丙……」。3つ以内に収まるならば「上中下」というのもあります。

プログラマーとしては、特に「連番」が馴染み深いものでしょう。
DBのオートインクリメント。Redmineのチケット番号。GitHubのイシュー番号。
Seesaaブログでは、記事に、システム全体で一意な連番を振っています。

「番号」はどこかで中央管理されており、その中央にアクセスする手段がない者は、付与することはできません。
たとえば、私が自主映画を撮って勝手に「スター・ウォーズ エピソード10です」と言ったところで、それはスター・ウォーズの10作目にはカウントされません。

管理においては、どこまで番号を進めたかを把握することが重要です。
番号が飛んでしまうのとはともかく、重複してしまっては、識別子になりません。

番号は、区切ることにより、管理の権限を委譲することが可能です。
たとえばIPアドレス。グローバルIPは世界的に管理されていますが、プライベートIPならばそのローカルネットワークの管理者が好きに割り振ることができます。
TwitterがツイートのIDを生成するのに使っていたsnowflakeでは、スケーラビリティを得るために、64ビットのうちの数ビットをDBのシャード番号に割り当てています。これにより、水平分割した各々のDBが中央とやりとりすることなく衝突しないIDを生成することが可能になります。

識別子に時刻(タイムスタンプ)を用いることも「番号」の一種とみてよいと思います。
どちらも、一次元座標にある点を識別子として、ある識別対象と一対一で結びつける、という行為だからです。
時刻は連番と違って誰でも勝手に付与することができますが、中央管理はされています。協定世界時(UTC)です。高精度の時計と同期しない状態で、手元の時計がたまたま指していた時刻を使っても、信頼性は低いものです。


「ハッシュ値」

他の二つと違って、時代的に後に登場し、コンピューターと密接な関わりがある手法です。
説明が難しいので、Wikipediaに丸投げしておきます。
ハッシュ関数 - Wikipedia

ハッシュ値はダイジェストとも呼ばれますが、識別子を付与したい対象の全体を要約するということで、たとえば「東京急行電鉄」を「東急」と略すような、「オーシャン・パシフィック・ピース」を「オッパッピー」と略すような、そういう手法の延長にあるといえるかもしれません(さすがに強引すぎるか……)。

ハッシュ値は、セキュリティや暗号技術などと縁の深いものですが、識別子として使うこともあります。
その最たる例が、MercurialやGitなどのDVCS(Distributed Version Control System, 分散バージョン管理システム)です。コミットの識別子としてハッシュ値うことにより、他のリポジトリの変更を取り込むことが可能になります。
たとえばSubversionでは、リビジョンの識別子は連番なので、リポジトリ内で中央管理する必要があります。リポジトリを複製して、それぞれに別の内容をコミットすれば、識別子は衝突します。
DVCSでは、コミットの内容を元に生成したハッシュ値を用いることにより、複製したリポジトリに別々にコミットしても、識別子が衝突しない(可能性が非常に高い)わけです。
この、中央管理しなくても衝突しない、という性質が「ハッシュ値」の強みです。

Dockerにおいてもハッシュ値が識別子として利用されています。
「docker images --no-trunc」コマンドを実行すると、以下のように表示されます。(一部「...」で省略)
REPOSITORY   TAG     IMAGE ID                     CREATED      ...
postgres 9.6.2 sha256:9910dc9f2ac0dbc1... 8 days ago ...
SHA-256はハッシュ関数の一種です。
タグではイメージの本当のバージョンを特定することはできません。PostgreSQL 9.6.2 のリリース日は2月9日ですが、公式Dockerイメージである postgres:9.6.2 の生成日はコマンドを実行した3月30日時点で「8 days ago」となっています。ポスグレのバージョンは同じでも、日々新しくビルドしていることが伺えます。


三つの手法についての概略は以上です。
ここから記事の最後までは、これらの手法について思ったことを気ままに綴っていきます。


識別子と意味

「名前」の特徴はなんといっても、意味がある、ということです。
パソコンのファイル名・フォルダ名が、全て連番やハッシュ値だったとしたら、我々が正気を保つのは困難になるでしょう。
プログラマー視点でいうと、ソースコードのクラス名やメソッド名や変数名、データベースのテーブル名やカラム名、これらにはみな「名前」をつけます。

「番号」それ自体にはあまり意味がありません。大きか小さいか、といった比較はできますが。
数字を一つ間違えるだけで、全然違うものを指してしまいます。
番号の無意味さに対する対策もいろいろあり、入力ミス防止の点では、チェックディジットを付与して冗長な番号にするというものがあります。
また、意味を与えるために「名前」と併用する、というのも一般的かと思います。
たとえば、やろうと思えば数字だけで郵便物を出すことが可能です。弊社の住所は「東京都渋谷区渋谷3丁目3−1 A-PLACE渋谷金王7階」で、渋谷区渋谷まで郵便番号が存在するため「150-0002 3-3-1 7」という番号だけで識別できなくはない。しかし、大抵は地名も書いて送ります。個人宅向けなら氏名も書くでしょう。番号と名前の整合性をチェックすることで、ミスの検出が容易になるわけです。
ソフトウェアのバージョンにコードネームを付けることも多いですね。Debian 8 なら「Jessie」、Mac OS X 10.6 なら「Snow Leopard」、Android 6.0 なら「Marshmallow」、などなど。「10.6と10.7」と言うよりも「Snow Leopard と Lion」と言ったほうが差異が際立ちます。

「名前」を識別子にすると、後から変えるのが難しくなる、という弱点もあります。
ソフトウェア開発でバグが発見されたり、運用でトラブルがあったときは、すぐにイシュートラッカーに登録して追跡することが肝心です。このとき、全貌が明らかになってくるにつれて、当初想定していた原因や影響範囲は間違っていたと判明するのはよくあることです。こうなると、最初につけたタイトルは変えたくなってきます。
Wikiのようなシステムだった場合、タイトルを変えることは苦痛をもたらします。変更前のURLがデッドリンクになったり、それを防ごうとすると変更前のページから変更後のページへのエイリアスを運用しなければならなかったり。
Redmineのようなシステムでは、イシュー登録時に付与される連番が不変のIDとなり、リンクも「#999」といった表記で行えるので、名前は後からいくらでも変更できます。最初から正しい名前を付けなければならないというプレッシャーから解放されたので、初めて使ったときは感動しました。

「名前」には意味があるというのは、意味がはっきりしていないものに適切な名前を付けるのは難しい、ということです。
事前に意味がわかっているものについて名前を付けるのは容易いといえます。たとえばWebサービスにおいてサーバーが存在する意味は明白なので、ホスト名はwwwであるとかdb1,db2であるとか、役割をもとにすればよいわけです。
一方で、人名の場合は難しくなります。その人が存在する意味とは? 新生児は言うまでもなく、ゲームのキャラクター名だって決めるのに何時間も悩んだりしますね。サーバーのホスト名と同じようにやったら後々破綻するのは目に見えています。

「名前」は意味を持つがゆえに、人間が意味を考えないと付与することができません。その点、「番号」や「ハッシュ値」はコンピューターで自動的に生成することができます。
ブログやメールにおいてはタイトルを付けること、すなわち投稿内容に「名前」を付けることがほぼ義務付けられていますが、マイクロブログ(Twitterみたいなやつのことです)やメッセンジャー(LINEのトークなど)ではタイトルは必要ありません。どうせシステム側で「番号」を振るので、タイトルは必須ではなかったのです。後知恵ではありますが、このタイトルを決めなくてもよいという軽やかさこそ、コミュニケーションツールとして広く普及する原動力のひとつだったといえるでしょう。


「番号」と「名前」の併用

よく見られるものですね。前述した、漫画のタイトルと巻数であるとか、郵便物の住所であるとか。

ネットワークサービスで自分の氏名でアカウントが取れなかったので、適当な番号を追加する、といった経験は誰しもあるでしょう。

Githubでは、イシューの文中に「#999」と書くと別のイシューへ自動リンクされますが、手前に名前を付け「Username/Repository#999」と書くと、別リポジトリのイシューにリンクすることが可能となります。
ちなみに https://github.com/issues?q=is%3Aopen+is%3Aissue にアクセスすると何百万件ものイシューが出てくるので、裏ではシステム全体の通し番号が付いているかもしれません。このページはリロードするたびに結果が変わるので、DB分割されており全体で一意なわけではないのかもしれません。

コンピューター関連では、人間にはわかりやすいように「名前」を見せて、裏では「番号」で管理、というのがよくあります。
ファイルシステムにおいて、人が認識するのはファイル名ですが、裏ではiノード番号で管理しています。
IPアドレスとは別にホスト名を付けますし、インターネットにはDNSがあります。
Rubyのシンボルは、ソースコード上では文字列ですが、内部的には整数で管理されています。


組織と「番号」

組織が対外的に広く公表するものには番号が付けられるものです。照合する際に重要ですね。

MITRE社はソフトウェアの脆弱性情報に識別子を付与して公表しています。
CVE (Common Vulnerabilities and Exposures) と呼ばれ、 CVE-2017-5638 といったように、西暦+連番が振られています。

特許庁も、申請内容を公表する際には特許番号を付与します。


識別子を人が入力するということ

住所は「名前」ですが、電話番号は「番号」です。なぜ名前ではなく番号だったかというと、当時の技術水準で電話交換機を作るにはそれが当然だったからでしょう。また、電話機一台一台に文字入力機能を搭載するというのも非現実的です。逆に、交換機が登場する前は、ユーザーは電話番号など使わずに交換手に相手の名前を伝えていたそうです。
電話よりも後に発明された、コンピューターの存在を前提とするEメールの場合、メールアドレスを「名前」で管理しますよね。
ケータイの出現以降、電話「番号」を意識することは減りました。アドレス帳に登録した「名前」で管理するようになりましたから。知人の電話番号をソラで言える割合も激減しました。

人が入力することを考えた場合、やはり「名前」が扱いやすいものでしょう。

「番号」を入力する場合、桁が多いと大変なので、金額の場合は3,4文字に一回カンマを入れたり、電話番号や郵便番号の場合はハイフンを入れたり、認知的な負荷を減らす工夫があります。

「ハッシュ値」を人が入力するのは現実的ではありません。40文字以上あるのが普通ですし、「番号」以上に意味が読み取れないものですから。
Gitのハッシュ値を扱う際は、コピペで済ませるか、SourceTreeやGitHubのGUIに任せることになります。
Mercurialは、ハッシュ値とは別に連番でもリビジョンを指定することができます。もちろん外部のクローンリポジトリとやりとりする際には使えませんが、ログを確認するといった手元のリポジトリで完結する作業ではコマンドを打つのがすごく楽になります。


識別子の使い分け

「名前」は意味を表します。
ということは、「意味」で識別したいなら名前をつけるべきです。

「ハッシュ値」は内容全体をスキャンして生成されます。
ということは、「内容」で識別したいならハッシュ値です。

「番号」はどうでしょう。他の二つ比べてすぐには思いつかなかったのですが、あえて言うなら「場所」といったところでしょうか。
まだ意味がはっきりしていない、内容も後から変わるかもしれない、そういうモノに対して、とりあえず場所を決めてしまおう、というときに、連番は便利ですね。
時刻を使うと時系列上の位置が示すことができます。前述したTwitterのsnowflakeでは、上位の41ビットがタイムスタンプとなっており、IDだけでツイートをソートすることが可能なのだそうです。


時間があるなら空間も、ということで、空間上の位置を識別子にすることも考えられます。
たとえばGoogleマップでは位置情報の利用して「この場所で撮られた写真」が見れたりしますし、スプレッドシートやTrelloを使っていると視覚的な位置関係で把握できたりしますし、便利だなと思うのですが、しかしこれは識別子として使われているのではありません。
これといったものが思いつかないのですが、何か面白い応用ができるような気がしています。

この記事へのコメント