読者です 読者をやめる 読者になる 読者になる

珍しいSHA1ハッシュを追い求めて

雑記

SHA1ハッシュってあるだろう?」

放課後、いつものように情報処理室に行くと、高山先輩が嬉しそうな顔でそう言った。

「ええ、SHA1、ありますね」

SHA1って何桁か覚えているかい?」

「えっと…」

一年下の後輩、岡村が口を開いた。

「50桁くらいはありましたっけ…?」

先輩はパソコンに向かって何かを打ちはじめた。

現在、情報部の部員は三人しかいない。部長の高山先輩と、二年の自分と、後輩の岡村だ。いや、正確に言うと、先輩の学年にはもう少しいたのだが、もうほとんど部室に来ることはなくなってしまった。無理もない、この季節になると先輩たちは受験勉強で忙しくなる。

「例えば、こういうふうに… 適当なSHA1をエディタで開いて…」

echo -n | openssl sha1 | vim -

部長だけは今も部活に来てこうやって色々なことを教えてくれている。本人曰く、普通に勉強したら受かるでしょ、らしい。そう言ってもあまり嫌味には聞こえないのが先輩のいいところだ。

「カラム数を見ると、40だ。40桁だね」

「そうなんですね」

後輩の岡村は身を乗り出して先輩の端末操作を観察している。

「正確には160桁ですよね…」

僕は思わず口を開いた。

「そう、桁というより、160ビット、それを16進数で表現するから…」

先輩はそう言いながらプリント用紙の山から一枚手にとって説明しはじめた。

「こうやって4ビットずつ区切って0からfで表現するから、160ビットのハッシュを40桁で表現できるんだ」

「なるほど〜」

岡村は指を折りながら2進数表現を確認している。岡村の理解が追いついていることを確認しながら先輩は口を開く。

「つまり、SHA1ハッシュは16文字40桁で表現される。つまり16の40乗種類考えられるわけだね。これはもちろん2の160乗と同じ」

そう言いながら先輩はプリント用紙に簡単な式を書く。

「えっと、0.3かけると… これくらいかな…」

16^{40} = 2^{160} \simeq 10^{48}

「10の48乗…たくさんありますね…」

「そうだね、そこそこ多い」

先輩はシャーペンを顔の前で振りながらこう言った。

「そこでだ、SHA1ハッシュで、全てが数字になることはあるだろうか」

全て数字になるSHA1ハッシュ

情報部の活動は、いつもこんな感じで始まる。 誰かが問題を持ち寄って、それを下校時刻までにそれぞれ解く。 今日は部長の出題というわけだ。

空文字のSHA1ハッシュは da39a3ee5e6b4b0d3255bfef95601890afd80709 だ (これはさっき先輩がやってみせたようにecho -n | openssl sha1すると表示される)。これにはdやらaやらeなど、沢山アルファベットが含まれている。 つまり、SHA1ハッシュの16進数表現が、全て数字になるような元メッセージを求めよという問題なのだ。

早速、自分も作業に取り掛かった。 まずどれくらい存在するかを考える。 16文字の中で10文字が数字なので… と言いながらブラウザーのアドレスバーに (10/16)^40 と押してエンターを押した。

\displaystyle \left(\frac{10}{16}\right)^{40} = 6.84 \cdots \times {10}^{-9}

10の-9乗か… しかしSHA1ハッシュから直接元のメッセージを求めるのはできないので虱潰しに調べるしかない…

そんなことを考えながら、プログラムを書き始めた。 メッセージは適当にアルファベットと数字から作って、とにかくsha1ハッシュを計算して条件にあっていれば表示するだけのプログラムだ。 後輩の岡村の様子を見ると、どうやらSHA1ハッシュのアルゴリズムを確認しているようだった。 部長はシャーペンで何やら数式を書いている。

自分のプログラムは、しばらくして次のように出力しはじめた。

gflyP 9708825594493053358052040804954710052563
OqmSX 5405673447021682949913714302263814023323
pcBsd 0830855821698284247230340812332913765683
jPHSf 0747815384663891403181360803310055645039

おお、やはりあるのか…と思いながら、opensslコマンドでハッシュが間違っていないことを確認しながら、プログラムの出力を眺めていた。

「先輩、けっこうありますね…」

「おう、速いな」

「もう計算できちゃったんですか…」

岡村はシャーペンを机に放って、結果を見にやってきた。

「元メッセージはどう選んだんだい?」

先輩は自分が書いたコードを眺めながらそう言った。

「アルファベット大文字小文字もしくは数字です」

「なるほど…」

先輩は、シャーペンを手にとって何やら書き始めた。

「探したのは5桁のアルファベットもしくは数字のメッセージというわけだ。つまり62の5乗は…」

先輩はシャーペンを手に持ったまま、器用にアドレスバーに数式を入れる。

 62^{5} = 916132832

「およそ9億個ですね」

「ということは、そのうちSHA1が全て数字になるのは…」

岡村は必至に食いついてくる。

