「id」という名前を避けたくて、それでも名前を切り詰めたくて。

行儀が悪い話なので、安直に真似せずに、「ちゃんと理解して」ね。あと今回は javascript の例だけど、動的な言語には共通の悩みといっていい。

「id」という名前を避けたくて、それでも名前を切り詰めたくて。

id という名前を避けたい数多くの例

「id」ほど避けたい名前もないよなぁ、といつも思っているのである。自分が管理するデータ構造を設計するのに、「id」という命名を避けたいケースはやまほどある。

無論「ターゲットシステムの予約語」と衝突するからだが、ざっと今思いつくだけでもこれだけある:

  1. DBMS の予約語 (DBMS 依存かな?)
  2. プログラミング言語の予約語、もしくは「マインドとしてはほぼ予約語」
  3. html の id アトリビュート

衝突しても困らないケースはもちろん「文字列リテラル」をキーに出来るなにがしか環境で、2. のケースだってこう使う限りは別に困らない:

1 d = {"id": 12345}

問題となるのはこういう場合だよね:

1 class MyScruct(object):
2     pass
3 
4 d = MyScruct()
5 d.id = 12345

Python のこのケースは別に言語的には何の問題もないけれど、読み手を混乱させることは確実だ。(エンジニアのスキルにバラつきがあるチームの場合は何かしら注意喚起しといた方がいいパターン。)

「マインドとしては予約語」というのはつまり、『プログラムが「id」だらけで「これはどの誰のどこぞのなんの誰の id だ」』を避けたいからだ。こんなん「書いてても」「読んでても」混乱する。WEB プログラミングなら確実に「html のエレメントの id アトリビュート」と混同を出来るだけ避けたいのだ。

そして「だからといってサフィックスやプレフィックスを付けようと考え出す」と、今度は違う種類の悩みに突入する。

いっそ長い名前にしてしまおうか、が採れない理由があるとすれば

この話、一度は書こうと思っていたこと。書く機会が持てて良かった。というのもこの話、「そんなん当たり前だろ」と思う人とそうでない人の差が非常に激しくて、職場でこれがまったく伝わらなくて困った経験があったのだ。(だいたい下記で説明するどちらか一方のメリットしか見えてない。)

こういうふうに思っている人、いないかな? 「key-value さえありゃぁ、99% 困んなくね?」。そう思っている人に向けていいたいことだ、これは。(これは「json だけあれば世界は平和」に通ずる。)

key-value pair というのはキーと名前を対応付ける概念のことでしかないが、「いかなる方式で、いつ、どのように対応づけているか」によって全くみえる世界が違う。以下を key-value pair と呼ぶバカはいないが、でも「キーと値の関連付けには違いない:

C の構造体。構造体のフィールド「名」と「値」のペア。
1 typedef struct _MyStruct {
2     char field_a[64],
3     char field_b[32],
4     char field_c[32],
5 } MyStruct;

これは「field_a ⇒ “aaa…”」のような関連付けを「行える」構造ということになるが、この「関連付け」を行うのは「C コンパイラがコンパイル時に」行うことであって、ランタイムに動作する際は「それが field_a であったのだ」という事実の一切合財は忘れ去られている。C の構造体は「弁当箱の間仕切り」を思い出してもらうといい。メモリの塊を間仕切りで分割して、『「どのゾーンにレンコン(文字列 aaa)を入れるか」を「お母さん(プログラマ)」が「さいばし(メンバ名アクセス .field_a)」で指示した』ことは、それを早弁する息子にはわからないことである。

このような「固定レイアウト」が、「プログラミング言語で作成するアプリケーション」としては脆いものであるその理由とは、無論「名前の情報を失っている」ために互換性を保ち続けることが難しいからだ。実際以下は上の例と「互換に見えるが違うシロモノ」である:

C の構造体。構造体のフィールド「名」と「値」のペア。
1 typedef struct _MyStruct {
2     char field_b[32];
3     char field_c[32];
4     char field_a[64];
5 } MyStruct;

