僕は僕にどういう教育を授けたか

まえがき

会社の若い子に「情報系出身でもないのに一体どうやって勉強してきたんですか?」と聞かれたのでランチを食べながら「こんな本読んだ。これもタメになった。あ、これもタメになった」とKindleを広げながらリストアップした。思い返せばたくさん本を読んだ。その中には役に立ったものもあれば時間の無駄だったものもある。すると「あ、役に立った本だけ抽出したら有益かもしれないな」と思ったのでエントリにする。

僕は文章を簡潔に分かりやすくまとめる才能が致命的にないのでこのエントリもげっそりするほど長い*1が、2017年も暮れなのでここはひとつ日本酒でもかっ喰らいながら自分の人生を振り返ってみようと思う。

無理やり要点をまとめるならば、

を身につけたらどんなに低く見積もっても僕と同程度のプログラマをつくるレシピには充分だと思う。具体的な書籍名も挙げてあるので誰かの人生に勇気を与えられたらいいな。

 

僕について

あー超言いたくないので今まで隠してたんですが僕は英文学部出身*3なんですよ。チョムスキーとか読んでました。割と自由な学科だったので英文学と英語学に飽きてからは通訳の授業を取ったりしていた。

数学は1A2Bまでしかやっていない。生物と化学はちょっとだけ覚えている。物理はまともにやった記憶すらない。コンピュータサイエンスの教育は1秒も受けていない。

ピアノと耳コピが趣味でMIDIにハマったのがきっかけでそれを発表するためのサイトを始めた。そのうち第三者の作品投稿を募るようになってPerlCGIを書き始めた。どこかで買ってきた本*4を見ながら無料公開された掲示板CGIを元にコピペと改造を繰り返してサグラダファミリアを建築していた。

3年次に大学を中退。前述のプログラミングもどきをきっかけに知り合った大学の先輩が起業したのでその会社で働き始めた。

 

インフラ

会社での僕の業務は雑用全般だったのでクビになることを恐れて誰もやり手がいなかったLinuxサーバ管理をし始めた。当然すべて物理サーバ。当時使っていたのはRedHat 9。そして出たばかりのFedora Coreに乗り換えてyumコマンドで依存ライブラリも含めて一発でインストールできることに感動したのを覚えている。ここではいくつか重要な知識を身につけた。

この業務に5年間たずさわったが、ほぼ次の本を読めば同等の知識を獲得できる。 

TCP/IPの絵本 ネットワークっておもしろい!

TCP/IPの絵本 ネットワークっておもしろい!

 

右も左もわからない人がざっとインターネットプロトコルスイートについて雰囲気を知るのに良い。

 

ネットワークはなぜつながるのか 第2版 知っておきたいTCP/IP、LAN、光ファイバの基礎知識

ネットワークはなぜつながるのか 第2版 知っておきたいTCP/IP、LAN、光ファイバの基礎知識

 

ルーティングの基礎がちゃんとわかる。

 

マスタリングTCP/IP 入門編 第5版

マスタリングTCP/IP 入門編 第5版

 

マスタリングTCP/IPを入門編でも読めればTCPダンプも怖くない。

 

CentOS7で作るネットワークサーバ構築ガイド (Network server construction gu)

CentOS7で作るネットワークサーバ構築ガイド (Network server construction gu)

 

ここだけ、この本を手放しに推奨するということじゃなくて「自分の使ってるLinuxディストリビューションApache/Nginx, Postfix, Bind, Samba等々を『全部入りで』解説されてる本」で自分にあうものを1冊探して欲しい。僕は現在はもっぱらDebianばかり使っておりRedHat系を推奨するという意図はない。

 

プログラマ転身

インフラのかたわらで会社のサーバサイド開発を手伝うようになってちょっとずつプログラマとしての経験値を貯めていった。僕のおすすめのアプローチは「何かひとつ『これさえあればとりあえず安心して何か書ける』ようになるまでひとつの言語を集中して頑張ること」だ。僕の場合はそれはJavaだった。

Java、なぜか今日では古くさくてイケてない言語の代名詞みたいに若い人に思われてそうだけど2017年現在ですら充分有力な選択肢ですぞ〜。

  • C++やRustを除くと最速の部類の実行性能を誇る
  • JVMGCのチューニングや運用実績も豊富
  • コアライブラリの代表的なデータ構造*6は種類も豊富だし実装も参考になる
  • ジェネリクス
  • 強力なスレッドサポートとconcurrentパッケージ
  • 昔でいうとAWTやSwing、いまならJavaFXとかでスタンドアロンGUIアプリケーションも書ける
  • AndroidJavaライクな言語で開発できる
  • 巷に開発者が多く、枯れた知見や強い静的型付けのおかげでチーム開発にも向いている
  • 別にやりたきゃFat Jarにして配布したりスクリプト処理だってできる

ちょっと最後は無理やりか!ほとんどのプログラミング言語は本質的にやろうと思えばなんだってできるね。

とにかく僕の場合はJavaだったわけだ。ジェネリックプログラミングやマルチスレッド処理、GUI開発の知見なんかは僕の現時点での最大の武器であるAndroid開発にも非常に役立った。 

