Subscribed unsubscribe Subscribe Subscribe

@kyanny's blog

Write down what I learnt. Opinions are my own.

Quipper に入社して丸3年が経った

f:id:a666666:20160529035602p:plain

Quipper に入社して丸3年が経った。そんなことすっかり忘れていた。3年と言われてもピンとこない。体感的にはものすごく昔のことのように感じられる。それだけいろいろあって濃い日々を過ごせたということだと思っておこう。

せっかくなので、 Quipper 日本オフィスの変遷になぞらえながら振り返ってみる。

入社前夜

前職で担当していたサービスの事業展開がうまくいかず、英語を使う環境で仕事をしてみたいという希望もあり、海外スタートアップ企業への転職を考えはじめたのが2013年の3月だった。

3月上旬にWantedly で見つけた Engine Yard のソフトウェア開発者募集に応募して選考に進んだものの、合否がなかなか出ず、やきもきしていた頃に前職の同僚で同じサービスをペアで開発していた @banyan に「Quipper に転職するんだけど話聞いてみない?」と誘われたのが4月の半ば(たしか木曜日だった)

Quipper 日本オフィス代表(当時)の横井さん、 Quipper 共同創業者で CTO の中野さん・同じく共同創業者で CEO の渡辺さんとの Skype 面談を翌金曜日の深夜に行い、その場で採用のオファーをもらった。さすがに即決はできないので考える時間が欲しいと言ったら、「わかった。しかし我々も長くは待てないので週明けの月曜日中に返事をくれ」と返され、スピード感に圧倒されたのをよく覚えている。週末 @banyan とサシで飲みにいっていろいろ話し、翌週にオファー受諾・上司に退職の意向を伝えた。

Engine Yard に行っていたらどうなっていただろうかと今でもたまに考えるが、たぶん無理だっただろうなぁと思う(主に英語力の点で)この3年で Engine Yard の日本オフィスは撤退し、当時お世話になった方々もいまはいなくなってしまったようだ。

目黒 HUB Tokyo 時代

Quipper 入社時のオフィスは目黒の HUB Tokyo というコワーキングスペースのシェアオフィススペースだった。二部屋借りていたがどちらも狭く、暑く、今思えば「よくあんなところに転職したよな...」というのが正直な感想だ。転職はタイミング、とはよくいったものだと思う。あと、やっぱり人が大事。1年間近で仕事をしてきた @banyan がいること、中野さんのこともブログでどういう感じか知っていたこと、渡辺さんのビジョンが非常に魅力的だったこと、このどれか一つでも欠けていたら踏み込めなかっただろう。

オフィス環境としてはお世辞にもよいとはいえなかったが、目黒時代は毎日とても楽しかった(今も楽しくないわけじゃないよ)謎の六角形の木の机に3人が座り、狭いのでノートパソコンを開きすぎるとぶつかってしまったり、初夏という季節もあり嫌な虫が出たり、目黒駅から徒歩十分以上と遠く毎日汗をかいて通ったり、目黒川が超臭かったり、そんな不便さにすらわくわくした。自分が入社したのが5月の末で、8月の頭にはオフィスを引っ越したので実質3ヶ月もいなかったはずなのだが、とてもそんな短期間だったとは思えない。もっと長い時間をあそこで過ごしたような感覚。

この頃は、1年後に会社がまだあるかどうかもわからない感じだった(少なくとも自分はそう思っていた)なので、先のことなんて考えず、とにかく新しいサービスを作ることだけを考えていた。

笹塚 合建東京ビル時代

もともと手狭なオフィスだったので、これ以上の増員には耐えられないだろうということで移転先を探し始めたのは2013年の6月だったか7月だったか。契約の関係で8月頭には引っ越しをしなくてはならないので余裕は少なく、いくつか候補地はあったものの消去法で残ったのが笹塚オフィスだった(原宿の超オシャレなデザイナーズマンション物件や、 DeNA ゆかりの地・代々木公園前のよさげな物件などもあったが、競り負けたり条件を満たせない部分があって諦めたりした)

