seri::diary

日常

死ぬまでエンジニアでいたい

理想の死に方は、前夜にOSSリポジトリにPRを出して、翌朝には机で突っ伏して死んでて、最期に出したPRのコメント欄がr.i.p.で溢れている。そんな状態。

なんて話を冗談めかして飲み会ですることがある。*1が、本人は割と本気で死ぬまでコードを書いていたいと思っている。

年金がもらえそうにないので生涯働かないといけないみたいな主張を最近各種メディア界隈でよく見る。しかし、それとは別に、体が元気なら死ぬまで働き続けていたいものである。定年で悠々自適におとなしくできる気がしないし、それに、エンジニアとしてならリモートでも働きやすいし、数十年後にはさらにそれに適した社会になっているとも思われるし。

死ぬまでエンジニアでいる。それはおそらく難しい。

40歳を超えた辺りで、現場を離れてマネージャーとかIT芸人とかCTOとか技術顧問とか、そにかく違う肩書で働くようになる人がたくさんいるを見てもわかるように、そもそもエンジニアという職業を長く続けることは難しいと思われる訳で、死ぬまでエンジニアでいるのは多分容易ではない。自分の周りでいえば、現場でコードを書いてる人で一番高齢の人でも40歳で、それ以上は見たことがない。大体それぐらいになるとマネジメントとか会社の広報活動をメインでするようになって、現場から離れてコードを書かなくなっている。

でも自分はマネジメント方面には今のところ興味がない。仕事でそういう振る舞いが必要になって、他の人を巻き込んでそれらしいことをすることはあっても、それを専門にして飯を食いたいとは思わない。

もし自分が現場を離れてプロジェクトマネージャーになったとしても、自分が担当してるプロジェクトでこっそりコードを書いてPRを出したり、開発環境を改善するツールやライブラリを作って配布したり、CI環境を整備してテストとデプロイを自動化したり、PRというPRに目を通してコメントしてまわってウザがられるであろう。そんなプロジェクトマネージャーがいたら自分だったらイヤなので、やはり自分はマネージャーになるべきでない。とにかく目についた気になる所は即座にissueを立てて時間ができた瞬間に片っ端から修正するPRを出していく、そんな人間はやはりマネジメントをするよりコード書き野郎として生きるしかない。

ではそれをどうやって実現するか。今のところ割りと真面目に考えている話として以下を考えていたりする。

コードを書き続ける

当たり前のことかもしれないが意外と難しいということに気づいた。なぜなら、仕事における課題は大体コードの外で起きている。コードの外で起きている問題に対処し始めるとあれよあれよとコードを書く時間は失われていく。だから、意識してコードを書き続け無いと1週間のスプリントで何もdeployできない、なんていうのが普通になる。それが習慣化したらエンジニアとしての自分は死ぬと思っている。*2

あと家にいても気になるコードは読む、cloneして手元で動かす、バグを見つけたらPRを出す。気になる技術はdoc読んでわかった気にならずに動くコードを写経して感覚を覚える。そういう習慣が大事。youtubeやニコ動ばかり観ててはいかんのだ(けもフレ12.1話最高でした)。

長らくdocker以外の新しい技術を学んでいなかったなと反省して、最近はelectron本の写経をするなどしている。electronの本というよりモダンなフロントエンド開発の要素をまるごと勉強できてお得な一冊。サンプルコードもES2015で書かれている。

それ以外だとsparkのコードリーディングなど。今年はpythonscalaを今年学ぶ言語として勉強している。

健康の維持

どんなに優秀でも健康でない奴に重要な仕事は振られなくなる。残念ながらこれが人間の心理。人は細かいところをよく見ているもので、毎週月曜日に休んでたりすると必ず誰かがマークしている。コワイ。いわんや、高齢で体が弱いとか、ますます仕事が無くなりそうではないか。それをひっくり返すだけのバリューを出せればよいが、そうだとしてもやはり体調が悪いのは精神的によくないし、長期的にはモチベーションに関係してくる気がする。生涯現役目指すマンにとっては大きな壁となるであろう。

まずは体力ということで、最近忙しかった関係で通ってないけど(だめじゃん)ジムで筋トレしたり走ったりしている。むしろ走る方がメインか。なんでもいいけど運動は大事。運動と瞑想の習慣の無い者の末路はなんとやら、というのは多分正解。

瞑想も『サーチ・インサイド・ユアセルフ』を読んでからやってるが、1日の振り返りになって大変よい。1日に起きた出来事が頭に浮かばなくなるまで瞑想し続けてから寝ると変な気持ちを翌日まで持ち越さなくて済む。