いまからJavaを身につけるのは何がいいんだろうな…とりあえず

 Javaメモ目次(Hishidama's Java Memo)

の2つには大変お世話になった。Javaは「とりあえず使い始める」という点でちょっと分量が多いように感じられて*7初学者はウッ…となるかもしれないので何でも良いので本屋さんで簡単なのを買ってみるというのも手です。

 

改訂2版 パーフェクトJava

改訂2版 パーフェクトJava

 

これを手放しに推奨というわけではないけど何も知らない人は一旦ざっと俯瞰できる本を何か買ってください。始めた頃の僕みたいな実力だったらもっと入門的なものでも良いかもしれない。

 

EFFECTIVE JAVA 第2版 (The Java Series)

EFFECTIVE JAVA 第2版 (The Java Series)

 

これはJava書きのバイブル。Java 8, 9時代の内容を反映した3rd Editionも原著(英語)はすでに予約を開始してる。

 

新装版 リファクタリング―既存のコードを安全に改善する― (OBJECT TECHNOLOGY SERIES)

新装版 リファクタリング―既存のコードを安全に改善する― (OBJECT TECHNOLOGY SERIES)

 

僕はリファクタリングをチーム開発、使いやすいAPI設計、テストを書きやすい構造等々のすべての高品質なプログラミングの基礎だと考えており、この本は一歩前に進むために必読であった。Javaが題材なので手に取りやすい。

 

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

 

言語に依らずシンプルで分かりやすいコードを書くためのテクニックが簡潔にまとまっている。新入社員には必ず薦めている。

 

Java並行処理プログラミング ―その「基盤」と「最新API」を究める―

Java並行処理プログラミング ―その「基盤」と「最新API」を究める―

 

これ絶版なのが惜しくて惜しくてしょうがない。Javaが持つ強力なマルチスレッドプログラミングをjava.util.concurrentパッケージを使って安全に使うためのテクニックがすべて詰まっている。また「スレッドセーフとは何なのか」という観点から、単純にロックを安全に取る方法のみならずトラバースを安全に行うことに特化したアプローチなども学ぶことができる。

 

無職、そして東京へ

さて5年経って会社もIPOを目指すスタートアップからただの中小企業と化してしまったし僕も自分の人生に行き詰まりを感じて最初のスタートアップを退職した。*8

僕はちょっとだけ無職をしつつ自分が次になにをすべきか考えていたが、ふと「そういや大学3年まで取った単位もったいねえな。どっかに入り直すとして1回生からやり直すカネはないし引き継げたりせんかな」と思って中退した大学の教務課に行ったところ「確かに中退してるけど、キミ、5年以内なら無試験で復学できるね。ああ、来月末で5年だね。」と言われ、そのあまりのタイミングの良さに運命を感じて大学に戻ることにした。この時は手に職があったので知人経由で割のいいプログラミングのバイトを貰って働きつつ1年で卒業した。*9 

この間に後に嫁さんになる女性とコミケで出会った。向こうは同人誌を作る側、こちらは同人誌を買う側である*10。奇しくも冬コミ初日の今日これを書いているのは感慨深いな。大学卒業後よめさんを追いかけて東京で職を探した。

 

ネイティブアプリ開発

ウェブの会社に拾ってもらってここで技術者としての基礎を全て叩き直された。京都時代にちょっと触ってたAndroid開発を本格的にやるようになった。ネイティブアプリ開発にはいくつかコツがあることを知った。

スレッド間通信

ネイティブアプリ開発のコツというとライフサイクルコールバックとか非同期処理とか貧弱なネットワークやハードウェアリソースなんかが挙げられると思うけど、最も大切かつ既存のほとんどすべてのGUIフレームワークに共通する仕組みがある。それがイベントループとメッセージパッシング機構でありこれらは取りも直さずスレッド間のメッセージのやり取りために存在している。

イベントループ

あるスレッドに関連付けられイベントやメッセージを待ち受けてる人。AndroidではLooperがこれに相当する。MessageQueueというその名の通り届いたメッセージをキューに積んで処理したりする。

メッセージ

スレッド間でやり取りされるメッセージ。AndroidではMessageというそのままの名前。

ハンドラ

イベントループにメッセージを届けたり処理を記述するための仕組み。AndroidではHandlerというこれまたそのままの名前のクラスがある。

 

なんでこれらの知識が必要かというと、こういった仕組みはGUIフレームワークに普遍的な考え方であり何かトラブルが起こった時の調査の取っ掛かりになるばかりか、まったく新しいGUI開発に従事した際にも充分に応用が効くようになるからだ。ここに挙げたものはiOSにもまったく同じものがあるしJava Swingにもあったし未来に生まれる次世代のスマホOSでも同じものが提供されるであろうことが想像に難くない。

ここまで分かるようになってくると、UIスレッドというのがそういう名前が付いただけのシングルスレッドであることも分かるし、反対にメッセージループの機構が単に別スレッドからUI部品を更新するためだけのものではなく任意のスレッド間の柔軟なメッセージングに使えることが分かる。

ちょっと脱線したけどAndroid開発の参考書籍をいくつか。

いきなり書籍じゃないけど「Androidの入門書なにがいいか?」って一番困る質問なんだよね…すでに僕はAndroid入門者じゃないので当時の気持ちが思い出せない…

ただこのトレーニングコースは無料で簡潔かつ本質的であり、今なお色褪せぬAndroid開発の極意をごく短期間で身につけることができる。

どうしてもIDEのインストールから手取り足取りやりたいひとはAmazonで適当に検索して評判の良さそうなものをなんでも1冊手にとって見ればいいと思います。

 

黒帯エンジニアが教えるプロの技術 Android開発の教科書(ヤフー黒帯シリーズ)

黒帯エンジニアが教えるプロの技術 Android開発の教科書(ヤフー黒帯シリーズ)

 

ヤフー黒帯本。Androidに限らずiOSもそうだと思うが公式の方法と現場で使われているデファクトスタンダードの乖離が初学者の挫折ポイントだったりすると思うので、最近の知見をサッと1冊で詰め込めるのはおいしい。

 

Androidを支える技術〈I〉──60fpsを達成するモダンなGUIシステム (WEB+DB PRESS plus)

Androidを支える技術〈I〉──60fpsを達成するモダンなGUIシステム (WEB+DB PRESS plus)

 

Androidの内部に切り込んだ世界でもこれ以上の書籍はないと思われる名著。I巻はGUIにフォーカスを置いている。前述のHandlerのような話題もはるかに深く掘り下げられている。

 

Androidを支える技術〈II〉──真のマルチタスクに挑んだモバイルOSの心臓部 (WEB+DB PRESS plus)
 

II巻はActivity(iOSのViewControllerに相当)に関する内容。普段何気なく利用しているActivityが誰によってどのように生成/破棄されているのかと言ったライフサイクルの心臓部の解説がある。

 

Androidを支える技術の別冊「詳説 Binder」。すいませんこれ怒られるかもしれないけどまだ読んでなくて正月休みに読む予定なんだけど、AndroidにはBinderというプロセス間通信の仕組みがあって普通にIDEの参照ジャンプで辿っていくだけだとこのBinderの層でプッツリと途絶えてその先を見つけられないのだ。

ここを読むにはAndroidのなかみ InsideAndroidという書籍*11の解説とかを見ると助けになるんだけど、著者の有野さんも

と仰っているしdex.fmでご本人が熱弁しておられた様子をお聞きしても名著であることを確信したのでここで紹介した。

 

アルゴリズムとデータ構造

たぶんここまで来るだけでモバイルアプリ開発者としてなんとなく食っていくには充分だと思うんだけど外資に転職したくて勉強したアルゴリズムとデータ構造について理解を深めるにつれ「これは順番が逆だった!これを知ってからプログラミングをやるべきだった」という思いに強く駆られたので紹介しないといけない。

 

アルゴリズム

アルゴリズムはある問題を解くためのアプローチだ。数列を昇順に並べ替えるアルゴリズム木構造から目的のデータを素早く探すためのアルゴリズム等がある。

 

データ構造

アルゴリズムを実装する際に効率的に処理するためにデータの集まりを都合の良い状態に整えたもの。整列済みの配列は立派なデータ構造だし、平衡二分木もB+木も赤黒木もデータ構造である。

 

で、普段こんなの使わんでしょ?と思いきや全然そんなことなくて、自分で実装することはほとんどないかも知れないけど普段のプラグラミングやサービス開発でよく使うものでこれらアルゴリズムとデータ構造の恩恵を受けていないものは存在しないほど身近で重要なものだったりする。

普段使ってるRDBMSファイルシステムはB+木のお陰で挿入も削除も検索も高速だし、全文検索エンジンはトライ木の一種のサフィックス木で実装されてるかもしれない。

身近なプログラミングでダントツに重要なデータ構造としてHashMap(Dictionary)があると思うけど、普段これがどうやって実装されてるか*12考えたことがあるだろうか?

このあと紹介する参考書籍とかを読めば分かるけど、HashMapはキーのハッシュ値をモジュロ演算して固定長配列*13に割り当てて、ハッシュ値の衝突は連結リストの後ろにつなげていくだけで実現されている。それぞれは非常にシンプルなデータ構造とシンプルなアルゴリズムを工夫してこのようなベンリなデータ構造を作ってるわけである*14

またAndroidにはキーが任意の型Tではなくint固定の代わりにメモリ効率が良いとされているSparseArrayというデータ構造があるが、これは内部では2分探索木が使われている。つまりエントリが爆発的に増えればおそらく挿入時にはSparseArrayの方が有利だし検索時にはHashMapの方が有利であることを示唆している*15

こういう知識はいざ自分で新しい何かを生み出せばならないときに強力な持ち駒として力を発揮するはずだ。

 

この本は何個か読んだ中でも比較的とっつきやすかった。使い慣れたJavaだし。

 

なっとく!アルゴリズム

なっとく!アルゴリズム

 

この本はアルゴリズム本の中で特別読みやすかった。図もたっぷりで軽妙な語り口。それでいて動的計画法のナップザック問題あたりまでもカバーする骨太さ。言語はPythonだが読めないということはまずないだろう。

 

アルゴリズムクイックリファレンス 第2版

アルゴリズムクイックリファレンス 第2版

 

僕が読んだ中で最も広範かつ深くアルゴリズムについて学べる本。いきなり「アルゴリズムの数学」と題して計算量とは、最良・最悪・平均性能とは何なのかみっちりと解説してくれる。その後他のアルゴリズム本にもあるような整列、探索、グラフに始まりAI、ネットワークフローの深きまでがっつり入っていく。これをマスターすれば怖いものなし。僕はマスターしていない!

 

AtCoder代表のchokudaiさんによるTopCoderSRMに挑むための訓練本。やや難易度が高いけど実際のプログラミングコンテストに挑むための力をつけることができる。外資企業の面接で間違いなく力を発揮する。

 

UNIX/Linux

個人のPCレベルだとWindowsがまだ世界で90%ものシェア*16を持っててまさに圧倒的という言う他ないんだけど、ことサーバ環境においては7割近くがUNIX系OS*17という調査もあるようで元インフラ出身の僕の肌感覚とも近い。それにiOSDarwinというUNIXだしAndroidLinuxをベースにしていることを考えるとUNIX/Linux力は非常に重要だと思う。

ここでUNIX/Linux力といった場合単純にコマンドを扱えるよりもうちょっと踏み込んだ点を指していて、具体的には次のようなことだ。

  • 入出力
  • ファイル(inodeとか)
  • プロセスのフォークやシグナル
  • pthreadと同期
  • プロセス間通信(名前付きパイプとかUNIXドメインソケットとか)

で、これらは普通にコマンドを利用してるぐらいでは中々身につけるのは難しくて、やっぱりglibc越しにシステムコールを呼ぶようなプログラムを書かないといけない。

かつて一回だけ本番環境のMySQLが原因不明のタイミング*18でネイティブクラッシュを繰り返して生きた心地がしなかったんだけどstraceコマンドでシステムコールトレースをつぶさに追ったところRDSのホスト名が長すぎて名前解決キャッシュのテーブルの最大バイト長を超えて書き込もうとしてセグメンテーション違反を起こしていたのを突き止めたことがある*19

 

詳解UNIXプログラミング 第3版

詳解UNIXプログラミング 第3版

 

上に挙げたようなことをすべて身につけることができる本。900ページぐらいあってめっちゃムズイけどCを書いてコンパイルしてっていうのは実は生まれて初めてだったので楽しかった。全部やるのは無理だった。

 

詳解UNIXプログラミングのLinux版かつテーマをグッとしぼった感じの本。初学者はこちらの方がずっとオススメ。grep等のよくあるコマンドを自分で実装してみることができる。

 

教養

直接なにか利益を受けるつもりで学んだものではないんだけど回り回って血となり肉となった知識がいくつかあって、その中のひとつが関数型プログラミングだ。

特にHaskellモナドを学んで将来確定する計算結果をパイプライン処理するというような考え方はAndroid開発時にRxJavaを見た時にすんなり理解できる助けになってくれたりして大いに役立った。また昔は特に苦手だった再帰も、スタックフレームを消費する末尾呼び出しとアキュムレータを用いた最適化可能な末尾再帰を使い分けることができるようになったりと得るものが多かった。再帰的な考え方は再帰的なデータ構造やアルゴリズムを考える時に脳の体操になるので非常に役立つ。

 

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

 

たぶんHaskellの入門書で圧倒的に一番分かりやすい。Haskellの機能をシュールな絵とともにのらりくらりと解説してくれるんだけど、Functor, Apprecative, Monoidとステップを踏んで最終的にMonadを作るところまで学ぶことができる。

 

ScalaをBetter JavaというよりはかなりHaskellっぽく使うための様々なアプローチが紹介されている。随所に練習問題があって難しいが素晴らしい本。

 

Land of Lisp

Land of Lisp

 

スポンジボブのようなシュールな絵でLispの歴史の解説に始まり最終的にCommon Lispを使ってちょっとしたゲームまで作ってしまう面白い本。S式という連結リストと関数だけというかなり割り切った言語設計とカッコだらけの見た目でかなりぎょっとするが、再帰を使って連結リストに関数適用していくという関数型プログラミングの醍醐味のようなものを学ぶことができる。

 

フロントエンド開発

むしろ僕が教えていただきたい…僕のフロントエンドの知識はjQueryprototype.jsで停まっている化石状態だ。

ただ来春の僕のミッションに素早いプロトタイピングが加わるのでReact Nativeでクライアントサイドを、Cloud Functionsでバックエンドを書いていこうかなと思っているので双方に共通する技術スタックとしてTypeScriptを速習予定だ。

 

TypeScript実践マスター

TypeScript実践マスター

 

とりあえずこれはパラパラ読んでみたが初学者には良さそうだった。

 

数学

数学の出来なさがかなり僕のコンプレックスになっており自分なりに少しずつ努力している。僕はビッグオー記法のO(logN)の意味が最初わからなくて指数対数からやり直したレベルだ…こちらは完全に教養として本当に少しずつ進めているので進展があったら共有したい。

このエントリで拝見した高専の数学は良さそうである。

 

やっていき

いや〜長すぎてもう誰も読んでないと思うので最後にちょっと好きなことを書きます。

自分がこれだけ路傍の石ころみたいな状態からプログラマになってこうしてなんとか食ってこれたのは、やっぱり諦めなかったからだと思う。僕は常にいまの自分が許せなかった。いまの自分の不甲斐なさが悔しかったのだ。これは裏を返せば自分の可能性を信じているわけだ。まだやれるはず、まだ道は続いているはずだと信じている。

ちゃんと教育を受けた人が大学で4年、修士で2年、博士で3年だとしてざっと10年。僕はこのような専門教育は受けていないがかれこれ12年自分で自分にコンピュータサイエンスを教え続けてきた。努力を積み重ねれば彼らに追いつくのも不可能とは限らないのだ。毎日は1㍉1㍉の積み重ねだが12年経って振り返った時に「気付かないうちに随分登ったなあ」と小高い丘から目を細めながら過去の自分を見下ろせればそれで良い。

22歳からでも、いや30歳からでも遅すぎるということはないのだ。自分が今日より若い日は今後一生ないのだと思うと何歳からはじめても今日が最善の日だ。やっていこう!

 

情熱プログラマー ソフトウェア開発者の幸せな生き方

情熱プログラマー ソフトウェア開発者の幸せな生き方

 

技術書以外はめったに読まないんだけどとても好きな本。オフショアでの衝撃的な経験や生きるためにコードを書くということ、採用に対する考え方、エンジニアとしての幸せなど示唆に富んだ本。

 

最後に

2017年は本当にお世話になりました。来年はちょっと今まで目を背けつづけてきた機械学習にチャレンジせねばと思っています。どうか良いお年を。

*1:マジで長くて1万字を超えちゃって笑ってしまった

*2:身も蓋もないけどコレはホント。しかし運は待つのではなく無理やりつかみ取りに行くのです。

*3:だから英語に苦手意識がないんですね〜。けどこの時の英語力はハナクソみたいなもんだったのでその後のトレーニングの方が大事。

*4:これはもはや記憶にない。何でも良かったのだ。

*5:29ビットマスクのグローバルIPをやりくりしていた

*6:List, Set, Hash, Dequeなんでもいいんだけど、Arrayを元にしたものやLinkedな実装など用途に応じて自在に使い分けできる。僕はアルゴリズムとデータ構造を学んでからJavaのコアライブラリの凄さを見直した。

*7:リテラルも弱いし配列やコレクションもRubyPythonとかより使いにくくてとっつきづらいよね…

*8:僕を引っ張ってくれた恩人の先輩もそれからほどなく共同創業者に株式を売り払って東京に出ていった

*9:思えばこのとき2年かかってもいいから理学部情報学科にでも編入試験を受ければよかったな〜。でも学士を取れてよかった。

*10:「ここここのあとののの飲みにいきませんか」みたいなことを言った。通報も逮捕もされなくてよかった。

*11:こっちは読んだ!

*12:もっというとどうやってO(1)の検索を実現しているか

*13:ホントは然るべきタイミングでリサイズしないといけない。卜部先生のブログに詳しい。

*14:これを理解するとどうしてMapが挿入順序を持たないか簡単に分かるだろう

*15:ただし一般的なアプリで現実的なデータ量では2分探索木でもO(1)に近い性能が出ると思われる。参考サイト

*16:https://news.mynavi.jp/article/20170904-a057/

*17:https://w3techs.com/technologies/overview/operating_system/all

*18:後にレプリケーションのタイミングと判明

*19:これは --skip-name-resolve オプションを付けていればそもそも起きなかったミスではあるが…

現職のままシリコンバレーに赴任する

電撃的な展開により会社からシリコンバレーのラボへの異動を命じられた。現職のまま任期付き(恐らく2年)の赴任となる。

オフィスはパロアルトだが家賃が高く到底住めないのでサンマテオとかその辺りに安アパートを借りることになると思う。何ひとつ分からないのでどうかご指導賜りたい。

 

それから東京の家を完全に引き払う予定なのでドジョウの里親を探している。もし助けてやっても良いという方は本エントリの一番最後までスクロールしていただければ幸いである。

(14時23分追記:里親見つかりました。本当にありがとうございました。)

 

経緯

12月も半ばになろうという頃、突然すごいエライ人に呼び出されてパロアルト赴任に興味はないか訊かれた。ちょうど失意のどん底だったので「行きたいです!」と答えた。翌週経営会議で承認されてそのまま内示となった。スピード感!

 

葛藤

ただここからウジウジとナメクジのように悩むことになる。

ラボといっても技術者の赴任は弊社初とのことだ。当然プログラマは僕しかいない。業務内容は

  • 新規事業の発掘およびそれに関わる技術調査、助言
  • プロトタイピング
  • 現地企業との共同研究、連携

等と聞いている。プログラミングをしている時間はざっと50%以下になるそうだ。必要な英語力や交渉力もプログラマとして求められるそれとは別次元のものになる。

 

2017年は僕にとっては確変状態みたいな年で信じがたいほど多くのチャンスがあった。

モバイルアプリバブルの最末期。34歳。恐らくキャリアの絶頂。2年後同じような好条件で引き合いのある可能性の方が低いのだ。そんな状況で内資企業の、プログラミングを主としない業務に2年間を費やして良いのだろうか?

 

ビッグボス

赴任先で二人三脚でやっていく予定のボスが一時帰国したので面談した。次のような失礼千万な問いを率直に投げかけた。

 

「僕は元々アメリカに行きたかったので2年後任期が切れたときにそのまま別の会社に転職しちゃって帰ってこないかも知れないです。そんな男をわざわざ行かせますか?」

 

「うーんそんじゃあさ、新規事業がうまくいきすぎて現地にジョイントベンチャーでも設立してそこの社長になって帰れないぐらいのところを目標にしない?君だって2年間ずっと受け身で言われたことだけをやって終わるつもりはないだろう?」

 

このひと言に震えるほど感動した。必ずこの2年間で会社に利益をもたらすのだと誓った。僕は身勝手な人間だったと反省した。プログラムを書くとか書かないとかではなく、どこに居て何をしようが自分の向き合い方次第で僕自身もきっと成長することができる。

 

キャリア感

2017年は30代前半最後の年であり自分のキャリアについて深く考えさせられた。僕はスタートがどん底*1だったのでこの10年とにかく這い上がるだけだった。

ようやく「これがプログラミングの世界か」とその入口に立った頃、こりゃ大学からちゃんとビジョンを持って然るべき教育を受けてきた連中と今から素手で殴り合うのは得策じゃないかも知れんとちょっと考え始めた。別に自分のプログラマとしてのキャリアを諦めた訳ではなくステータス振り分けの問題で、要するに三国志許チョを知力に振るのは無駄な努力ってもんなので、僕はもうわずかにしか残っていないパラメータを僕を最も輝かせる方向に賭けるしかないのである。

そのひとつとしてビジネスデベロップメント力をこの2年で養うのはきっと悪くない選択だと信じたい。どの道止まっている時間はない。いのち短し歩けよ中年!

 

ドジョウの里親を募集しています

f:id:fushiroyama:20171227073027j:plain

東京の家を引き払う予定で色々調べたが米国まで生体を持ち込むのが法的にもロジスティクス的にも著しく困難であることが分かったのでドジョウの引き取り手を探しています。内訳は次の通りです。

  • マドジョウ * 2
  • シマドジョウ * 1
  • ミナミヌマエビ * 3
  • ヒメタニシ * 3
  • ヒメダカ * 6

水槽、LEDライト、外掛けフィルター、水交換ポンプ、バケツ、予備の川砂、予備の餌等あります。

引き取りはこれらまとめて1セットです。引き取って育ててくださる方は @fushiroyama までDMください。DMはフォロー関係になくとも送受信できるよう解放しています。東京23区内は僕が車で直接お届けできます。もし直接引き取り*2に来てくださった方には技術書が2冊買える程度のAmazonギフト券を寸志として贈らせてください。

(14時23分追記:里親見つかりました。本当にありがとうございました。)

*1:大学を中退して京都の小さなスタートアップで5年を過ごしその後無職になって色々あって今に至る。この話はまたどこかで。

*2:東京都江東区

アメリカ就職に失敗したはなし

前口上

アメリカで就職できなかった。華々しい成功譚は見かけるが、夢と散った話はあまり表に出てこない。

なんというか「三振したバッターが相手ピッチャーのことを語る」みたいでまるっきり時間の無駄かもしれないが、もしかしたら参考になる人もいるかも知れないし、実際に就職した人に「お前のアプローチはまったく的外れだ」と言われるかも知れない。僕も何が悪かったのか教えてもらいたい気持ちもあるし、迷ったがこのエントリを公開する。

ちなみにめっっっっちゃ長いので、要点だけ知りたい人は、アメリカで就職するにはとにかく

 

就労ビザ>技術力>学歴>>>>>>>>>>>>(越えられない壁)>英語力

 

だというのだけお伝えできればと思う。

アメリカで働くために英語を頑張るぐらいなら、それより大学(院)に入り直してコンピュータサイエンスの学位をとり*1、同時に技術力を磨くほうがよほど近道だと感じた。

 

それから、現職の同僚はこのエントリをみて微妙な気持ちになると思うので、その点について最後に「現職について」で補足する。

 

秋風五丈原

じいちゃんが死んだ。大正・昭和・平成の3つの時代を生き、たくさんのひ孫に囲まれて90年超の幸せな人生を脱稿した。しかしそれでもなおこのことは僕にはショックだった。「あー人生ってマジで1回なんだ。知ってたけど知らなかったわー」という感じ。

漠然とした概念としての死が、自分の人生が必ず最後に到達する終着駅だとやっと実感をもって気付いた。

 

ここで僕は、やりたいことはとにかく何でも試してやりきってから死のうと決めた。そのとき思いついたのが「アメリカでソフトウェア技術者として通用するか試したい」ということだった。僕にはそれに至るちゃんとしたロジックとかはなくて、とにかく自分の目で見て自分の足でアメリカの大地を踏みしめ、自分の鼻でSFの雑踏のマリファナまみれの風を吸い込み、自分の手でソフトウェア技術者としての痕跡を残したかった。

 

ちょうどその頃堤修一さんのフリーランスを休業して就職しますというエントリを発見したり、身近な何人かのプログラマが渡米したりして僕もあのようになりたいとトランペット少年のように思った。早速Creators Learning English Meetupというイベントに帰国中の堤さんが登壇されるということで、突撃して彼を捕まえて質問攻めにした。

それから具体的な就職活動について考えた。

 

就職活動

どうしていいかさっぱりわからなかった。どこに応募したらいいのか分からない。

とりあえずこの時はアホだったので、さる世界的な多国籍企業東京支社にソフトウェア技術者として応募した。なんか、そのうち転籍でもできるんじゃとか思ったのである。いま振り返ると筋が悪すぎる。

ちなみに割と早い段階で早々に落とされた。ただこの経験はしておいてよかったなと後に思う。ここの面接でアメリカ企業というのは基本的にコンピュータサイエンスの基礎力を応募者に強く求めるということがわかったからだ。

 

それからもしばらくは具体的なアクションが起こせず、うーんどうしたらいいんだ?とかつまらないことを考えていたんだけど、事件が起きる。ある日LinkedInに海外企業のリクルーターから「当社のSenior Android Engineer職に興味はないか?」と直接コンタクトが来たのである。

 

この会社は事前に課題として、実際にその会社のフラッグシップアプリで問題が起こりやすい部分をスマートに解決するための簡易な実装を提出するように求めてきた。僕は自分が優秀であるとアピールするために、組み込みライブラリを組み合わせればまあサラッとできそうなものをわざわざ自前でドバーッと書いて提出したところ、採用担当者はこれを痛く気に入ってめでたく本面接に進むことができた。

 

面接はすべてSkypeGoogle Hangoutsを使ってリモートから英語でなされた。この時も英語なんてカタコトかつ単語の羅列で何の問題もなくて、とにかくコーディングにつぐコーディングだった。ある人は整列や探索の簡単なアルゴリズム*2の実装を求めてきたし、HashMapがどう実装されているかかなり踏み込んだ説明*3を求めてきたりしたし、あるいはわざと罠を仕込まれた作りかけのAndroidアプリを共有されて、画面共有ツールで僕がどうやって正しい実装にたどり着くか監視されたりした。

 

この会社は2人ぐらい突破したんだけど、その後急にリクルーターが代わって

「ところでビザのステータスは?」

「え?ステータスとは?君たちがサポートしてくれると思ってた」

「うーん残念だけどいまはUSの就労ビザはサポートできないんだ」

と言われてなんとそこですべてのプロセスが終わってしまった。

 

このときもしかしてビザとはとんでもなく重要な要素なのでは…?と初めて気付いた。

 

ビザ

それからアメリカの就労ビザについて色々調べた。僕がお世話になる可能性のあるビザは次の3つ*4だ。

  • H1B(専門職ビザ)
  • L1(駐在員ビザ)
  • E2(投資家、またはそれを補佐する専門職ビザ)

H1B

専門職ビザの花形である。米国の企業が発行する。

米国はITの聖地であり、技術者は全然足りていないんだそうで「その足らない技術者を海外の人材で賄うため」というお題目でGoogleとかMicrosoftみたいな世界に冠たるIT企業はH1Bをバンバンサポートしてくれるらしい。

就労する業務に深く関連のある学士以上の学位を持っている必要がある。望むらくは修士以上。博士は優遇される。

L1

日本企業の社員が駐在員として米国支社に転籍するような場合に発行してもらえるビザ。その会社に1年以上在籍している必要がある。

日系企業がアメリカ支社に社員を送り込む際の最も一般的な方法のひとつ。

E2

日本の起業家が自分自身に投資してアメリカで起業したり、それを補佐する専門職者に対して発行される可能性があるビザ。L1と違って最低在籍期間のようなものがなく、すぐに発行することが可能。

 

他にもトレーニングビザというものがあるらしいが、詳しいことは僕は知らない。

また、Diversity Programといって、アメリカが移民の多様性を推進するために移民の申請が少ない国の人を対象に申込者の中から純粋なるくじ引きを行って当選者には永住権の申請権を与えるという面白いプログラムがある。こちらはまさに運任せなのと当選率が低い*5ので参考程度に。

 

転機、そして内定

さて、その頃たまたまGoogle I/OでSFに行ったときにDeploy NIKUというイベントでお会いしたDrivemodeのCEOの古賀洋吉さんにどうやったらアメリカでソフトウェア技術者として採用されて就労ビザを発行してもらえるか相談したら、

「片っ端から応募しまくりました?してない?どうして?LinkedInとかで募集している会社に『初年度は給料はこのぐらいで我慢してやるから就労ビザだしてくれ』って100社ぐらいにメール送ったら5社ぐらい返事くれるんじゃないですか?」

と言われた。なるほど、ぼくは全然行動力が足りていなかったのだ。

 

それからちょっと気が楽になって、少しずつ友人のツテとかを頼ってアメリカの会社に応募するようになった。具体的には

  • 僕がアメリカで働くことに興味があること
  • 出向ではなくアメリカ企業からの直接雇用を望んでいること
  • そのためにたとえば即時解雇のリスクがあることや試験内容が現地基準で難しくなっても問題ないこと
  • その代わり待遇は現地基準にして欲しいこと

を伝えた。

 

また、同時にLinkedInのプロフィールを英語で充実させて、自分が経験豊富なプログラマであり転職の意志があることを明示した。具体的には

  • 自分が経験のある言語、フレームワークを年数込みで列挙
  • 転職に興味があること
  • これらを英語で詳述

した。これはもっとも効果のある施策だった。

LinkedInは転職斡旋リクルーターからのコンタクトばかりくるが、1割ぐらい企業のHRが直に連絡を取ってくれる。リクルーターが「弊社に興味がないか?」と聞いてくるのは「試験を受けないか?」と言ってるだけなのでそれ自体に特に意味はないのだが、僕のように関連学位をもたない人間は書類審査でバンバン落とされるので、少なくとも相手企業のHRが連絡を取ってくれた場合は電話面接を受けることができる。そうすれば実力次第で次のステップにすすむ希望が生まれる。

 

実際に数ヶ月でヨーロッパのスタートアップから数社、アメリカのスタートアップから数社、国際的な多国籍企業からもいくつかInterviewのお誘いを受けた。ひとまずは友人のツテとこの中から興味のある3社ぐらいに絞って面接を受けた。

面接は相変わらずコンピュータサイエンスの基礎知識とアルゴリズムクイズが中心で、対策のしがいがあった。少なくとも、

のような準備が有効だった。

 

そしてついに、行きたい会社から内定がもらえた。オファーレターを見てその金額や福利厚生に興奮したし、「いかなるときも当社都合で解雇できるものとする」というような内容に戦慄したりした。

 

挫折

内定はゴールではなかった。ビザがおりないのである。

ビザの手続きは先方の弁護士に任せていたのだが、H1Bが簡単にいかなそうで他に色々利用できる可能性はないか探っているようだった。これはたぶん本当で、向こうも採用という莫大なコストをかけて内定を出した候補者はどうにかして入社までこぎつかせたいようだった。

 

これは推測にしかすぎないが、自分の学位が足を引っ張ったのではないかと想像する。僕はコンピュータサイエンスの学位を持っていない。数学や物理学といった関連学位も持っていない。

アメリカは日本以上の学歴社会である。学歴がひとつの「免許」として機能している。「おれ無免許だけど車の運転うまいっす」とかいう奴を誰も相手にしないのと同じだ。これはとても健全なことだ。大学というのがきちんと社会の求める役割を果たしているのだ。H1Bが業務に関連する学位を必須条件として定めていることは非常に合理的だが僕にとっては悪いニュースだ。

もしかしたらトランプ政権もタイミングが悪かったのかもしれない。トランプ政権下では移民ビザをとにかく制限する方向に動いている。H1Bもアメリカ人の雇用を守るためにかなり厳しくなるという話だ。

 

先方の人事とやりとりしつつ半年待ったが「率直に言って、来年4月のビザは絶望的だ」という話を受けて、こちらから正式にお断りの連絡を入れた。こちらは4歳と0歳の子供がおり、僕の身の振り如何で保育園も妻の復職もすべてが左右される。いつまでも宙ぶらりんで居るわけにはいかなかった。無念だ。人生はままならぬ。

 

こうして僕のアメリカ就職は一旦頓挫することになった。

 

振り返り

とにかく就労ビザがどれだけ大切か思い知らされた。どれだけ面接でうまくやってもビザがなくては何も始まらないということがよくわかった。

学歴もかなり大切で、僕が安易に「自分が行けそうなところで偏差値が高い大学をなんとなく選ぶ。学部はどこでもいい」というようないい加減な高校生活を送ってしまったことを非常に強く後悔した。

突出した技術力や実績があればこれらは挽回のチャンスがあるが、僕のような凡人が凡人なりに就職するには大学の勉強とそこから必然的に導かれる就職先というストーリーは非常に大切だった。

 

家族のこともありしばらくはこういったアクションは起こさないが、もうあと5歳でも若ければコンピュータサイエンスの大学院を受けていたかもなーという感じ。

 

現職について

ここまで書いて、現職を辞めるわけでもないのによくもまあいけしゃあしゃあとこんなエントリを書けるなと呆れる向きもあるかも知れない。これには少しフォローを入れたい。

 

まず、僕は現職を非常に気に入っているし採ってもらったことを深く感謝している。優れたボスと同僚に恵まれ、プロジェクトはいつも創造意欲を掻き立てられるし、みんなメリハリをつけて働いていて余程のことがない限り残業もない。待遇も日本にいる限りにおいては申し分ない。

 

それとは別で「会社と従業員は対等だ」という僕のキャリア感がある。

僕は僕にしかなしえない技術力を提供し、会社はそれの対価として報酬を払う。僕は常にプライドを持って問題を解決し、素早くアプリをつくり、会社に貢献してきたつもりだ。そしてその度合というのは「勤続年数」で測られるものではないと思っている。

 

会社が僕に居て欲しいと思い、僕が会社に居たいと思う。両者の利害が一致して契約が持続される。これがシンプルで良いのではないか。

祖父の死でカッとなって色々足掻いてみたが、結局僕は現職に残ることを選んだ。そして会社はまだ僕に力を貸して欲しいと言ってくれる。もう少しお世話になります。

*1:金銭に余裕があるならアメリカの大学(院)を出るとインターンシップ等で利用できる就労ビザが取得できるようだ

*2:NDAにサインしたので詳しい内容は書けないが、ソートのアルゴリズムを直接実装せよというようなものはなかった。ただ、O(n log n)の代表的な探索アルゴリズムついて基本的なアイディアを問われたことはある。

*3:Arrayとキーのハッシュ値を用いた基本的なアイディアや、コリジョンの解決方法の複数のアプローチ等

*4:これ、僕はど素人なので微妙に間違ってたりするかも知れないし、詳しくは自分で調べるか弁護士に相談して欲しい。

*5:応募総数と当選者から割り出した当選率はたしか公式サイトに公開されていて、それによると0.8%ぐらいだが、実際には当たったひと全員が永住権を申請するわけではないのでもう少し多めに当選を出しているとの噂

エンジニアの英語力

TL; DR

  • どれだけ努力しても"ネイティブ並"は無理なので諦めが肝心
  • エンジニアは英語ができなくても話を聞いてもらえるので「伝える意思」と「分かったか分かってないかを絶対に曖昧にしない」こと

 

謝辞

このエントリは弊Android Projectのビルド待ち時間を使って書かれています。Android Studioさんに感謝します。

ビルド待ち時間にブログを書かれたくない場合は弊社は僕に全部盛りiMacを買ってください。

 

前口上

英語力に関するエントリは盛り上がりやすく荒れやすい気がするんだけど、それはやっぱりみんな英語は出来たほうがいいに決まってるしさりとて英語を身につけるのは難しいよねってことが分かってるからだと思う。

 

僕はエンジニアの中では比較的英語が得意な部類に入ると思うけど、それでも全然充分だとは思わない。ただ、これでやっていけないか?というと全然そんなことはないので、一番重要なのはエンジニアには何が期待されているのかというのを改めて認識しなおすことで、手持ちの英語力でも幸せに生きていこうではないかということが言いたい。

 

僕について

僕はもう10年以上プログラマをしている。職種はソフトウェアエンジニアである。

旅行を除いて1週間以上海外にいたことがなく、外資系企業での勤務歴もない。

英語力はTOEIC最高点が890。ここはサバを読んでTOEIC900マンとしてくれ、頼む。英検も準1級を持っている。

ここまで書いて「英語強者だ。解散!」とか「出た、ただの自虐風自慢」というのは簡単だけど、このエントリには僕がTOEIC900に至るまでの道のりと、実はそれは不要であることの両方を記した。良かったら読み進めて欲しい。

 

エンジニアの英語力について

まず、一番最初にエンジニアに必要な英語力について書いておきたい。

エンジニアにとって英語力はあるに越したことはないが、なくても別に死なない、ソフトクリームのあのカラフルなつぶつぶチョコレートぐらいのもんだということだ。

 

で、最初に言い切ってしまうと、エンジニアはエンジニアリングができるというただ一点においてすでに価値があるので、エンジニアが英語ができなくても相手は辛抱強くコミュニケーションを取ってくれるという事実だ。

弊社にはイギリス人、アメリカ人、ニュージーランド人といった英語を母国語とする同僚が多数いるが

  1. エンジニア同士の場合、コードは英語より雄弁なコミュニケーション手段となる
  2. 相手が非エンジニアの場合も、作るのはこっちなので無碍にはしてこない

ことが分かっている。

 

従って僕はエンジニアは

  1. 英語の一次情報(ライブラリやRFC等)をいかなる手段を用いても構わないので読めること
  2. 相手とどうしても英語を話す必要があるときはいかなる手段を用いても構わないのでゴールについて合意できること

が達成できればなんだって構わないと考える。

いかなる手段を用いても構わないので、前者はGoogle翻訳を使えばいいし、後者は同僚に通訳を頼んでもいい。僕はふざけてなどいない。エンジニアとのコミュニケーションミスで本来とは違うものを作ってしまう損失は通訳のそれよりゼロ2桁ぐらい大きい。

 

肝心なのは、君にコミュニケーションの意思があることと、いま訊かれており合意しなければならないことが分かったかどうか確実にすることだ。

相手が日本人ですら「ちょっとそこ仕様がフワフワしててわかんないっす」とか確認するじゃないですか。相手が違う言語を話してたら尚更だ。「よくわかんないけどウンって言ってきた」って、文字で書くとアホなの?と思うけど実際には多くの人がやっちゃう。

 

「わかんなかった」

「あ、そこはわかった、後半わかんない」

「こういう理解でいい?」

 

ってしつこく確認すること。これは英語力の問題ではなくコミュニケーション意思の問題だ。そしてそれが何より大事だと思う。

 

僕は海外企業で働いたことがないけど、実は海外企業に内定をもらった*1ことがあって、その時の面接で「英語で困る」なんてことはまったくなかった。

もちろん流暢にはしゃべれるわけないんだけど、別に単語の羅列でも相手が求めているアルゴリズムをホワイトボードにスラスラ書けることの方が肝心だ。

 

僕は周りのものすごいエンジニアたちが英語ができないという思い込みで海外に挑戦しない例を見てきて「なんてラッキーなんだ!エンジニアの価値はエンジニアリング力にあるのに、そんなことで諦めてくれるなんて!ライバルが減った!」ぐらいに思っている。

このエントリで書いちゃったけど、このエントリを読んで海外企業受けたよありがとうと言ってもらえる方がもっと嬉しいかな。

 

どうやって実践的な英語力を身につけるか

とはいえ英語は出来たほうが有利だ。ここでは僕がいかにしてTOEIC900の英語力を身に着けたか紹介する。

僕のアプローチはただひとつ。「理解できる英語をひたすら繰り返す」だ。

 

僕はスピードラーニングの完全否定者である。英語は聞き流すだけでは絶対にできるようにならないが持論だ。想像してみてよ、なんでも良い、例えばアラビア語の音声を10年間毎日聞いたとして、アラビア語が理解できるようになると思うか?

 

僕が考える良い英語の教材は次の条件を備えていることだ。

  • 英語母国語話者が英語で解説する
  • 内容はゆっくり平易で、辞書なしで9割理解できる
  • 1レッスンに2〜3個わからない単語が出てくる

英語母国語話者による英語の解説

これが非常に肝要で、英語を読み上げて日本語で解説するタイプの教材は日本語の解説を聞いた時点で満足しちゃって記憶に残らない。

英語を英語で解説することがどれだけ大事かというと、これは知らない単語を別の平易な説明で言い換えることができるということだ。これはいざ自分が直接知らない単語をどうにか相手に説明しないといけない場面で役に立つ。

 

内容はゆっくり平易で辞書なしで理解できる

これはある程度意味がわかった状態で「単語の字面」と「ネイティブな音」とを結びつける必要があることを意味する。実は知っている単語が、ネイティブの発音ではそれと認識できなかったという経験はしばしばすることになる。

単語と音が結びついてくると、今度は未知の単語を「聞いただけで」だいたいのつづりが想像できるようになる。あるいは、けったいなつづりでも辞書に向かって音声入力で聴いたとおりしゃべりかけるとスペルを教えてくれるようになる。

 

いきなりABC Newsを聞くようなトレーニングは速すぎて取りこぼしの方が多いと個人的に考える。平易でゆっくり過ぎると中級者は「ちょっとこれは簡単過ぎるのでは…」と不安になるかも知れない。

人間は9割わかると前後から内容を類推することができるのでほとんど理解できたような気になるが、逆に言うと1割分かっていないということは各レッスンで1割ずつ未知の内容を習得できるということだ。

 

僕のオススメはESLPodである。

サイトが分かりにくいのが難点だが、僕は15ドル/月の有料会員で、Select Englishというコースから15本好きなプログラムをDLできるので「Daily English」という、日常でよくあるシチュエーションの会話を通じて単語や表現を学ぶコースから10本、「Cultural English」という、アメリカの歴史や偉人といったアメリカの文化を学ぶコースから5本それぞれ毎月DLしてスマホに入れてウォーキングしながら聴いている。

 

僕はこのサービスが有料化する前からのユーザで、実に7年間このレッスンを聴いている。人間、7年間も続けたら大抵のことはできるようになるのだ。

月15本で1本20分ぐらいなので、毎日聴いていると10日ぐらいでやりきってしまう。その場合は繰り返し聞く。何度でも繰り返し聞く。そうすることで記憶が強固になる。イディオムが勝手に口をついて出るようになる。

僕がこれまでにした英語のトレーニングにはESLPodただひとつだけだ。他に何一つしていない。それだけでTOEIC900取れる。TOEICなんてTOEICの点数を上げるための対策すらしていない。そんなことは無意味だ。TOEICの点を上げることそれ自体には何の価値もない。

 

シャドウィングと英語思考

前述の英語リスニングに慣れてくると、今度はそれに合わせて気になる表現や単語をブツブツとシャドウィングしてみよう。聞くのと話すのはやっぱり違う。聞こえたのと同じように言えるようになるまで何回でもブツブツしゃべる。英語には日本語にない音がいくつもあるので、最初はうまく口が動かない。冗談みたいだけど口が攣ったりする。練習あるのみである。

 

ゆっくりしゃべってくれるのに合わせて自分でも言ってみるのを繰り返していると、対面で英語話者とスピーキングのトレーニングをしていないのに不思議としゃべれるようになる。とにかく重要なのは

  • 言葉のシャワー(理解できるものじゃないとダメよ!)を浴びまくって、イディオムが脳内でスッと出てくるようになる
  • 何回もシャドウィングして口が慣れてくる

これを繰り返すとしゃべるほうも結構イケるようになってくる。

 

僕のオススメは日常生活で「これ英語で何ていうかな」と妄想しながら生きるトレーニングだ。普段からそういうことを考えていると、いざ英語話者を前にしたときに自然な流れで言いたいことが言える。

「まずは日本語で考えてそれを英訳する」ステージから「脳内で英語で直接考える」ステージに移行できればしめたものだ。あとは少しずつ教材の難易度を上げていくだけだ。グッドラック!!!

 

TOEIC900について

さて、これまで散々言ってきたTOEIC900ってどのぐらいの英語力なんだろうかと思った時に、自分の例で言うと

  1. 英語母国語話者と知的な会話は無理
  2. ジョークを言い合うなんてとても不可能
  3. ニュースや新聞も満足に視聴できない

レベルである。

 

たとえば、ABC NewsやBBC NewsはPodcastで無料で視聴できるのでいますぐ聴いてみて欲しい。TOEIC900程度ではたぶん7割ぐらいしか分からない。

新聞も、例えばFinancial Timesを買ってみて欲しい。辞書なしに1コラム満足に読むことができないと思う。

TOEIC900ってこれっぽっちの英語力なのだ。英語ができるという物差しにしては短すぎる。

 

ニュース、新聞は日本でもそうだと思うけど、格式張った知的な単語や言い回しが頻出する。そしてその新聞やニュースの内容を議論するようなビジネスマンが、知的な単語を使ってないわけがないのである。同僚のイギリス人同士が政策について議論しているような場では、まるっきり何をしゃべっているのかチンプンカンプンだ。

同僚の英語母国語話者が久しぶりに同郷の同僚と話していて「いや〜、久しぶりに"Intellectual(=知的)"な会話をしたよ^^」って彼に言ってるのを聞いてちょっと傷ついたものだ。

 

ただ、ことエンジニアと仕事をする上では、彼らはちゃんと「第2外国語としての英語話者」に向けて平易な単語や表現をわざわざ選んでしゃべってくれるし、こちらが分からなくてもある程度譲歩してもらえる。これは僥倖だ。我々エンジニアは恵まれた世界線にいるのだ。

 

まとめ

最後にまとめると、やっぱり英語は極めようとするには難しすぎるのである程度妥協しないとキリがないということかな。

英語ってたぶんエンジニアにとって目的ではなくて手段であるはずで、目的というのはエンジニアとして幸せに仕事したいとかそういうことだと思うから、手段にこだわりすぎるあまり目的がおろそかになるのはやっぱり不幸なことだと思うので、エンジニアリングの方にフォーカスしていきましょうということです。

 

最後とっ散らかっちまった…

ビルドしつつちょこちょこ書いたのでつながりも悪いかもしれないけどまあいいや。じゃあの。

*1:この会社はビザがおりず、半年待ってついに諦めることになった。この悔しさとその時の経験はいつか別エントリにする。

Android 4系(API16-19)のTLS1.1, 1.2対応

TL; DR

  1. API16-19はデフォルトでTLS1.1, 1.2が有効になってないので適宜ONにしてやる
  2. TLS1.1, 12を有効にしたとて、強いCipher suitesが使えるかは別問題

知ってる人は知っている。知らない人は覚えてね。

前口上

さて、iOS 9からTLS1.2が必須になったのは記憶に新しい。このタイミングで社内のAPIサーバの設定が変わって芋づる式に対応に追われたAndroiderも少なくないはずだ。

本件、僕自身もすぐ忘れるのでAndroid 4系(API16-19)のTLS1.1, 1.2対応について改めてまとめておきたい。

Default configuration for different Android versions に書いてあることが全てなんだけど、AndroidAPIレベルとSSL/TLSの関係は次のとおりだ。

  • SSLv3…使うな
  • TLSv1…API 1からサポート、API 1から有効
  • TLSv1.1…API 16からサポート、API 20から有効
  • TLSv1.2…API 16からサポート、API 20から有効

TLS 1.0で検索すると、各社サポートを打ち切る方針を打ち出していることが分かる。

このタイミングで最低でもサポートするAPIレベルを16を下限としたいところだ。

サーバの設定を確認

さて、TLS 1.0を切ると決めたらApacheなりNginxなりでサポートするTLSのバージョンを設定するはずだ。ここではそれについては解説しない。ここではcurlコマンドでちゃんと設定が正しくなされているか確認してみる。

MacOSデフォルトのcurlはOpenSSLが組み込まれてないので再インストールする。

 

% brew install --with-openssl curl

% brew link curl --force

% cat<<'EOS'>>~/.zshrc
export CURL_HOME="/usr/local/opt/curl"
export PATH="$CURL_HOME/bin:$PATH"
EOS

% source ~/.zshrc

% curl --version | grep -i openssl
curl 7.57.0 (x86_64-apple-darwin16.7.0) libcurl/7.57.0 OpenSSL/1.0.2n zlib/1.2.8

 

これでOK。

curl -I --tlsv1.2 -s -v "https://your_server"

こんな感じで --tlsv1.1, --tlsv1.10 でそれぞれ試してみよう。

サポートしていないバージョンで

error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure

みたいになればOK。

Android側でどうするか

で、Android側でどうするかが関心事だと思う。

ここではOkHttpをHTTPクライアントに使った場合に絞る。

 

まず、SSLSocketFactoryを拡張して、TLS 1.1, 1.2を有効にする以外はほとんど処理を元のクラスにdelegateするだけのファクトリクラスを作る。

private static class TLSSocketFactory extends SSLSocketFactory {

private SSLSocketFactory internalSSLSocketFactory;

public TLSSocketFactory() throws KeyManagementException, NoSuchAlgorithmException {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, null, null);
internalSSLSocketFactory = context.getSocketFactory();
}

@Override
public String[] getDefaultCipherSuites() {
return internalSSLSocketFactory.getDefaultCipherSuites();
}

@Override
public String[] getSupportedCipherSuites() {
return internalSSLSocketFactory.getSupportedCipherSuites();
}

@Override
public Socket createSocket() throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket());
}