笹塚オフィスの思い出は、なんといっても DIY な引越し作業だ。「せっかくなら Quipper らしいオフィスにしよう」と始まった引っ越しプロジェクトは、内装に凝りつつお金を節約するために極力業者に作業を依頼せず、自分たちでできることは自分たちでやろうということで、移転先オフィスの床に貼ってあったカーペットタイルを土日丸二日かけてみんなで剥がした。接着剤を剥がすために刷毛で剥離剤を塗ってスクレイパーでこそぎ落とすのだが、これがものすごい肉体労働で、翌週は「こんなところも筋肉痛になるものなのか」と驚くくらい筋肉痛だった。たぶん人生でスクレイパーの刃を交換する回数全部使いきったと思う。

笹塚オフィスではちょうど2年過ごした。この間に人が増えたり減ったりし、プロジェクトも始まったり終わったりし、海外へも二、三回行き、会社は投資を受けたり買収されたりした。部室みたいだった目黒オフィスに比べて、笹塚オフィスはいかにもスタートアップ・ベンチャー企業のオフィスという感じだった。自分たちのやっていることが「サービス」から「事業」に変わっていく感じがした。

笹塚時代はベネッセと一緒にやっていたプロジェクトに従事していた期間が長く、当時は「このままいずれベネッセの子会社みたいになって、ベネッセのオンライン教育サービスの基幹システムみたいな部分を担う仕事とかをするのかな」と思っていた。その後リクルートに買われたことは全く予想外だったが、やっぱり同じような仕事をすることになったのはなんとも不思議なものだなと思う。

笹塚オフィスも駅から遠く(徒歩十分)、住宅街にあったためランチの選択肢が少ないことが不評だったが、自分は周辺環境を気に入っていた。鍋家、キャンティ、牛楽苑、デニーズ、そして mosha cafe。特に mosha cafe には、最後の半年くらいは週の半分以上通っていた。ランチとコーヒーの味が恋しい。この後オフィスは京橋に引っ越してランチの選択肢は激増するのだが、場所柄ランチ営業時間が早めに終わってしまう店ばかりで、自分にとっては逆に選択肢が少なくなってしまった。笹塚時代の mosha cafe のような、一人の時間を過ごせるお気に入りの場所は、まだ見つけられていない。

京橋 昭和ビル時代

リクルート(正確にはリクルートマーケティングパートナーズ)に買収されて新しいプロジェクトが始まり、笹塚オフィスの契約が切れるタイミングで親会社のオフィスの近くに引っ越した。2015年8月のことだ。いずれは親会社のオフィスに入ることになるが、それまでの「つなぎ」という位置づけだったので、期間限定・とにかく物理的に近いことが重要で、そもそも選択肢も多くなかった。引っ越しのもろもろ手配なども担当者がそつなくこなし、ビルのボロさの割に内装などはいい感じで、あれはあれでいいオフィスだった。

京橋時代は「スタディサプリ」のプロジェクトが全てだった(引っ越し当時は「スタディサプリ」というブランド名はまだ無かったのだが)とにかく大変だった... というかまだ過去形になってない感覚。目黒時代からの仲間が去ったり、新しい仲間が大勢増えたり、リクルートの人たちが出向という形で増えたりした。日本オフィスのチームがいろんな意味で充実した一方で、海外オフィスのチームとの距離が少しできてしまったようにも感じる。

京橋オフィスは駅から近く、ロケーションも銀座・東京駅などの周辺でいかにもオフィス街という感じだったが、昭和ビル自体はなかなかボロくて入り口も大通りに面しておらず、ちょっとうらぶれた感じだった。親会社のオフィスとは1ブロックしか離れていなかったが、ミーティングなど face to face のやり取りが必要な場合はたいてい Quipper のオフィスにきてもらい、こちらから出向くことはあまり多くなかった(個人差は大いにあるが)

京橋オフィスには8ヶ月くらい通った。今月の上旬までいたのでまだ全然「思い出」という感じがせず、したがって書くこともあまりない。

京橋 三井住友海上テプコビル時代