「9億に10/16の40乗をかけると…」

\displaystyle 62^{5} \times  \left(\frac{10}{16}\right)^{40} = 6.268 \cdots

「6個です。だいたい」

部長は、後輩の理解が追いついていることに満足そうに頷いた。 それは、自分のプログラムが6桁のメッセージの解を表示しはじめたのと、ほとんど同時だった。

gflyP 9708825594493053358052040804954710052563
OqmSX 5405673447021682949913714302263814023323
pcBsd 0830855821698284247230340812332913765683
jPHSf 0747815384663891403181360803310055645039
IL3rj 3243002591985408609566985352935152776909
MUeWp 8297833274599142233719359578426918109541
JG4Y80 0099530489181060830720140193389029330084
WiMtG0 2254364706744121285147874769100420502311
KMnsR0 4760110574803441139829661498017839123177
...

「つまり今考えているメッセージで5文字で、えっと」

部長がその言葉を遮ってこう続けた。

「アルファベット大文字小文字数字で5文字からなる元メッセージに対して、SHA1ハッシュが全て数字となるのは6つ、およそ期待値通りだね」

岡村も頷いた。

「でも、これに何の意味があるんですかね」

僕は思わず尋ねてしまった。

「意味というのは」

「いや、つまりですよ。SHA1というのは160ビットのバイナリー列って最初に言ってたじゃないですか。この16進数の表現は、なんというか、人間が勝手に4ビットずつ区切って勝手に0からfに割り当てただけじゃないですか」

「なるほど、たしかにそれはそうだね」

「つまり、SHA1ハッシュが数字だろうがアルファベットだろうがSHA1にとっては何の特徴にもならない気がするんです」

「なるほどね」

先輩は頷きながらそう言った。

「でも、それだからこそ、こうやって期待値通り、6個見つかったということにもなるね」

「そこなんですけど…」

岡村が怪訝そうな顔で口を開いた。

「それってつまり、16進数の表現の中で、文字がまんべんなく現れるということを仮定していますよね」

「たしかに」

「本当にそうなんですかね」

先輩はしばらく考えていたが、急に笑顔になって、こう言った。

SHA1の文字が本当に一様に分布するか、計算してみよう!」

その言葉と同時に、下校時刻を告げるチャイムが鳴った。

SHA1ハッシュの文字分布

家に帰ってから、改めて全て数字となるSHA1ハッシュの計算を再開した。 数字とアルファベット6文字の元メッセージは 62^{6} = 56800235584個あり、その中でSHA1が全て数字となるものは397個あった。 期待値は388.64個なので、少し多めに見つかっていることがわかった。 7文字の中でも計算しようとしたが、時間がかかってプログラムが終了しなかった (後日結果を見てみたら、24061個見つかった。期待値は24095.86個、その誤差は0.15%未満となった)。

夕食後、SHA1ハッシュの文字分布を集計するプログラムを組んだ。 元メッセージは先程の計算と同じように、アルファベット大文字小文字もしくは数字で選んだ。 元メッセージの長さを軸に集計すると、次のようになった。

|X| 0 1 2 3 4 5
0 5 (12.500%) 133 (5.363%) 9551 (6.212%) 595152 (6.243%) 36950623 (6.252%) 2290283302 (6.250%)
1 1 (2.500%) 157 (6.331%) 9648 (6.275%) 595439 (6.246%) 36949237 (6.251%) 2290285498 (6.250%)
2 1 (2.500%) 139 (5.605%) 9539 (6.204%) 596209 (6.254%) 36946427 (6.251%) 2290302477 (6.250%)
3 3 (7.500%) 140 (5.645%) 9598 (6.242%) 596671 (6.259%) 36949064 (6.251%) 2290259456 (6.250%)
4 1 (2.500%) 164 (6.613%) 9727 (6.326%) 596720 (6.259%) 36943267 (6.250%) 2290339043 (6.250%)
5 4 (10.000%) 143 (5.766%) 9586 (6.234%) 595214 (6.244%) 36942983 (6.250%) 2290318196 (6.250%)
6 2 (5.000%) 175 (7.056%) 9673 (6.291%) 595193 (6.243%) 36931008 (6.248%) 2290360368 (6.250%)
7 1 (2.500%) 163 (6.573%) 9558 (6.216%) 595012 (6.242%) 36937662 (6.249%) 2290455010 (6.250%)
8 2 (5.000%) 149 (6.008%) 9691 (6.303%) 595441 (6.246%) 36950679 (6.252%) 2290411313 (6.250%)
9 4 (10.000%) 172 (6.935%) 9658 (6.281%) 596341 (6.255%) 36943415 (6.250%) 2290385579 (6.250%)
a 3 (7.500%) 168 (6.774%) 9797 (6.372%) 596924 (6.262%) 36936091 (6.249%) 2290363615 (6.250%)
b 3 (7.500%) 131 (5.282%) 9611 (6.251%) 595164 (6.243%) 36942624 (6.250%) 2290301647 (6.250%)
c 0 (0.000%) 186 (7.500%) 9417 (6.124%) 596098 (6.253%) 36928976 (6.248%) 2290281914 (6.250%)
d 3 (7.500%) 176 (7.097%) 9571 (6.225%) 596065 (6.253%) 36936884 (6.249%) 2290307306 (6.250%)
e 4 (10.000%) 153 (6.169%) 9567 (6.222%) 595888 (6.251%) 36930409 (6.248%) 2290299939 (6.250%)
f 3 (7.500%) 131 (5.282%) 9568 (6.223%) 595589 (6.248%) 36934091 (6.249%) 2290358617 (6.250%)