@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
}

@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
}

@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
}

@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
}

@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
}

private Socket enableTLSOnSocket(Socket socket) {
if (socket != null && (socket instanceof SSLSocket)) {
((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.1", "TLSv1.2"});
}
return socket;
}
}

 

次に、X509TrustManagerを取得する処理をコピペする。

private X509TrustManager getTrustManager() throws NoSuchAlgorithmException, KeyStoreException {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
}
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
return trustManager;
}

 

あとはいつもどおり。

ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS).build();
OkHttpClient okHttpClient;
try {
okHttpClient = new OkHttpClient.Builder()
.connectionSpecs(Collections.singletonList(spec))
.sslSocketFactory(new TLSSocketFactory(), getTrustManager())
.build();
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
throw new RuntimeException(e);
}
return okHttpClient;

 

これでこのOkHttpClientを使ったHTTPリクエスト/レスポンスはTLS1.1, 1.2を使って行われる。

 

なお、Builder#sslSocketFactory に

Most applications should not call this method, and instead use the system defaults. Those classes include special optimizations that can be lost if the implementations are decorated.

と明記されているので、この部分を自前で書き換える場合はパフォーマンス上の不利に留意しつつ、 Build.VERSION.SDK_INT で条件式でくくったりする方が良さそうだ。

Cipher suites