あと、生えている最後の親知らずを先日抜いてきた。まだ少し痛い。だが、親知らずも放置したまま高齢になるとますます抜くのが難しくなるし、傷口が塞がるのも遅くなるし感染症にもかかりやすくなる、ということで今のうちに抜いておこうと思って抜いた。幸いにして真っ直ぐ生えていたので歯医者に着いてから10分もかからずに診察は終了した。この歳になるとこういう細かい体のメンテが大事じゃないかと思っている。

それ以外にやっていることとして、月1で温泉に行くとか。もちろんY氏直伝でN氏も絶賛していた交代浴をするのだ。

travel.spot-app.jp

千葉県野田市にある七光台温泉が好きで、自宅からだと電車と徒歩で片道2時間弱かかるのだが、その分土日に行ってもそこまで混んでなくて大変居心地がよい。都内の銭湯はいつ行っても混んでいてあまり好きではない。

nanakoudai.3riku.co.jp

今できていないこと

最近勉強会で登壇することがなくなってしまい、すっかりプレゼンスが下がっている。これはなんとかしないといけない。だが、今のところ発表できるネタと勉強会のテーマが合わなかったりして二の足を踏んでいる。なので基本的にブログやQiitaでのoutputをメインにしようと思うが、できれば今年中に最低一回はどこかで話したい。

あといい加減○○の人として認識されたいが、せいぜい「エモブログの人」とか(これは実際に言われたことがある)、「職業railsマン」程度なので(多分)、もう少し技術的なところでプレゼンスを発揮していきたい所存である。

その一環として、最近読んでいるsparkのコードリーディングの成果を一部qiitaに書き始めた。ある程度まとまったら第二弾を書く予定。

http://qiita.com/seri_k/items/363f5c1afdf63ca2f889qiita.com

*1:「そういう時に限ってcircle ciが障害起こしててビルドがコケて無念の中死ぬものである」というコメントを酒の席で頂いた

*2:今の会社に入ってから初めて1sprint内で何もデプロイできない経験をして結構ショックだった

自分がRubyMineでRails appを書く理由

基本、Rails appを書く時はIDE(RubyMine)を使っている(それ以外はatomvim)。

今の職場だとIDE使ってるの自分だけで、これまでもだいたい他の人はRails appを書く場合でもemacsvimatom、といった具合だった。Rails appはそういうエディタで書くのが普通らしい(自分の観測範囲においては)。

しかし、自分は仕事でrailsを書くようになってから3年ぐらいになるが、ずっとRubyMineを使っている。理由は下記。

  1. 自分で各種プラグインを入れなくても開発に必要な機能が最初から揃っているのでインストールするだけで開発がすぐ始められる。
  2. リファクタ機能が強力。メソッド名をファイルをまたいで一括で変更できるのが超絶便利。
  3. メソッド、クラスの定義元に一発でジャンプできる。使っているgemのソースを読みたくなったときも一発で手元のrails appからgemのソースへシームレスにジャンプできる。
  4. typoやsyntax erorrをエディタ上で教えてくれるので実行する前にミスに気が付ける。
  5. classやファイルをインクリメンタルサーチで検索してファイルを開くことができる。
  6. クラス構造のtreeを表示できる。1classが500行超えぐらいになるとこれがないとジャンプがつらい。

これらの機能はvimでもemacsでもプラグイン入れれば実現できるのかもしれないが、やり方を調べて設定ファイルを書いて、さらにトラブルが起きたときに自分で調査する手間を考えると、インストールすればすぐ使えて、万が一なんかあってもサポートが効く有料IDEの方がいいと思っているのでRubyMineをずっと使っている。

そこまでエディタにこだわりがないというか、正直なんでもいいと思っている。ただ、web appのメンテという仕事においては、できるだけ早く目的の箇所を探し出してコードを書く必要があるため、その効率だけを考えてエディタを選んでいる。その結果としてIDEに落ち着いている、という感じだろうか。プライベートだとC++Atomで書いてたりはするが、仕事だったらVS使ってると思う。

ちょっとだけOSSに貢献したらすごく新感覚だった話

普段は仕事でコードを書いていて、それでおちんぎんは貰えるし、こんな自分でも多少は社会の役に立っているのではないかなぁという実感がまぁ多少は持てる時もある訳で、それなりに満たされた生活をしている。サラリーマンなので、当然大人の事情でしんどい気持ちでコードを書いてる時も少なからずあるが、それでも仕事は仕事である。頑張って書くのである。そんな毎日。

そんな中、1円ももらえないし、役に立つかどうか良くわからんが、気になったのでやった、というような状況でコードを書いてみたら、不思議な事に仕事でコードを書くのとは全く異なる満足感が得られてめっちゃテンション上がったので、その辺の話をしたい。