親会社のほうでいろいろ調整などが済み、同じオフィスに入れるようになったので再度引っ越しをしたのが今年の5月の上旬。新しいオフィスでは「会社組織は違えども、同じスタディサプリに関わるメンバーなのだから変な壁を作らないようにしたい」という想いのもと、親会社の関連部署のメンバーと一緒に席をランダムシャッフルで決めた。おかげで引っ越し後しばらくは「入学式直後」みたいなよそよそしい雰囲気がオフィス全体を取り巻いていたが、ぼちぼち馴染んできたように思う。

新しいオフィスはビルも内装も何もかも綺麗で、設備も充実している。一方で、長机の「島」に同じオフィスチェアがずらっと並ぶレイアウトはいかにも「オフィス」という感じで、イケアで買った広い木の机のまわりにまちまちの椅子で座っていた昭和ビル時代までのオフィスと比べると殺風景なのは否めない。良くも悪くも「思えば遠くまできたもんだ」という感じだ。目黒・笹塚時代がちょっと懐かしくもある。

このオフィスで、これからどのくらいの時間を過ごすことになるのだろう。

なんだか自分のというよりオフィスの振り返りばかりになってしまった。自分のことでいうと、これまでだいたい3年から4年の間で転職してきた。今のところ転職する気はないし、買収後に CEO との 1 on 1 で「少なくともこのプロジェクトを成功させるまでは辞めない」と約束した、そのプロジェクトはまだまだ続いているので、来年の今頃は「丸4年」を迎えているんじゃないかな。

ImageMagick の identify は input-file に URL を受け付ける

$ identify http://www.dan.co.jp/~dankogai/dan-180x240.png
http://www.dan.co.jp/~dankogai/dan-180x240.png=>dan-180x240.png PNG 180x240 180x240+0+0 8-bit sRGB 10.5KB 0.000u 0:00.009
$ wget http://www.dan.co.jp/~dankogai/dan-180x240.png; identify dan-180x240.png
--2016-05-25 03:14:05--  http://www.dan.co.jp/~dankogai/dan-180x240.png
Resolving www.dan.co.jp... 182.171.67.97, 240d:1b:20:e300::97
Connecting to www.dan.co.jp|182.171.67.97|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 10514 (10K) [image/png]
Saving to: 'dan-180x240.png'

dan-180x240.png                    100%[==================================================================>]  10.27K  --.-KB/s   in 0.001s

2016-05-25 03:14:05 (7.76 MB/s) - 'dan-180x240.png' saved [10514/10514]

dan-180x240.png PNG 180x240 180x240+0+0 8-bit sRGB 10.5KB 0.000u 0:00.000

man を眺めたが URL も ok とは書いてないようだった。

$ identify --version
Version: ImageMagick 6.9.3-7 Q16 x86_64 2016-03-27 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2016 ImageMagick Studio LLC
License: http://www.imagemagick.org/script/license.php
Features: Cipher DPC Modules
Delegates (built-in): bzlib freetype jng jpeg ltdl lzma png tiff xml zlib

最近読んだもの

Reading

Coming to Rust from Ruby - via @codeship | via @codeship

単に自分の観測範囲の問題のような気もするが、Goに早いうちから手を出し始めたのはPythonプログラマが多かったのに、RustはRubyプログラマへのPRやRubyを真似た部分が目立つ(Cargoとか)

単にそれぞれエバンジェリストとして活動した人たちのルーツに偏りがあっただけなのか(GoはGoogleが作った言語で、Googleの公用語はC++ Java Pythonだった。RustはRubyコミュニティで有名な人たちが開発やコミュニティ活動に精力的に参加している)それ以外に何か理由があるのか。

RustにRubyistが惹かれる理由はなんとなくわかるような気もする。静的型をもつRubyっぽい言語が欲しかった、というのがひとつ。そして、Rubyistはちょっと小難しいプログラミング言語を好む、という節を提唱したい。メタプログラミングとか、度を越して使いたがる時期が誰にでも訪れるし、違うパラダイムの言語が流行るととりあえず手を出したがる。要するに新しいもの好きでプログラミングが好きな人たちなのだ。だから手応えがありそうな言語にチャレンジしたくなる。

Our best practices are killing mobile web performance · molily