冒頭で書いたように、TLS 1.1, 1.2に対応することと、充分に強度のあるCipher suitesを使えることは別問題である。

サーバ側で低APIレベルでは対応していないCipher suitesしか許容しない設定にした場合、本記事の内容を適用してもSSL Handshakeに(当然ながら)失敗する。


SSLSocket | Android Developers

を見て、どのCipher suitesを許容するかAPIチームと相談しつつ、最低APIレベルをどこで妥協するか決めるのが望ましそうだ。以上。

 

参考リンク

 

「書き直した方が早い」は9割のケースで間違いだった

はじめに、本エントリは特定の企業、チーム、個人を指して書いたものでは一切ない。100%僕の個人的な経験から来ている。

さて、職業プログラミングに従事していると一度は「これ書き直した方が早いっす」とか言ったことある気がする。自分の場合、多くは歴史のあるレガシーコードを読んだときだ。思い返せば、自分がこう思ったときはほとんどそれは間違いであった。

「なんだこのコード…」
「これ何書いてあるか分かんないっす」
「うーんこれもう書き直した方が早くないっすか?」

この流れは非常に危険だ。
なぜならプログラムというのは本質的に書いてある通りにしか動かないからだ。ちゃんと読めば絶対に何を書いてあるかは分かる。

ここで安易に選んだ書き直しという選択は、自分が慣れ親しんだやり方でその部分をそっくり置き換えるというだけで、それは他人にとってあたらしい「これ何書いてあるかあるか分かんない」を生む結果にしかならない。