ある日、BigQueryにアクセスしている部分で突然エラーがraiseされてトラブったので調べてみたところ、GCPruby clientであるgcloudというgemがGoogleaccess tokenを取得する時に Faraday::TimeoutError をraiseしていたことが原因だった(因みにこの時のgcloudのバージョンは0.12)。 Faradayのエラークラスをそのままraiseしてくることを想定していなかったので、自前で入れていたリトライ機構をすり抜けてしまっていた。

gcloudのnamespace配下で定義された専用のエラークラスがあるにも関わらず、Faradayのエラーを生でraiseしてきたという状況が単純に気になって調べてみたところ、errorをraiseしてきたのはgcloud本体ではなく、そのgemが使っているauth用のクライアントであるgoogleauthというgemの方だった。ソースを読んだところ、確かにrequestする部分でエラーを拾ってない事がわかった。で、gcloud側もこのエラーを拾ってwarpしてない、という状況だった。

正直このエラーをどちらかにwrapしてほしい。Faradayのエラーがそのままraiseされてくるのは流石に想定しづらいし、何よりgoogleauthもgcloudも、独自のエラークラスを定義し、主要なエラーについてはそれでwrapしてraiseするように実装してるのだから統一してほしいというモンである。

しかし、今回見つけた問題に関してはどちらが面倒を見るのが良いのだろうか?個人的意見としては、auth周りで起きている問題なのでgoogleauthに面倒見てほしい。が、googleauthを使ってるgcloud側で面倒見てもまぁそこまで筋は悪くはない。

というようなことを考えていたら、gcloudのrepositoryですでに似たような問題でお悩み相談しているissueが立っていた。なんでFaradayのエラーが飛んでくんねんという話とhttp requestのエラーくらいretryするべきではという話がごっちゃになってややこしいのだが、少なくとも議論の終盤の流れとしてgcloud側が何か対応をする気はないらしかった。

ならば、googleauth側でやるべきことが大体わかってる自分がやるのが良いだろうと思い、googleauthの方にPRを出し、先日approveされた。googleの中の人(多分)のレビューともなると色々細かいツッコミが入るかなと思ったが割とあっさりとapproveされた。まぁ大した修正じゃないしな。

まだマージされていないが、このPR自体はgcloudの方で立っていた件のissueの方で紹介されて、この件はgoogleauthの方に責任を持たせてgcloud側の上記のissue自体をcloseする提案が出ている。大した修正ではないにせよ、半年ぐらい停滞していた議論を少しでもすすめるきっかけを作れたという結果には満足しているし、自分自身も困っていた問題なので、ぜひマージされてほしい。たのむ。

今回の件は多分誰でも出来たことではあったし、ブログのネタにするにはあまりにしょうもない話だったかもしれない。しかし、自分が問題だと感じていることに対して解決策を提示して、それをほかの人が(しかもgoogleの人が)認めてくれた、という結果が、とても嬉しかった。OSSにcontributeしても1円にもならない訳だが、なんというか、多くの人がOSS活動をしている理由が少しだけわかった気がした。

なお、調子にのってopenid-rubyという2年ぐらいメンテされてなかったOpenID(Connectではない方)のクライアントにもbugfixのPRを送ってみた。もうcommiterも放置しててマージしてもらえないかなと思ってたらちゃんとマージしてもらえた。こちらはたまたま壊れてたの見つけたから直したよ、という程度だったが、自分にすぐできるのはこういうことなので身の丈に合った仕事と言えるのかも知れない。

今後も何か自分にできそうなことがあれば積極的にPRを送ってみようと思えた。多分その方が仕事でコードを書くよりも下手すると楽しい。

2017年3月29日追記

技術書を読む意味について考えること

先日書いた記事が妙にバズってしまった。

別段新しいことをやったつもりはなく、NNは初めて勉強したけど専門外の知らないことを突然勉強するのは今に始まったことじゃないし、みんな普通にやってるもんだと思ってた。

それでもバズってしまったのは、あの書籍が発売直後からAmazonでずっと在庫がなくなるなどして相当の注目を集めていて、 Deep Learning というワードがバズワード的に流行っていたから、という部分が大きいだろう。そうでなければ、あんな下手な書評がこんなにバズらない。

自分は無名なエンジニアであり、「とりあえずあの人が書いているんだから流行っているのだろう」と認識されるようなインフルエンサーでもない。ついでに言えばDeep Learning自体はもっと前、それこそ数年前から聞くようになった単語であり、それを今更勉強しているのは本当に今更感がある。本当は去年Tensor Flowがリリースされてちょっといじってみた時に深く勉強すべきだったと思っている。出遅れてしまったなぁという反省がある。今からキャッチアップして、TensorFlowやChainerを使って業務に活かすにはかなり時間がかかるだろう。ただ、それでも今のところはいくつかNNで解決するのに適していそうな問題がいくつか手元にあるため、今後も継続して勉強していきたい所である。