右に行くほど、つまり沢山のSHA1ハッシュを調べるほど、全ての文字が均等に現れることがわかる。 ある特定の文字だけ沢山出現したり、少なかったりすることはなさそうだ。 この結果に満足して、僕は就寝についた。

SHA1SHA1がだいたい数字

「おお、たしかに収束しているようだね」

昨日の計算結果を見せると、先輩は満足そうにそう言った。

「100を16で割って6.25%ですね」

岡村も頷いている。

「まあ証明したわけじゃないが…」

先輩は軽く咳をしてから頭を掻いた。

SHA1の文字は一様に出現することを仮定して、いろいろな期待値をもとめてみよう」

そう言いながら、先輩はテキストファイルを順番に開きはじめた。

LvwEJe1 6051096335827944381165360280845795286423 111613f48230408056a71706b4174442640b6489
sG9wqMH 9788371029031493501751484666965269206614 e6279615888204167759744340b6c13236273b37
AIJsWgU 4833765415800580402215357543416053382134 823885871864d85103339b7110420321b64b7901
HFrIKpt 1515438875168251864313109613925545684403 820316645881e3020f35e48662206491972878c8
wc9U6dv 5898389906639826050665528530112945836398 5966347988220353b91383721534804a3a278c21

多くの数字が並んでいるが、一番右のSHA1には少しアルファベットが含まれているようだ。

「これはなんですか」

思わず僕は疑問を口にした。

「これは、SHA1ハッシュが全て数字となり、そしてそれをさらにSHA1ハッシュを取った時に、アルファベットが4文字以下となるものだ」

岡村は少し混乱した様子で紙を手に取った。

「つまり…まずは全てが数字で… さらにSHA1ハッシュを取ったら40文字の中で4文字以下がアルファベットとなるので、こうなりますね」

\displaystyle \left(\frac{10}{16}\right)^{40} \sum_{k=0}^{4} {40 \choose k} \left(\frac{6}{16}\right)^{k} \left(\frac{10}{16}\right)^{40-k} = 6.687 \times 10^{-13}

先輩は空中で指を振りながら、計算が間違っていないことを確かめながら口を開いた。

「そうそう、元メッセージは昨日と同様、アルファベットもしくは数字の7文字以下で探索したものだから… その数を掛けて…」

\displaystyle \cdots \times \sum_{i=0}^{7} {62}^{i} = 2.394\cdots

「期待値はこれくらいになる」

「あ、少し低くないですか」

岡村がすぐに指摘した。

「期待値が2.4で、見つかったのが5個ですか」

「まあこういうこともあるさ。次のファイルは」

HQC8su9 03003251839018941492807233288828427481a2 3395664741163489868183a7392270978a505005
VByKdTE 027985658547959903b826850439976465234220 1a3268963b445974667880579063493748834739

SHA1ハッシュと、そのさらにSHA1ハッシュをあわせた80文字の中で、アルファベットが3文字しか含まれないものだ。2つしか見つからなかった」

\displaystyle \sum_{k=0}^{3} {80 \choose k} \left(\frac{6}{16}\right)^{k} \left(\frac{10}{16}\right)^{80-k} \sum_{i=0}^{7} {62}^{i} = 3.173\cdots

「今度は期待値が少し高いんですね」

僕はファイルのSHA1を見つめ、目を凝らしてどこにアルファベットがあるのかを探しながらそう言った。

「でも、昨日考えていた、全てが数字になるものは期待値通りでしたよね」

「だいたいね」

「要するに、3個とか4個とかの時はあまり合わないけど」

「沢山見つかるほど、見つかった数と期待値の誤差が小さくなるということですね」

「それはまあ、そうだろうね」

岡村は自分のディレクトリに移動してテキストファイルを開いた。

「ところで僕も探索してみました」

ZDyHlIM 5954449490599416666154915409164008649984

そのファイルには一行しか書かれていなかった。

「これは?」

「全て数字なんですけど、7種類の数字しか含まれていないんです」

「なるほど…」

先輩も身を乗り出して後輩が見つけたSHA1ハッシュを眺めている。

「5と9と4と… 0と6、1、それから8で… 確かに7つの数字みたいだね」

「そうなんです。アルファベットと数字7文字以下の文字列のSHA1の中ではこの1つしか見つかりませんでした」

「貴重だね」