ここで取るべき行動は、注意深く辛抱強くコードを読み、解きほぐし、チームメンバやテックリードに相談し、リファクタリングすることだ。安易に「書き直す」なんて言ってはならない。少なくともそこに「何書いてあるか分かるまで」書き直すなんて言えないはずだ。

うわー読みづらいなー、つらいなー、書き直したいなーと思ったら、その行動が実装、テスト、QA含めて前のコードと同等の品質に持っていけるか熟考と見積もりを重ねて、経営陣に納得してもらえるだけの説明がつくか、その覚悟はあるか、思い返したいところだ。

自戒を強くこめて。

Date, Last-ModifiedヘッダがありCache-Controlが不適切な場合OkHttpが思いもよらないCacheをするので注意

(お詫びと訂正)

本件、「思いもよらないCache」ではなく、RFCに示された通りの実装でした。以下に追記します。

RFC7234 4.2.2 Calculating Heuristic Freshness にすべて書いてあるので詳しくはそちらを参照して欲しいですが、サーバが文書の失効に関する明示的な情報を何も返さない場合、キャッシュは他のヘッダを用いてコンテンツの鮮度をヒューリスティック*1に設定するかもしれないとのことです。

RFC7234の当該項目にたとえば代わりに"Last-Modified"を用いることやヒューリスティックな計算結果は最大10%にすることなど何もかも説明がありました。つまりOkHttpがこのようなキャッシュ戦略を取ることは思いもよらないでも何でもなくRFCの勧告通りということです。お詫びして訂正します。