で、話はちょっと変わるのだが、人はどうして技術書を読むのだろうか。自分は技術書をそこまで読む方ではないのだが、基本的にNNのように知らない知識を効率よく勉強するためである。

逆にES界隈など、変化が激しい(今年は落ち着きつつあるようだが)分野については、ネット上の情報と書籍の情報での鮮度に差が激しいのでネット上で鮮度の高い情報を調べる事が多い。仕事においては後者に該当する分野の技術を使ってる事が多いので、ネットで公式リソースに直接当たるケースが圧倒的に多い。

特にruby界隈は、言語自体もgemもコミュニティ内で議論されている内容まで追わないと状況が分からないことが結構ある。前に、ruby2.1.x(パッチバージョンまでは覚えてない)でバグを踏んで、その情報はbugs.ruby-lang.orgのissueにしかなかったことがある(どれかは忘れたが)。単にググって見つかる情報だけではもう足りなくなってきている。それぐらい情報の更新頻度が高い。

ではなぜ技術書も読むのかというと、ある程度枯れて体系化された知識は技術書の方がよくまとまっている事が多いからである。リアルタイムに日々更新されるコミュニティの議論やmasterブランチのcommit logと比べると、いわば綺麗に編纂され直された歴史書とでも言うのだろうか。そういう感覚が自分にはある。歴史書には瑣末なことが書かれないように、本質的な内容だけがきれいにまとめられる余地がある。自分は技術書に対してそういうものを期待しているし、そういうものが書かれている技術書を中心的に読む傾向がある。

今年読んだもので言えば、TCP/IP, HTTP1.x, HTTP2.0の歴史と昨今のN/W界隈で話題になってることまで広く説明されている「ハイパフォーマンスブラウザネットワーキング」がまさに「歴史書」といった感じの本。@kazuhoさんのhttp2に関するスライドを読んでいてTCP/IP輻輳制御の話がよく出てくるのだが、ネットワーク全然知らないので良くわからんということで手に取ってみた。非常に広く・深くといった感じで昨今話題になっていることを理解するのに最適な一冊だった。こういう、過去の経緯から最新の事情までを広く、かつ分野も広く知りたいような時には技術書というフォーマットが強いと特に感じた一冊でもある。

ハイパフォーマンス ブラウザネットワーキング ―ネットワークアプリケーションのためのパフォーマンス最適化

ハイパフォーマンス ブラウザネットワーキング ―ネットワークアプリケーションのためのパフォーマンス最適化

あと技術書というか学術書だが、統計や機械学習といったアカデミックな分野での情報発信が根強い分野は学術書にしかないネタがまだまだ多いと感じているので、この分野はまだ書籍に優位性がある。

学術書、といっても今年は統計、機械学習関係と自然言語系の本しか買ってないのだが、一冊の値段が結構お高いので買うのに気合が必要だったりするのだが、大学に行って勉強するのに比べれば低コスト勉強できるのではないかと思う。

が、逆にアカデミックな分野は専門の人とディスカッションしたり質問したりしながら勉強しないと流石に厳しいなぁと感じる分野でもある。「データ解析のための統計モデリング入門」は初学者の自分でも分かりやすい部類の統計の本だと思うのだが、「こういう場合はどうだろう?」「ここはどういう意味だろう?」というクエスチョンマークを頭に沢山浮かべながらも調べる術がないので気合でゴリ押して読む感じになってしまうので、やはり大学の教科書的な使われ方を想定してるんだろうなという気はした。実際作者の久保拓哉弥先生は北大の准教授(出版当時で今は不明)なので、いつか直接この本に関する講義を受けてみたいなぁと思った(まぁ自分が関東にいる限り無理だろうけど…)

何が言いたいかというと、そろそろもう独学での勉強が限界に来ている。

こんな私でもニューラルネットワークをスクラッチで実装できました(30歳 男性)

この記事はトレタ Advent Calendar 2016の22日目です。
21日目はswdhActiveRecordオブジェクトを関連ごとシリアライズしてデシリアライズするでした。

スナップショット的にその時点のモデルを関連モデル含めて保存したい、っていう要望はBtoBやってると結構遭遇しますね。テーブルをちゃんと正規化すればするほど難しくなるやつなのでgem化されてるとありがたいです。

f:id:serihiro:20161221233653j:plain

さて、この記事ではゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装を読んでpythonに入門するところから初めてニューラルネットワークを実際に実装して見た所感を記述します。平たく言えば読書感想文です。


書籍「ゼロから作るDeep LearningPythonで学ぶディープラーニングの理論と実装」の概要