僕も感心して思わず軽く口笛を吹いた。

文字連続

先輩は、また計算結果が書かれたファイルを開いてみせた。

dGtWcS d6141925ca72293fba0b5f43000000000000f70b
9J2DJv8 a9fc511111111111125d3d87910d68ddfe350f24
QDi79qF c80b072b3f2c2b6b34cb404cfc2555555555555a
cE2sXYZ d59938cf4afe4effffffffffff6f8181ef568bc1
y1m5aTz b60a1e6666666666665f09a5d8a0c05eb382dca0

「これは、同じ文字が12個連続するところがあるSHA1ハッシュだ」

「おおお」

「それから、13個連続するのも1つ見つけることができた」

rcb0lQv 3333333333333455f30f9a9f761fb8e94b906a0f

「なんかすごい」

「つまり、12個以上連続するものがいま6個あるのだけど…」

そう言いながら先輩はシャーペンを持って期待値を計算しはじめた。

「連続している文字が16通りで、その位置が40-12+1通りだから…」

\displaystyle 16 \cdot (40 - 12 + 1) \left(\frac{1}{16}\right)^{12} \sum_{i=0}^{7} {62}^{i} = 5.900 \cdots

「だいたい期待値通りですね」

岡村は期待値の式を眺めながら数式の意味を追っている。

「他にもこんなものも見つかった」

先輩はそう言いながらファイルを開いた。

w5PPbh5 222222222220101b01d906e32e61373b45265361
bzajR5G 555555555558a91de2fb2fa98af5048bfd887c65
daycahL 2222222222281376525c8dbab5a18e3ca933d5c3
Q4dDYWa fffffffffffa03a907d9650eb91bc99642a86199
2o0XCwe 888888888886d55909a93a870fe44fd91aea5392

「先頭11文字が同じSHA1ハッシュだ」

「おもしろいですね」

GitHubのリンクで、こんなの見つけたらスクショを取りたくなりますね」

「そうそう、こんなSHA1はおもしろい、ただそれだけでいいと思うんだよね」

さらに珍しいSHA1を目指して

部室に夕日が差し込んでいる。まもなく下校時刻になる。

「色々な性質のSHA1ハッシュが集まりましたね」

岡村は感慨深くため息を付いた。

「そうだね。まず考えたのが全て数字、二回SHA1を取ったもの、それから岡村くんが見つけてくれた、7つの数字しか含まれていないもの、先輩が計算した連続文字…」

「いろいろあるね」

先輩はしばらく夕日を眺めてから、口を開いた。

「全て同じ文字となる確率はどれくらいかな」

岡村はアドレスバーに式を打ち込んで答えた。

\displaystyle 16 \cdot \left(\frac{1}{16}\right)^{40} = 1.095\cdots \times 10^{-47}

「10の-47乗ですね。だいたい」

「なるほど」

先輩は頷きながら暗算してこう続けた。

SHA1が全て0となる確率は、それを16で割ると、6、いや7かける10の-49乗くらいかな」

「そうですね」

岡村は式を調整しながら答えた。

「じゃあ、いくつSHA1を探したらどのくらいの確率で全て同じ文字のものを見つけられるかな」

先輩は少し意地悪そうにこう言った。

「えっと、さっきのを1から引いて… あれ」

どうやら検索エンジンの誤差で計算できないようだ。僕はすかさずWolframAlphaを開いて計算してみせた。

\displaystyle \left(1 - 16 \cdot \left(\frac{1}{16}\right)^{40}\right)^{10^{46}} = 0.8963\cdots

「10の46乗個探すと、10%の確率で見つかる計算になります」

「1秒に10の7乗個のSHA1を計算できるパソコンがあるとしよう」

岡村はすぐに計算して結果を答えた。

「およそ3かける10の31乗年かかりますね」

「宇宙ができてから確か138億年だよね」

僕がそう言うと、すぐに岡村は計算結果を割って答えた。

「宇宙が10の21乗個できちゃいますね」

「そりゃあ、大変だなぁ」

先輩が夕日を眺めながらそう言うと、下校のチャイムが鳴った。どのくらいの性質のSHA1なら現実的な時間で見つけられるか、そんなことを議論しながら、僕らは帰路に着いた。

git grepで仕事してる

雑記

私はコードを書く時に頻繁にgit grepを使っていて、一日に何回くらいgit grepを使っているのか気になったのでログを取ってみました。

2016 10/24 月: 61
2016 10/25 火: 36
2016 10/26 水: 19
2016 10/27 木: 80
2016 10/28 金: 51
2016 10/31 月: 96
2016 11/ 1 火: 47
2016 11/ 2 水: 53
2016 11/ 4 金: 84
2016 11/ 7 月: 56
2016 11/ 8 火: 33
2016 11/ 9 水: 19
2016 11/10 木: 71