Twitterにて指摘してくださった @hydrakecat さんありがとうございました🙇

 

以下、それを踏まえてお読みください。追記ここまで。

 

 TL;DR

随分長いタイトルになったが、次の条件を満たす場合にOkHttpのCacheが思いもよらない挙動をする場合がある。

  1. HTTPレスポンスに"Date", "Last-Modified"ヘッダが両方指定されている
  2. HTTPレスポンスの"Cache-Control"ヘッダにno-cache, no-store, max-age=0*2 のいずれもが未指定
  3. HTTPレスポンスの"Expires"ヘッダが未指定
  4. "Date" - "Last-Modified" が長い期間(たとえば3日間)

このとき、端的に言うと、思いがけず長い期間Cacheが破棄されずネットワークリクエストすら送られずローカルのCacheを見続ける

 

な…何を言っているのかわからねーと思うが、俺も何をされたのかわからなかったので順番に書いていく。

 

事の発端

そもそもこれに気付いたきっかけが、弊社アプリでCloudFrontに置いた小さな設定ファイルを見に行っている部分がファイルが更新されてもいつまで経っても再DLされず、よく観察してみるとリクエストすら飛んでないことが発覚したためだ。

ファイルにcurl -Iして見ると、たしかに"Cache-Control"ヘッダは設定が漏れている。