本の内容としては

という流れ。

なお、本文中に出てくるサンプルコードはすべてgithub公開されているので、写経するのがだるいという人でも安心だ。

読んで実装してみた所感

前提条件として、自分は機械学習は多少かじった程度の知識しかなく、ニューラルネットワークについてはパーセプトロンとの違いとバックプロパゲーションのぼんやりとした概念、ぐらいしか知らないレベルでこの本を手に取ってみた。あともちろんpythonも書いたことがなかった。

TensorFlowやChainerのMNISTサンプルを動かしてみたりもしたのだが、フレームワークを使うといまいち抽象度が高くて具体的に何をやってるかわからないと感じていた。(余談だが結構そういう声をよくネットで見かける)

そういう状態で読んで一通り実装してみたのだが、今までふわっとしていた「ニューラルネットワークとはこういうものか」というイメージがかなり明確になった。それが何によるものかということを以下述べていきたい。

なぜこの本が良いと思ったのか

この本の最大の特徴は、機械学習系の本だと省略されがちな数学の前提知識について1つ1つ丁寧に説明されている点と、それらが全て動く「コード」を持って説明されている点にあると感じた。

文字通り「ゼロから作る」ための本である。pythonを手元で動かせる環境さえあれば数学の知識がなくても問題ない、という機械学習系の入門書にしては非常にめずらしい位置づけの本である。

どのくらい「ゼロ」でも大丈夫か?体を張って試してみた

結論から言えば、高校数学なんて忘却の彼方という人でも全然問題無いと言える。

例えば、ニューラルネットワークでは結果を求めるまでに途中で行われる計算の流れを行列を使うとスッキリ書けるのだが、いきなり行列の積だけ書いて放置するようなことはしない。 まずは行列の積の計算の仕方(行列の掛け算ではどの要素とどの要素を乗じるのかという所から)から説明してくれる。NumPy特有の話もあるが、基本的には遠い昔高校時代に習ったような説明を受けることができる。

また、勾配降下法の説明をしている章では、微分の定義から始まり数値微分の計算方法を説明している。至れり尽くせりである。

しかも、それらは数式だけでなく、すべてpythonの実装がセットになっている。例えば数値微分を行う関数もサンプルコード上で下記のように実装されている。 *1

def numerical_diff(f, x):
    h = 1e-4 # 0.0001
    return (f(x+h) - f(x-h)) / (2*h)

そのため、説明がよく理解できずとも、pythonの実装を読んで、写経して手元で動かしていじっているうちに計算手順のイメージを掴むことができる。

数値微分の実装は、引数をあれこれ変えて近似された値を計算で求められると、解析微分の公式だけを丸暗記していただけの微分がまた違うものに見えてきて非常に面白い。

コードを書いて動かして学ぶというアプローチについて

こういうアプローチは、数式よりもコードに馴染みがあるプログラマならではの方法かも知れないが、少なくとも自分はもともとコードを書いて動かした方が理解しやすい派なのでこの本のアプローチはかなりマッチしていた。

数学が得意な人にとっては、わざわざシンプルに表現された数式をプログラムに変換して動かして理解するという面倒なことをよくやるもんだという感想を抱くかもしれない。しかし、実際に自分でコードを書いて動かした結果を確認する学習方法は、プログラマにとって一番理解し易いアプローチではないかと思う。*2

一方で、説明がかなり細かいので、既知の知識と重複するところが多い人には本書は少々冗長に感じるかも知れない。 しかし、大学時代に数学を勉強していない筆者のような人間にとってはこういう基礎知識こそがありがたい。

まとめ

  • 「プログラミングは普通にできるけど機械学習とかニューラルネットワークってなんか難しそうだな~」と思ってる人にこそ読んでほしい一冊。
  • まだちゃんと理解できていない部分もあるので年末年始に改めて読み直したい。
  • トレタ Advent Calendar 201623日目は私とよく昼食を一緒に食べに行くサーバーサイドエンジニアの出番です。

*1:https://github.com/oreilly-japan/deep-learning-from-scratch/blob/471ff64c25d27eaad58d8b5a9e787249db974d44/ch04/gradient_1d.py#L6-L8

*2:余談だが自分も統計モデルについての本を読んでいた時はポアソン分布の定義にしたがって確率を計算するクラスを実装してみたりしていた。 bitbucket.org

自分はストレス耐性が高い方ではなく、無駄に色んなことに気を揉んでしまう性格のせいか疲れを溜め込みやすい性格のようだ。そういうことに気づいたのはつい最近である。最近ではあるが、最近そうなったか?というとそうでもないように思う。振り返れば中学ぐらいの頃から余計なものをたくさん背負う癖があったようにも思うし、そうでなければ(全くそういうのに向いてないのに)生徒会役員とかやらなかっただろうなと思う。