これは私が会社のPCでエディタの中でgit grepしたログを集計したものです。 結構ばらつきはありますが、40〜50回くらい、多い日で100回近くgit grepしているということが分かりました。 仕事の時間を割って平均したら大体5〜10分に一回はgit grepしていることになります。 もちろんコンスタントに使っているわけではなく、コードを書く時は増えますしミーティングがあれば減ります。 ですから、会議の多い日や設計段階を考えている日などは少なくなります。

私はVimScala・TypeScript・Goなどを書いています。 例えば、サーバーサイドを触る時にはコードとテンプレートを行き来するためにgit grepしますし、フロントエンドのコードも大体サーバーサイドと似たような変数名でモデリングしているため、git grepで関連ファイルを開いています。 JSONのフィールド名でもgit grepしますし、サイトのこの部分を変更したいという時は、クラス名を拾ってgit grepしてhtmlファイルを開きます。 ファクタリングや変数名関数名の変更も、まず最初にgit grepし、全て変更してからコンパイルします (手元で継続的にコンパイルするのはつらい)。 昔はcexprを使ってquickfixに流していましたが、今はlexprでlocation listに流してファイルを開いています。

他の人のコードのレビューする時は、動作確認したあとに、変更箇所の関数が使われている場所を必ず見るようにします。 関数に限らず、オブジェクト名でも、CSSのクラス名でも、サイトのパスの一部でも、なんでもgit grepします。

ある一つのプロジェクトのコードを長いこと触っていると、大体のコードの見た目が頭に入ってしまいいます。 DBでgroup byして引くときのORMのコードパターンはこんな感じなので…みたいな曖昧な記憶でも、git grepで目的のコードを開けたりします。 他のケースとして、エラーメッセージが出た時には、そのメッセージでgrepしてメッセージIDを見つけて、さらにそのメッセージIDでgrepするという二段git grepが発動します。

外部ライブラリも大体手元に落としてきてgrepするようにしています。 自分たちのコードからctagsで外部ライブラリに飛び、さらにそのリポジトリの中でgit grepするということがよくあります。

とにかくgit grepに頼っているので、たぶん自分はgit grepで仕事しているんだと思います。

コードを書いたり読んだりすることばかりが仕事ではないので、単純には仕事量の尺度にはなりませんが、しばらくログを取ってみて集計してみるとおもしろいことが分かったりするかもしれません。ぜひログを仕込んで回数を調べてみて下さい。あなたは一日に何回くらいgit grepしていますか?

スマホが割れた日

雑記

その瞬間は、前触れもなくやってきた。

いつものように、仕事帰りに烏丸御池の交差点で信号待ちをしていた。ちょうど赤に変わったタイミングで時間があったので、スマホでニュースを眺めていた。信号が切り替わり、そろそろ渡ろうとしてスマホをポケットにしまおうとした瞬間だった。左手と右手がいきなりぶつかり、右手の力が緩み、スマホは宙に舞った。

何が起きたのか自分でもわからなかった。はっと我に返ったら、スマホは硬いコンクリートに打ち付けられていた。

一体左手で何をしようとしたのだろうか、今となってはもう思い出せない。右手に痒みを感じて掻こうとしたのか、スマホを左手に持ち替えようとしたのか、あるいはゴミが入った目を掻こうとしたのか。両手が別の目的を持って動線を描いた結果、衝突してしまった。

精密機器がコンクリートにぶつかる瞬間、明らかによくない音がした。すぐに拾い上げて状況を確認したが、画面には無数のひびが入り、悲惨なものだった。全くなんでもないですよ、最近のスマホは衝撃には強いんですよと、周囲の人に心配されないよう冷静を装いながら横断歩道を渡ったが、画面の割れたスマホを握る手は震えが止まらなかった。

家に帰ってから改めて詳細に観察してみた。画面はもう見るも無残な姿だった。インクを流して蝶の羽根に見立てるのも悪くないアイディアだ、それくらい無数のヒビが走っていた。しかし枠は意外と無傷だった。いや、ほんの少しの傷はついていたが、枠が地面に当たったとは思えなかった。おそらく画面を下にして落下したのだろう。何よりもの救いだったのが、画面が割れた以外は、動作に全く問題がなかったことだ。落ちた直後に拾い上げたときも電源はついたままだったし、画面が割れていてもタッチパネルの操作は通常通り行うことができた。ただ、画面から少し変な油の臭いがした。

暫くの間、様々な思いが頭を駆け巡った。二年間大事に扱ってきたものを、一瞬の不注意で落としてしまったことが悔しかった。あの瞬間、なぜ両手がぶつかってしまったのだろう。反射的に足で受け止めていれば衝撃は和らいだだろうか、本当にインクを流してやろうか、流石にこの見た目はお客さんに会うときには恥ずかしすぎるな、自分でネジを開けて交換して直そうか、細かい紙やすりで削ったらちょっとはマシになるのではないか、新しい機種が出ているわけだし自分で直してうまくいかなければ新しいものを買えばいいんじゃないか。