なるほどもっともだと思うが、関係者が多く大きなソフトウェアだと全体の挙動をすべて掌握してる人がいなくて、この手のことをコントロールするのは難しそうだなとも思う。

Rails の Strong Parameters は Array[BSON::ObjectId] 型のデータを許可しない

params.permit(:object_ids) としたとき、

  • :object_ids => [BSON::ObjectId('573d8ac76200b0176c000001')] はダメ
    • 空の Array [] が返る
  • :object_ids => ["573d8ac76200b0176c000001"] なら ok

BSON::ObjectId のリストをフォーム入力などから受け取って BSON::ObjectId 型にキャストして処理するプログラムを書く場合は、コントローラーのレイヤでキャストせず、文字列表現で受け取ってモデルなど下層でキャストする。

https://github.com/kyanny/playground/blob/gh-pages/strong_parameters_reject_array_of_bson_objectid/app/controllers/users_controller.rb

  def user_params
    params.require(:user).permit({
                                   reference_ids: []
                                 })
  end

  def user_params2
    _params = params.require(:user)
    _params[:reference_ids] = _params[:reference_ids].map { |id| BSON::ObjectId(id) }
    _params.permit(reference_ids: [])
  end

https://github.com/kyanny/playground/blob/gh-pages/strong_parameters_reject_array_of_bson_objectid/test/controllers/users_controller_test.rb

  test "should create user" do
    id = BSON::ObjectId.new
    post :create, user: { reference_ids: [id] }
    data = JSON.parse(response.body)
    pp data
    assert_equal data['user_params']['reference_ids'], [id.to_s]
  end

  test "should create user (2)" do
    id = BSON::ObjectId.new
    post :create, user: { reference_ids: [id] }
    data = JSON.parse(response.body)
    pp data
    assert_equal data['user_params2']['reference_ids'], [id.to_s]
  end
# Running:

{"params"=>
  {"user"=>{"reference_ids"=>[{"$oid"=>"573d8b776200b017d6000001"}]},
   "controller"=>"users",
   "action"=>"create"},
 "user_params"=>{"reference_ids"=>["573d8b776200b017d6000001"]},
 "user_params2"=>{}}
F......{"params"=>
  {"user"=>{"reference_ids"=>[{"$oid"=>"573d8b776200b017d6000002"}]},
   "controller"=>"users",
   "action"=>"create"},
 "user_params"=>{"reference_ids"=>["573d8b776200b017d6000002"]},
 "user_params2"=>{}}
.

Finished in 0.062714s, 127.5633 runs/s, 31.8908 assertions/s.

  1) Failure:
UsersControllerTest#test_should_create_user_(2) [/Users/kyanny/playground/strong_parameters_reject_array_of_bson_objectid/test/controllers/users_controller_test.rb:26]:
Expected: nil
  Actual: ["573d8b776200b017d6000001"]

8 runs, 2 assertions, 1 failures, 0 errors, 0 skips

Treasure Data (Presto) で MongoDB の Array[BSON::ObjectId] 型のデータを string 型として入れてしまったとき Array にバラして展開する

  1. substr で文字列からゴミを取り除く
  2. split で string -> array にする
  3. UNNEST で展開する 7.16. SELECT — Presto 0.147 Documentation
SELECT 
   *
FROM
  (
  VALUES(
    1,
    '[573ca5ca6200b048be000001, 573ca5cc6200b048be000002]'
  ),
  (
    2,
    '[573ca5ca6200b048be000003, 573ca5cc6200b048be000004]'
  )
  ) AS t(
    id,
    object_ids
  ) CROSS
JOIN
  unnest(
    split(
      substr(object_ids,
        2,
        length(object_ids) -2),
      ','
    )
  ) AS t(object_id)

f:id:a666666:20160519023725p:plain

Hive の場合は LATERAL VIEW というので最後の展開のステップができるらしい; 元々データエンジニアの人に「array の要素の一つに対して = で JOIN するにはどうすればよいか」と聞いて LATERAL VIEW を教えてもらい、 Presto でのやり方を調べた、という順番。

Embulk の設定とかで Array 型で入れられるなら当然そうしたほうがよい(できるのかどうか知らない)