心が折れるというのは面白い表現だと感じる。普段は折れていないのだろうか。まっすぐなのだろうか。それとも真っ直ぐでも折れてもない、いい塩梅に曲がっているのだろうか。そういう形状が通常時ならば、枯れ枝のようにポキっと折れていたら、それはまぁ誰が見たってまともな状態ではないという兆候なのだろう。

自分はどうか。自分のことは良くわからない。しかし、もう、特定のことしかやっていたくないと思うことが増えた。仕事に行かずにこれだけをやっていたい。それは本を読むことだったりコードを書くことだったり、或いは延々と映画を観たいとか旅行に行きたいとか、あるいは何の予定もなくサンフランシスコに行ってスタートアップ訪問したいとか、突拍子もないことだったりもするのだけれど、何かこう色々とうまく回せなくなってきていると感じる。この状況をどう捉えればいいか自分でもよう分からない。

がんばって客観的に自分の状況を鑑みるに、どうにもこうにも消化不良を起こしているような心情が占めているように思う。自分では納得してないけどやらなきゃならん、やるぜー、うぉーっ、と頑張ってきたことが、ある日突然外的要因により、それはもうどうしようもないくらいの力によって、ポシャってしまう。そういうことがなぜか自分の身にはたくさんあった。

そういうことが何度かあって、結果として、自分は何も成果を残せていない。残せていないから、何も自分で自分を褒める要素なんてない。当然誰からも評価はされない。新たに得たものは何もない。一応会社員としてはこれはまずい。種を植えることになって、自分はいつものように土を作っていたが、やーそろそろ種を植えようかね、というところで、畑は使われなくなってしまった。使われないから自分は畑をもとの状態に戻した。

気がつけば、確実に結果を残せることを自分で身つけて拾って片付けるようなことに自然と意識が傾き始めた。必然であったし無意識でもあったのだが、結果が残せるということは精神衛生上とても良いことだった。そうか、何かの本に書いてあったけど、小さな勝利を積み重ねるのは、だから大事なのか、ということに気付かされる。

プログラミングで言えば、細かいバグを直したり、リファクタリングしたり、依存しているライブラリのバージョンを(特に理由もなく)上げてみたり、テストが足りないところでテストを書いてみたりといった、まぁ概ね自分やらなきゃならないタスクではないとは思うような、間違えようのないタスクをやることが心の平穏につながる。どうやったってうまくいくし、自分の成果として形に残る。これは素晴らしいことだ。面白くはないけれど。

そうこうしているうちに、もしかしたら、少しは育つかもしれない種みたいのが何とか見つかってきている。

自分は土は作れる。だが種がなかった。だけど種さえあれば、何とかなるのではないか。そういう気持ちが、最近になって、本当に久しぶりなのだがようやく芽生えてきた。

そういうものをどうにかこうにか育てられればと願う。 願うだけでなく、芽が出るぐらいまでは、まぁ自分で面倒を見てやりたいと、そう思う。畑はいつでも移ることができるが、多分育てられる種は見つけた場所でしか育てられないと思うので。

自分が2016年に作ったrails拡張系gemとその解説

この記事はトレタ Advent Calendar 2016の4日目です。
3日目は弊社CTOのmasuidriveによる【定番】 新しいWebサービスを開発・運営するときに使いたいサービス 【2016年末版】でした。私の推しサービスはBugsnagです。詳細はこちらのスライドを参照ください。

さて、3日目のこの記事では、私が今年railsの実装についての調査と暇つぶしを兼ねて作ったrails拡張系gemについて紹介します。

flatten_routes

github.com

routes.rb を書くときにresouceresourcesを使った書き方をしているとroutes.rbだけでは実際のURLがパッと見でわかりにくくなることがある。

$ rake routes を使えばよいのだが、routes.rb が膨大になってくると表示まで3~4秒待たされることもあり、プロジェクトが佳境を迎え、1秒でも惜しい状況では結構なストレスになる。

そこで、routes.rbを書くときのもう一つの記法である verb {directory} => {controller}/{action} の書き方もコメントで併記できたらよさそうと考えてこのgemを作った。

実行例は以下の通りである。 仮にroutes.rbが以下の通りだったとすると

Rails.application.routes.draw do
  ActiveAdmin.routes(self)
  resources :todos, except: [:new, :edit] do
    member do
      post :start
      post :finish
      post :restart
      post :giveup
      put :awesome
    end
    collection do
      get :finished
      get :not_yet
    end
  end
end

Rails.rootで以下のコマンドを実行することで

$ rake flatten_routes:annotate

こうなる