シャワーを浴びながら様々なことを考えたが、改めて机の上においている無残な画面を見て、餅は餅屋、根は張るかも知れないが専門家に任せるのが一番だと言い聞かせて、修理の予約を取った。なんだ、ジーニアスバーだなんて小洒落た名前をしているじゃないか。

快晴だった。阿蘇山の噴火のニュースを明け方まで聞いていて、よく眠れなかった。2万円までは払う覚悟で銀行でお金を下ろし、阪急電車に乗った。帰省の時くらいしか使わないから、夏の盆以来だった。土曜日の昼間にも関わらず、満席で立っている人も多かった。

いつもならスマホでニュースや技術ブログを眺めるところだったが、画面の割れたスマホを電車の中で見るのはなぜか自分が許さなかった。未成年での飲酒は流石に良くないんじゃないかと無粋なツッコミを入れながら、新海誠の作品を読み耽った。孤立感と憧憬、美しい女性たちの繊細な心情描写にすぐに惹き込まれていった。

梅田は久しぶりだった。いつも帰省のときには十三で折り返してしまうし、そもそも梅田に用事があるなんてことはなかった。もしかしたら就活でスカイビルに来て以来ではないか。そう思った瞬間、就活の時の様々な嫌な思い出が蘇り、慌てて心から追い出した。もうあの頃の自分とは違うんだ。

予約時刻までしばらく時間があったので、ヨドバシで暇を潰すことにした。京都でもそうなのだが、ヨドバシに入るとまず必ず地下一階に降りる。ラップトップなんてめったに買わないものだし相場感覚が分からなくなるものなので、たまにはこうしてスペックや価格を眺めて歩くのだ。SONYのラップトップを片手で持ち上げてみたり、こんな薄いものに未だにLANポートを作る技術に感心したり、いつまでも変わらないメモリー容量に落胆したりした。少し奥まったところに自作用のパーツも並んでいた。自作PCというのは作ったことないのだが、ラップトップのHDDをSSDに交換したときから、部品の価格にも目を向けるようになった。

京都のカメラコーナーは一階なのに、梅田は二階なんだなと呟きながらエスカレーターを登る。特に買いたいものはないのだが、唯一持っているNicon 1用のレンズは欲しくなることがある。財布に入っている現金で買えるのではないかという邪念を振り払い、カメラコーナーを後にする。途中で円偏光フィルターが目に止まった。大学で量子力学をやっていたものだから、偏光板や波長板などは研究室に身近にあった。円偏光を直線偏光にして偏光板を通して… あんなに一所懸命研究したのにだんだん記憶があやふやになっている。必死にBloch球に右円偏光と左円偏光を配置する。大丈夫だ、まだ思い出せる。このフロアの中でこのフィルターを量子現象として説明できる者は他にはいまい。なぜか勝ち誇ったような気分でヨドバシを後にした。

f:id:itchyny:20161008135759j:plain

陽射しはいよいよ増していた。道端に銀杏が落ちていなければ、夏かと勘違いするような暑さだった。タワークレーンにレンズを向けてみたが、こんな陽射しの中じゃ新海誠にはならないな、そんなあたり前なことを思いながら南下した。地下鉄で6分だと書いていたので、徒歩でのんびり行くことにした。

f:id:itchyny:20161008141221j:plain

大阪の地理には疎い。神戸に生まれ育った身として三宮の商店街の店には多少詳しいのだが、なんせ大阪まではでてくる機会すらなかった。だから心斎橋とか難波とか言われても位置関係はわからないし、大阪城にはどうやって行けば辿り着けるのかも知らない。しかし、今日の目的地はとにかく御堂筋を歩いていけば着くはずだ。道路標識が、和歌山へと続く道だと主張していた。陽射しの中で歩いていると気分も明るくなり、和歌山には行ったことないな、いつか行ってみるか、そういう気分になった。

大江橋淀屋橋を超えると金融街に入る。銀行支店が道に並ぶ。伏見町はここにもあるのか、水が良いところには酒蔵があると聞くがここはどうなのだろうか、道修町というのはどうしゅうではなくどうしょなのか、難読だなぁとか、1ブロック渡るたびに新しい発見がある。

f:id:itchyny:20161008143044j:plain

本町通を超えて阪神高速をくぐると、また街の景色が変化する。フランク・ミュラーエルメス、スワロフスキー、オメガ、カルティエルイ・ヴィトン。畏れ多くも店の入口に店員が待機していて入ろうものなら扉を開けてくれる親切なお店が立ち並ぶ。夏なんかはあそこに立つのは暑いだろうなぁ。スーツくらいきちんと決めていつか行きたいものだな。そんな風に思って通り過ぎていると、短パンジーンズ麦わら帽子の兄ちゃんが彼女らしき人物を連れて出てきたりしてずっこけそうになる。

心斎橋はおそらく生まれて初めて来た場所だ。高級ブランド店の並ぶ北側よりも、心なしか南側のほうが活気があるようにみえる。人の交通をうまく交わしながら、目的の店へと足を運ぶ。

f:id:itchyny:20161008171021j:plain