間仕切り位置が違っている上に名前も違っているが、「これを使うプログラム」は同じに見えてしまう。

key-value pair と「わざわざ呼ばれるもの」は、こういう「実際は間仕切られた各領域が名前を持たない間仕切り方式」ではなく、「間仕切られた領域の個々が「オレ、ここーっ」と「名前で」自己主張する」という方式のことを言う。C 言語の例で続けたほうがわかりやすいと思うので C で:

1 typedef struct _MyStruct {
2     char field_a_key[32]; // キー
3     char field_a_value[32];  // 値
4     char field_b_key[32];  // キー
5     char field_b_value[32];  // 値
6 } MyStruct;

こうなるともう「コンパイラそのものが関連には関与せずに」、プログラマが対応付けを自分で書くことになるが、弁当箱の例で言うならこれは、「お母さんがそれぞれに「レンコン」「しいたけ」「煮豆」という旗を立てる」ようなものだ。

awk でも Ruby でも Perl でも Python でも javascript でもなんでもいいが、およそ「スクリプト言語」と呼ばれる言語のほぼ全てにおいて、上で C で「自力で」実現しようとした「key-value pair」は「組み込みで」サポートされている、ということだ。そうした場合に「つい忘れがち」になるのが、まさに上で C で示した通りのこと、つまりキーの名前が大きくなるほどメモリを多く使うということである。(上の例ではわかりやすさのために動的メモリアロケートの例で書かなかったが、スクリプト言語では無論ダイナミックに拡縮する。)

この C 構造体方式と「スクリプト言語の key-value pair サポート」の対応関係と同じ対応関係にあるものでわかりやすいのが「csv と json」だ:

csv
1 key_a,key_b,key_c,key_d,key_e
2 1,2,3,4,5
3 5,4,3,2,1
4 3,4,5,1,2
json
1 [
2   {key_a: 1, key_b: 2, key_c: 3, key_d: 4, key_e: 5,},
3   {key_a: 5, key_b: 4, key_c: 3, key_d: 2, key_e: 1,},
4   {key_a: 3, key_b: 4, key_c: 5, key_d: 1, key_e: 2,},
5 ]

そう、この2つをみて「さすが json、読みやすいぜ、扱いやすいぜ」と言ってしまう人に向けているのだ。それは見方が一面的過ぎる。なんでもそうだが、何か競合する技術がある場合に、「どちらか一方だけが全てにおいて勝る」ということは十中八九ありえない。上の例でもすぐにわかる通り、「csv の方が遥かにコンパクトだ!」。だから「C の構造体はダメダメだ」ということはなく、すなわち「どちらも必要だし、適材適所で使うだけの話である」という、非常に当たり前の話。

さて、もうわかったよね? 今あたしが直面しているのは、「json である必要があり、javascript から使う必要があり、そしてサイズを切り詰めたい」というケースなのだ。

そして id 関連の名前を id 以外の名前のまま切り詰めるのはタイヘン

そもそもレコード数が小さいとか、あるいは繰り返しに現れないフィールド名なら多少長くてもいいんだけど、「入れ子の木となりうる全ての部分構造が id 的な識別子を持ち、なおかつ繰り返しが何百何千何万になりうる」とね、もう「id_xxx」だけでかさばっちゃってかさばっちゃって。

今扱ってるのは「MAL (MyAnimeList.net)」なので、「MAL における ID」としたいとしてだな、「id_mal」とか「malid」が一番素直だけれど、これじゃデカい、ということで「そのまま切り詰めようと試みる」とさ…:

  • maid – 不愉快な一致…
  • mid – 真ん中…。やだよぉ
  • idm – うーんわかりにくい
  • ix – インデクス、ではないんだよなぁ、「MAL の」が消えちゃってるし

そして辿りつく。記号…

英数字にこだわってるとあんましいい解がないんだけれど、あぁ、「#」とか良くない?