Rails.application.routes.draw do
# == generated by flatten_routes from here 2016-02-27 17:56:31 +0900
#  get    '/admin'                    => 'admin/dashboard#index'
#  get    '/admin/dashboard'          => 'admin/dashboard#index'
#  post   '/admin/todos/batch_action' => 'admin/todos#batch_action'
#  get    '/admin/todos'              => 'admin/todos#index'
#  post   '/admin/todos'              => 'admin/todos#create'
#  get    '/admin/todos/new'          => 'admin/todos#new'
#  get    '/admin/todos/:id/edit'     => 'admin/todos#edit'
#  get    '/admin/todos/:id'          => 'admin/todos#show'
#  patch  '/admin/todos/:id'          => 'admin/todos#update'
#  put    '/admin/todos/:id'          => 'admin/todos#update'
#  delete '/admin/todos/:id'          => 'admin/todos#destroy'
#  get    '/admin/comments'           => 'admin/comments#index'
#  post   '/admin/comments'           => 'admin/comments#create'
#  get    '/admin/comments/:id'       => 'admin/comments#show'
#  delete '/admin/comments/:id'       => 'admin/comments#destroy'
#  post   '/todos/:id/start'          => 'todos#start'
#  post   '/todos/:id/finish'         => 'todos#finish'
#  post   '/todos/:id/restart'        => 'todos#restart'
#  post   '/todos/:id/giveup'         => 'todos#giveup'
#  put    '/todos/:id/awesome'        => 'todos#awesome'
#  get    '/todos/finished'           => 'todos#finished'
#  get    '/todos/not_yet'            => 'todos#not_yet'
#  get    '/todos'                    => 'todos#index'
#  post   '/todos'                    => 'todos#create'
#  get    '/todos/:id'                => 'todos#show'
#  patch  '/todos/:id'                => 'todos#update'
#  put    '/todos/:id'                => 'todos#update'
#  delete '/todos/:id'                => 'todos#destroy'
# == generated by flatten_routes to here
  ActiveAdmin.routes(self)
  resources :todos, except: [:new, :edit] do
    member do
      post :start
      post :finish
      post :restart
      post :giveup
      put :awesome
    end
    collection do
      get :finished
      get :not_yet
    end
  end
end

コメントするだけでなく、いっそのことresorucesresourceを使う記法をすべてconvertしたい、という時には

$ rake flatten_routes:convert

を実行することで、上記の例でコメントされていた部分が記述され、オリジナルのresourcesresouceを使っていた方がコメントアウトされる。

railsからrouting情報を取り出す方法について

$ rake routeがそれっぽい情報を表示してくれているのでその辺のソースを参考に調べた。(このgemを書いた当時はrails5が出る前だったので4-2-stableブランチを参照した)

rails4.2におけるrake routesタスクは以下の様に実装されている

desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.'
task routes: :environment do
  all_routes = Rails.application.routes.routes
  require 'action_dispatch/routing/inspector'
  inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes)
  puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, ENV['CONTROLLER'])
end

Rails.application.routes.routesでrouting情報の構造を取得し、それを ActionDispatch::Routing::RoutesInspectorに渡して、 ActionDispatch::Routing::ConsoleFormatter で定義されたformat(定義箇所はここ)で整形されたものをputsしているということがわかる。ちなみに、ActionDispatch::Routing::RoutesInspectorActionController::RoutingErrorがraiseされたときに出るエラーページのルーティング一覧を表示する部分でも使われており、htmlのtableに整形するためのformatterも実装されている

なので、今回はオリジナルのformatterを作ってそれをinspectorに渡すように実装すればいいかなと思ったが、別にformatterを差し替えて使いまわすような使い方を提供するgemでもないので、ベタっとformatterを書いて実装した。中身はroutes.rbのフォーマットに合わせて置換したり見た目を揃えるためにスペース入れたりとかそういう当たり前のことしかしていないので割愛。

補足

単に実際のURLをコメントするだけなら他のgemとしてはannotateというgemがある。
自分はプライベートでも仕事でもよく使っており、ActiveRecordのModelにコメントでschema情報を付けるだけのgemかと思っていたのだが、READMEをよく読んだらrouting情報もコメントしてくれる機能があったらしい。じゃあこれ使えばいいか

error_arranger

github.com

Controller内部でエラーが起きて、rescue_fromに渡されたりそのままraiseされてrack layerで処理される前に何か前処理を入れたりできないかなぁと思って作ったgem。

例えば

class PostsController < ActionController
end

みたいなcontrollerがあったとして、PostsController内部で発生したエラーメッセージに何か注釈をつけたりしたい、みたいなケースがあったら(あるのか?)以下のようにarrange_exception!を実装することで実現できる。