しかしOkHttpで"Cache-Control"ヘッダがない場合のキャッシュ戦略がどうなっているか、ソースを読むより方法がない。

 

CacheStrategy

OkHttpのキャッシュはCacheInterceptorという内部Interceptorによって制御されており、その名の通りCacheというクラスによってリクエストURLをキーにしたDiskLruCacheとして実装されている。*3

 

そしてCacheの動きを決める最も重要なクラスがCacheStrategyである。

CacheStrategyはこれからネットワークに向かっていくリクエストとヒットしたキャッシュエントリを受け取って色んなヘッダを見て、クラス名が表すとおりキャッシュ戦略を決定する。

 

まず、CacheStrategy.Factory がレスポンスヘッダをフィールドにマップしていく。

this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
Headers headers = cacheResponse.headers();
for (int i = 0, size = headers.size(); i < size; i++) {
String fieldName = headers.name(i);
String value = headers.value(i);
if ("Date".equalsIgnoreCase(fieldName)) {
servedDate = HttpDate.parse(value);
servedDateString = value;
} else if ("Expires".equalsIgnoreCase(fieldName)) {
expires = HttpDate.parse(value);
} else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
lastModified = HttpDate.parse(value);
lastModifiedString = value;
} else if ("ETag".equalsIgnoreCase(fieldName)) {
etag = value;
} else if ("Age".equalsIgnoreCase(fieldName)) {
ageSeconds = HttpHeaders.parseSeconds(value, -1);
}
}