店内は混雑していた。店員の数と客の数が1:1くらいなのではと思うくらい、店員がたくさん働いていた。それでもどの店員に声をかけたらいいものかよく分からず、しばらくうろうろしていた。サポートは二階だと聞かされ、オシャレな螺旋階段を登った。

二階も同様に混雑していた。なぜか電源がつかないとかパスワードが分からないとか様々な事情でここに来るんだろう。一時間ほど歩いてきたので少し汗をかいており、座るのが憚られた。しかし、予約時刻を過ぎてもなかなか呼ばれず、貧血で倒れそうになり、仕方なく隅の方に座らせてもらった。

しばらく観察していたが、様々な人が働いていた。男性で長髪の人も、背が高い人も、韓国語や中国語が流暢な人も、足の不自由そうなスタッフもいた。上はみなスタッフのシャツを着ていたが、下はジーンズが多かった。そういう企業文化なんだろうなぁ、いいことだと思った。それにしても少し混雑しすぎていないだろうか。

対応してくれたのは、自分と同じくらいの背丈の男性だった。どうやら3500円ほどで画面の交換をしてくれるらしい。2万円は覚悟していたから、かなりありがたかった。一時間後にできますと言われたので、再び街に散歩に出かけた。用を足したかったので東急ハンズに入り、申し訳程度にボールペンを購入した。一階はすっかりハロウィン模様だった。作りたてのポップコーンの臭いに吐き気を感じながら、その場を離れた。

心斎橋は東西に地下街が発達していた。もう夕方だが今日は朝食をとったきりだったし、一時間ほど歩いたので空腹だった。昼夜兼用にしてしっかり食べよう。学生の頃なら牛丼屋で済ませていたんだろうな。そんなことを考えながら、店を選ぶ。西の端から東の端はかなり距離があり、地下街を歩くだけでも疲れてしまった。地下街の方向と東西南北が数度ずれているのがとても気になった。

結局、東端にある洋食屋を選ぶことにした。少し照明を落としており、こじんまりとした店だった。セットを頼んでから、また新海誠を読み始めた。いい具合の照明と、店内にかかるショパンのワルツが、物語を趣深く装飾してくれて最高の気分だった。中華料理屋を選ばなかった自分を密かに褒め称えた。ハンバーグがとても美味しくて、どうやって作ったのか聞きたいくらいだった。ご飯はおかわりできると案内されたが、ガツガツ食べる気分でもなかったので遠慮してしまった。

人通り忙しない商店街を抜けて、また店まで戻ってきた。数分したら、修理されたスマホが出てきた。間違いなく自分のスマホだったし、それはもう見事だった。素人が開けるときにつけがちなドライバー傷もなかった。保険がなかったら1万を超えていたという。これを修理してくれた技術者は、経験を積み、腕の立つ、顧客の製品を修理するという責任を負うことができる人間に違いない。それは素晴らしい仕事だ。対応してくれた人に対価を払いたい。保険は対価報酬の関係を不確実なものにする。そんなわけの分からない思考も、御堂筋に吹く秋風に飛ばされて、心は澄み切っていった。

もう梅田まで歩く体力は残っていなかったので、帰りは御堂筋線に乗った。ホームボタンが少し固くなっていたり、タッチパネルの滑りが少し悪くなっていることも、部品が新品である証拠だった。あのヒビの入った画面とはもうおさらばなのだ。いつもは必死にタイムラインを消化するのだが、そんな気力もなかった。

阪急京都線に乗り座席につくと、疲れがどっと出てきた。対応してくれた店員の笑顔、一週間は予約がいっぱいだと聞かされて唖然としているおじさん、洋食屋で手をそっと添えてお釣りを渡してくれた店員、修理を待つために店内で座り込んでいた中国人たち、限られた時間内で修理しなければいけないという使命を負って仕事をしている技術者たち、高級宝石店の扉の向こうに立っていた執事のような人たち。色々な人生や考えを想像し、その立場に立って見える風景を思い描きながら、電車の心地よい揺れに誘われ、眠りに引き込まれていった。

Vim 8.0 リリース!

Vim 8.0 released!

Vim 8.0が先ほどリリースされました。10年ぶりのVimのメジャーバージョンアップです。

Vimのバージョンをcronで毎日上げ続け、最新のパッチを確認し続ける日々を送ってきました。そして、今日も夜11時のcronでバージョンが上がりました。新しいメジャーバージョン、8.0でした。

ここ一年はVimにとって様々な重要な機能が入りました。JSONエンコーダーとパーサー、パッケージ機構、channelとjob、タイマー、ラムダ式など、プラグイン製作者にとって大事な機能ばかりです。今後、より高度なプラグインがでてくることでしょう。これらの機能に対する日本人の貢献は素晴らしいものです。

リポジトリGoogle codeからGithubに移動するという重要な決定も行われました。この決定の過程にも、vim-jpの皆さんが深く関わっています。私は傍から応援することしかできませんでしたが、皆さんの貢献によりVimの開発が更に活発になっていくのを嬉しく思いました。