2018-01-28追記: 末尾の追記参照
 1 { '#': '11661',
 2   nm: 'Kurosawa, Tomoyo',
 3   im: '/images/voiceactors/3/30873.jpg',
 4   dtl: { brth: 1996-04-09T15:00:00.000Z, bld: 'O' },
 5   nmj: '黒沢 ともよ',
 6   va: 
 7    [ { ani: 
 8         { '#': '31790',
 9           nm: 'Active Raid: Kidou Kyoushuushitsu Dai Hachi Gakari' },
10        ms: 2,
11        ch: { nm: 'Liko', '#': '134656' } },
12 //...

いやさ、このシリーズの続きとして、いい加減「エキスポート/インポート」を追加したんだけどね、その前にさ、上で説明した「サイズの問題」にあまりに無頓着に作ってたんで、すぐにブラウザが重くなってたわけさ。キーの名前を切り詰め切り詰めしたら、だいぶ動作が軽くなってきたわ。

その「名前切り詰め」で最後まで弱ってたのがこの「id」だったの。ワタシのヤツの場合は html の id と衝突するというだけでなくて、「MAL id」に直結しないノードがあるわけよ。「アニメ: 宝石の国, キャラ: フォスフォフィライト, 声優: 黒沢ともよ」という3つ組が一個のノードになるんだから。なので「id」はこの本当の id だけに取っておきたかった。なので仕方なく「idm」とかしてたんだけど、「もっと詰めたい」が限界でしょう? さすがに「i」はないなと思ったんで。

すげーと飛びつくなかれ、最後にちょっと注意

念のためにもう一つ説明としては注意して欲しい点。「json としてファイルに書き出す」のサイズと「ランタイムで動作する javascript が使うメモリ使用量」をごっちゃにしないこと。前者だと「’#’」と3バイト(引用符含めて)使って「非合理」に見えるかもしれないけれど、無論これは「外部化」のためにそうなってしまうだけのことで、JSON.parse して javascript オブジェクトになってしまえば「引用符ぶん余分にメモリが必要」なんてことにはならない。(ただしパース時はそら余計にメモリ使うけど。)

あと実は、「全体は json だけど部分的に csv (レコード単位でフィールド名を主張しないリスト) にしちゃえ」もありうることにも注意。もっと極限まで切り詰めたいならそこまでやればかなりサイズ削減出来る。無論プログラムは作りにくくなるしわかりにくくもなるけれど。ここまでの必要に迫られることも、現実には結構ある。(特に「ブラウザで動く」ものはメモリの制約を思ったより受けやすかったりするので。) そちらの場合はむしろキー名は切り詰めない方がいいだろう。(csv と同様に、「列インデクスにアクセスするためのキー名マップ」さえあれば良く、このマップは繰り返さないので多少大きくてもいい。)


2018-01-28追記:
すまぬ、「自分でやったことに騙されて」、json の仕様を捻じ曲げてしまってた。そういえばそうでした、という話なのだけれど、こういう対応関係ね:

1 // javascript の範疇では Object のキーは以下どっちも許される
2 var d1 = {"a": 1};
3 var d2 = {a: 1};
4 
5 // けど JSON は「データ交換を円滑に行うための javascript サブセット」なので…
6 JSON.parse('{"a": 1}'); // OK
7 JSON.parse('{a: 1}'); // SyntaxError: Unexpected token a in JSON at position 1

上で間違えた「JSON のつもりで貼り付けた不正な JSON」、自分では JSON.stringify をダンプしたものを貼り付けたつもりになってて、なおかつ「'{a: 1}' が不正なことを失念していた」ため、「引用符を省略してくれる JSON minify なんてないかなぁ」と探し始めてしまうという愚かなことを始めてしまったのだった。それで気付いた。

ワタシは何を貼り付けてしまったのかと言うと、node.js の util モジュールの inspect の結果だった:

1 const util = require('util');
2 util.inspect({a: 1}, false, null);  // ⇒ '{ a: 1 }'

inspect は「javascript として正当な文字列」を作ってはくれるが「JSON として妥当」とは限らない。

てわけでごめんなさいっっっ。