class PostsController < ApplicationController

  private

  def arrange_exception!(exception)
    exception.message << 'This error raised in PostsController'
  end
end

エラーがrailsで処理される前にcallbackを差し込む仕組みについて

結論から言えばモンキーパッチを当てているので、あまり行儀が良い方法ではない。

で、モンキーパッチの内容は下記である。

module ActionController
  module Rescue
    extend ActiveSupport::Concern
    include ActiveSupport::Rescuable

    private
      def process_action(*args)
        super
      rescue Exception => exception
        request.env['action_dispatch.show_detailed_exceptions'] ||= show_detailed_exceptions?
        arrange_exception!(exception) if self.respond_to?(:arrange_exception!)
        rescue_with_handler(exception) || raise(exception)
      end
  end
end

単にcontrollerにarrange_exception!という名前のメソッドが生えていたらそれを呼んでからrescue_fromで定義されたhandlerに渡すかそのままraiseする、という感じである。

このgemを作った当時、Controllerのactionで発生したエラーがどこでhandlingされるか分からなくて散々railsのソースを読んでようやく見つけた記憶があるのだが、rails/actionpack/lib/action_controller/metal/rescue.rbに実装されている。

ソースをちゃんと読み切れていないのだが、どうもrails/actionpack/lib/action_controller/metal配下に実装されているモジュールがロードされて、そのモジュールの中でprocess_actionというメソッドをoverrideしていってstackされていき、実際にリクエストが来てactionが実行されるときにcallbackとしてcontrollerからprocess_actionが呼ばれる。。という感じのように見えるのだが、よくわかっていない。謎い。

practical_errors

github.com

先に説明したerror_arrangerを使って、railsでよく見るエラーにもう少し丁寧な解説をつけてみたらrails初心者に優しくなるんじゃなかろうか?と思って作ってみたgem。

例えば、Post.find(1000)を実行してActiveRecord::RecordNotFoundがraiseされたとする。

そのときには以下のようなメッセージをerrorオブジェクトに追加してくれる。

ActiveRecord::RecordNotFound (
========== Practical Errors appends message from here ==========

Rails says, "Couldn't find Post with 'id'=1000".

You might have called ActiveRecord::FinderMethods#find, but the record for provided id was not found.
If you DO NOT want to raise ActiveRecord::RecordNotFound even if the record was not found,
you should use ActiveRecord::FinderMethods#find_by instead.

Post.find(1000) # raise ActiveRecord::RecordNotFound
Post.find_by(id: 1000) # just returns nill


========== Practical Errors appends message to here ==========
See more detail about Practical Errors: https://github.com/serihiro/practical_errors

)

idで検索してnilになるケースが想定される場合はfind_by使ってね、的なrails初心者のコードをレビューする時につけるようなコメントをrailsに勝手にやってもらえる。もちろん、ちゃんとエラーメッセージを読む相手であるということが前提だ。読まない相手の場合は頑張って教育しよう。

ちなみに、ActiveRecord::RecordNotUniqueのときの追加メッセージはこうだ。

The column included in the record that you were about to insert or update, is not allowed duplicated value.
This error was detected by database, not Rails.
You might have configured uniqueness validation to this column, but uniqueness validation is not panacea.
In race condition, uniqueness validation was passed easily.
For more detail, see rails document
http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_uniqueness_of

uniqueness: trueなvalidationを入れてもrace conditionではすり抜けるから気をつけような、的な。あまり役には立たないがActiveRecord::RecordNotUniqueがrescueされずに突き抜けてしまった時の慰めにはなるだろう。

ちなみにいまのところ対応しているエラーはActiveRecord::RecordNotFoudActiveRecord::RecordNotUniqueの2種類しかない。他にも色々作る予定だったが力尽きてしまったので、ぜひPRを送って熱いエラーメッセージを追加してもらえるとうれしい。

実装部分については特筆すべき事項はなく、前出のerror_arrangerを使ってraiseされたエラーのクラスを見てそのクラスに該当するAdviserクラスがあったらAdviserクラスにメッセージを付与してもらう、ぐらいの感じである。

      if PracticalErrors::ErrorAdvisers::const_defined?(exception.class.to_s)
        @customised_message << PracticalErrors::ErrorAdvisers::const_get(exception.class.to_s).advise(exception)
      else
        @customised_message << <<-"EOS".strip_heredoc
          hmm... I don't know this error :(
          Tell me => https://github.com/serihiro/practical_errors
        EOS
      end

Adviserがいない時は教えてくれ!っていうメセージを入れているが今の所一件も報告がないので誰も使ってないと思われる。PRお待ちしております。


トレタ Advent Calendar 2016の5日目は2回目の登場horimislimeによる「ここ最近作ってたMacアプリの話とか」の予定です。