新機能のうちすぐにユーザーが設定したり使用したりできる機能もたくさんあります。 set breakindent を設定しましょう、インデントのある長い行の折り返しの見た目が美しくなります。検索中に<C-g>, <C-t> を押してみましょう、カーソルはコマンドラインのまま、ハイライトを移動することができます。ビジュアルモードのg<C-a>, g<C-x>で連番を作れるようになりました。便利な機能がいっぱいです。

個人的に嬉しかったのは、getwininfo()でlocation listかquickfix listか判別できるようになったことです。この2つはコマンドが全く違うのに、これまで正しく判別する方法がありませんでした。これからは自信を持って、これらを区別するコードを書くことができます。

また、最近入ったScalaのサポートも嬉しいものでした。私はVimScalaを書いています。プラグインを使えば何不自由なく書けるのですが、やはりVimに正式にScalaのサポートが入った (有名なScalaプラグインが本体に取り込まれた) ことは大事なことです。Scalaを書く道具を迷っている人に、胸を張ってVimという選択肢を提案することができます。

最新のパッチをずっと追いかけている人にとっては連続した道の通過点に過ぎません。8.0がリリースされた後も、矢継ぎ早に8.0.0001, 8.0.0002とパッチは上がり続けています。しかし、メジャーバージョンを上げるというBramの決意には、こんなにも素晴らしい新機能が入ったVimをもっと多くの人に使って欲しいという思いが感じられます。

私はVimを開発されている皆さんが好きです。Vimプラグインを書かれている皆さんが好きです。Vimを使っている皆さんが好きです。

そしてVimが大好きです。新時代の幕開けに、祝杯を。

JavaScriptのsetTimeoutをログに出す

JavaScript

setTimeoutは難しい。いつ呼ばれるかよく分からないし、ライブラリーを使うとそのライブラリーがsetTimeoutを使いすぎてしまう。よく分からなかったけどsetTimeoutすると動くからそうしていた、んだけど実はタイミングの関係で偶然うまく動いているように見えているだけだった、なんてこともよくある。

ウェブアプリケーションの描画が遅い。「なぜか遅い」が、処理を丁寧に追っていっても手がかりがつかめないということがある。色々な方法を駆使した後に、なぜかsetTimeoutの発火が遅いということにたどり着いた。どれくらい遅いか。

window.setTimeout = (function(setTimeout) {
  return function() {
    var handler = arguments[0];
    var wait = arguments[1] || 0;
    var log = function(action, color) {
      var d = new Date();
      console.log('%c%s%c [%c%d%c/%c%d%c] (%s %s) %s', 'color:' + color, action, '',
                  'color:blue', (Date.now() - registered), '', 'color:blue', wait, '',
                  d.toLocaleTimeString(), d.getMilliseconds(), handler.toString().slice(0, 100));
    };
    var args = [].slice.call(arguments, 2);
    var registered = Date.now();
    args.unshift(function() {
      log('FIRED', 'red');
      handler();
      log('DONE', 'blue');
    }, wait);
    log('REGISTERED', 'green');
    return setTimeout.apply(null, args);
  };
})(window.setTimeout);

setTimeoutをログに可視化してみた。コードの中で使っているあらゆるsetTimeoutのログが出力される。 [X/Y] みたいな表示がされるが、 Y がsetTimeoutの第二引数で、 X がsetTimeout登録からの経過時間を表している。

f:id:itchyny:20160822124018p:plain

ああ、最悪だ。setTimeout 0のつもりが2秒も後に発火されている事がわかった。

本来ならば setTimeout(f, 500);REGISTERED [0/500]FIRED [500/500]DONE [530/500] のようになるのが正しい (f()に30msかかる時)。ブラウザー描画処理が入る可能性があるので、setTimeout 0でもFIREDが [20/0] とかになることはある。しかし、上の画像のように2秒も遅れていると「何かがおかしい」ということが分かる。

setTimeoutが可視化されたのは良いが、実際の原因は別のところにあることが多い。setTimeoutが意図しているよりも遅れるということは、そこで別の重い処理が走っているということになる。その処理とは、自分の書いたJavaScriptの処理だったり、大きなDOMの挿入、ブラウザーレンダリングだったりする。あとは、ブラウザーのTimelineやProfilesといった普通のデバッグ手法をとる。

console.logで時刻を出すというのは確かに原始的なやり方だ。printfデバッグと同じである。しかし、setTimeoutのタイミングが目に見えるのはとても便利だ。以前作ったsjspも同じように古典的な手法だが、今でも重宝している。

実行処理系のデバッガと、古典的な手法を行ったり来たりして原因を特定していくというのは、どの言語でもある気がする。どちらも良いところと悪いところがある。デバッグ技・プロファイル技は職人芸になりがちだが、文章にして伝えにくいところもあるので、どうしても特定の人にスキルが偏ってしまう。うまく伝えていきたい。