"Date"ヘッダが servedDate に、"Last-Modified"ヘッダが lastModifled に、"Expires"ヘッダが expires にマップされたのを覚えておいて欲しい。

 

次のCacheStrategy#get() とそこから呼ばれる getCandidate() がフィールドにマップされた値を元にキャッシュ戦略を組み立てる心臓部である。

long ageMillis = cacheResponseAge();
long freshMillis = computeFreshnessLifetime();
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
Response.Builder builder = cacheResponse.newBuilder();
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
}
long oneDayMillis = 24 * 60 * 60 * 1000L;
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
}
return new CacheStrategy(null, builder.build());
}

ここが問題のコードだ。

理解のために先に答えを書くと、

  • ageMillis…レスポンスがどのくらい古くなったか
  • freshMillis…キャッシュはどのくらいの期間新鮮か
  • そしてキャッシュを使ってよく、age < fresh ならキャッシュは新鮮だとみなし、ネットワークリクエストすら行わない。
return new CacheStrategy(null, builder.build());

この第一引数がネットワークリクエストだが、これがnullだとCacheInterceptorがネットワークリクエストを一切行わない。

僕が疑問を持っているのがこの計算方法である。

 

cacheResponseAge()

レスポンスのageの計算をしているメソッドだ。この計算方法はRFC 2616 13.2.3 Age Calculationsに明示してある。*4

long apparentReceivedAge = servedDate != null
? Math.max(0, receivedResponseMillis - servedDate.getTime())
: 0;
long receivedAge = ageSeconds != -1
? Math.max(apparentReceivedAge, SECONDS.toMillis(ageSeconds))
: apparentReceivedAge;
long responseDuration = receivedResponseMillis - sentRequestMillis;
long residentDuration = nowMillis - receivedResponseMillis;
return receivedAge + responseDuration + residentDuration;

コンテンツが発行されてから実際に受信に要した時間を計算し、現時点までの経過時間を加算するなどしてageを計算している。*5

ここはいいだろう。

 

computeFreshnessLifetime()

キャッシュが新鮮だと考えられる期間を計算する。コメントを読むと

Returns the number of milliseconds that the response was fresh for, starting from the served date.

とのことで「コンテンツが発行されたときから考えて、レスポンスが新鮮だとみなされるミリ秒」という感じか。

CacheControl responseCaching = cacheResponse.cacheControl();
if (responseCaching.maxAgeSeconds() != -1) {
return SECONDS.toMillis(responseCaching.maxAgeSeconds());
} else if (expires != null) {
long servedMillis = servedDate != null
? servedDate.getTime()
: receivedResponseMillis;
long delta = expires.getTime() - servedMillis;
return delta > 0 ? delta : 0;
} else if (lastModified != null
&& cacheResponse.request().url().query() == null) {
// As recommended by the HTTP RFC and implemented in Firefox, the
// max age of a document should be defaulted to 10% of the
// document's age at the time it was served. Default expiration
// dates aren't used for URIs containing a query.
long servedMillis = servedDate != null
? servedDate.getTime()
: sentRequestMillis;
long delta = servedMillis - lastModified.getTime();
return delta > 0 ? (delta / 10) : 0;
}
return 0;

冒頭の「特定条件」を見直していただければ分かるが、ここでは

} else if (lastModified != null
&& cacheResponse.request().url().query() == null) {

のブロックに入る。

なんとここでは、servedDate(要するにDateヘッダ)から lastModified(Last-Modifiedヘッダ)を引き、10で割って返している。10で割る…

当然だが、"Date"ヘッダと"Last-Modified"が非常に長い期間空いていると(いくら10%とはいえ)非常に長い期間になり、結果的にageを上回ってしまい、この期間ローカルのキャッシュしか見ない状態に陥ってしまう。

 

謎のRFC

コメントには次のようにある

As recommended by the HTTP RFC and implemented in Firefox, the
max age of a document should be defaulted to 10% of the
document's age at the time it was served. Default expiration
dates aren't used for URIs containing a query.

 HTTP RFCが勧告しFirefoxの実装が従っているように、ある文書のmax ageは文書が返されてから経過した時間の10%をデフォルトとすべきだ。

このRFCが何番の何項か分からないので、原文にあたりようがない。したがって現時点でこの仕様と実装が妥当なのか判断がつかない。

これについて何かご存知のかた、是非情報ください。

 

Cache-Controlヘッダが適切に存在する場合

"Cache-Control"ヘッダに

  • max-age=0(というかageより小さければなんでも)
  • no-cache, no-store

がある場合は、いずれもこの謎の計算式に到達せず、キャッシュは古いものとしてネットワークリクエストが送られる。

 

max-ageが計算したageより小さい場合とno-cacheの場合はETag(のための"If-None-Match")ヘッダも正しく使われるし、no-storeの場合はヘッダに従ってキャッシュエントリの保存自体が行われない。

この辺はこれまでに挙げたクラスのソースコードを実際に見て欲しい。行数も少なく、シンプルに書かれているので読みやすい。

 

Date, Last-Modifiedヘッダ

この挙動に触れるまで、"Date"ヘッダと"Last-Modified"ヘッダについてきちんとRFCで読んだことがなかったので改めて読んでみた。

 

RFC2616 14.18 Date

要約すると、メッセージが生成された日時ということだ。(これはそのファイルが置かれた日時という意味ではないので注意)

ざっくりと、このHTTPレスポンスのボディが生成されてクライアントに向けて送出する直前ぐらいまでの時間のようだ。

サーバエラー等やNTPサーバが利用できないような状況を除いて、基本的に全てのHTTPレスポンスに付けなければならない。日本語訳はこちら

 

RFC7232 2.2 Last-Modified

これは提供するコンテンツが最後に更新された日時だ。

更新されたという定義はコンテキストによるようで、それがファイルであればファイルの更新タイムスタンプかもしれないし、XMLJSONの一部ならツリーの一部コンテンツ以下の更新を意味するかもしれない。

条件付きリクエストやキャッシュ鮮度によってネットワークトラフィックを軽減できるため、サーバはLast-Modifiedヘッダを返すべきである。

An origin server SHOULD obtain the Last-Modified value of the
representation as close as possible to the time that it generates the
Date field value for its response.

そしてここは個人的に重要だと思ったので引用したが、Last-Modifiedは可能な限りDateに近づけるようにすべきだと書いてある。正しく運用することでより正確なキャッシュ運用が可能だからだろう。日本語訳はこちら

 

まとめ

まとめと言っても難しいな…特定のヘッダの組み合わせでOkHttpが思いもよらないキャッシュを持つというのを誰かに共有したかった。

敢えて教訓を挙げるならば、キャッシュして欲しくないコンテンツは正しく"Cache-Control"ヘッダを設定しようということだ。

 

弊社ではCloudFrontに置いたファイルを更新してもアプリがそれをいつまで経っても読みに行ってくれず、一体何が起こってるんだ!?とソースコードを読んでこのような挙動を発見した。

同じような運用をしている人は一度そのファイルにcurl -Iしてレスポンスヘッダを確認してみて欲しい。

 

最終的に少し発散してしまったが、computeFreshnessLifetime() の計算式がいかなる根拠によるものか未だに興味があるので、何かご存知のかたは教えてください。宜しくお願い申し上げます。

*1:正確でないかもしれないが経験的にある程度近似しそうなアプローチ

*2:正確に言うと0である必要はなく、後述のage以下ならよい

*3:この辺りに関連することはOkHttpのInterceptorとNetworkInterceptorとCacheの関係 - 怠惰を求めて勤勉に行き着くに書いた

*4:日本語訳はたとえばこちら

*5:本当はより正確な"Age"ヘッダがあればそれを利用する。詳しくはRFC2616参照。