Rubyを「知ってるつもり」の人にお勧めな「Metaprogramming Ruby」本
Tweet
とてもいい本だったので紹介してみる。
Metaprogramming Ruby: Program Like the Ruby Pros by Paolo Perrotta
この本を読み始めてすぐに、自分がこの本に対してタイトルから想像していた内容と違うことに気付いた。
自分が想像していたのは、「こういうケースでは、こういうメタプログラミングをするといいよ」「こういうメタプログラミングのパターンもあるよ」というRubyでするメタプログラミングの実践編の本かと思っていた。でも、これは間違いで、この本はRubyでメタプログラミングができるようになるためのRubyの基礎知識が書いてある本だった(基礎、と言っても初心者向けというわけではなくて、Rubyのベース部分という意味で)。
想像とは違っていたのだけど、結果的に、ちょうど今自分が読むべき本だった。
自分は、この本を読むまで半年ちょっとRailsを通してRubyを経験してきて、期間的にはそれほどでもないが、それなりの規模のシステムも作ってきた。その結果、それなりにRubyのことをわかっているつもりだったし、最近はRailsのソースとかも少しずつ読むようになってきて、見よう見まねでメタプログラミングもしていた。
そのくらいのときにこの本にめぐり合ったのだけど、とてもいいタイミングだったようだ。実はよくRubyのことわかってなかった、ということを痛感できた。
この本を読む前にメタプログラミング実践編的なのに入っていたら、上辺だけのメタプログラミングをしてしまい、訳がわからなくなっていたかもしれない。
この本の構成としては、
- 10%: メタプログラミングとは何か、の導入
- 50%: メタプログラミングで必要になってくるRubyについての解説
- 20%: メタプログラミングの基礎を習得したところで、RailsのActiveRecordを例にとってメタプログラミングがどう使われているかの解説
- 10%: メタプログラミングに対するテストの書き方
- 10%: 便利なTipsを含むその他
な感じ。
こんな感じで半分くらいをRubyそのものについて読ませる内容になっている。Rubyでメタプログラミングができるようになるための基礎体力をじっくり着けてくれる作りになっている。
たとえば、「スコープ」というプログラミング言語を勉強するときに基本中の基本なところだけども、10ページくらいかけてしっかりと教えてくれる。スコープの章の中の各タイトルを抜き出してみると、Scope/Changing Scope/Scope Gates/Flattening the Scope/Sharing the Scope とこれだけある。そして、スコープを理解することはメタプログラミングをするときにもとても重要なこともよくわかる。
その他にも、自分が今まであやふやに過ごしてきたことで、この本によってはっきりと理解できるようなったものとして、ぱっと思いつくものだけでも、
- Objectとは何か?
- Classとは何か?
- Class名とは何か?
- Classはどこにあるか?
- Class変数とは何か?
- selfとは何?
- メソッドとは何?
- Class メソッドとは何か?
- Instance メソッドとは何か?
- Moduleとはなにか?
- classとsuperclassとmoduleの関係
- methodを探す順序は?
- lambdaとは何か?
- procとは何か?
- lambdaとprocの違いは?
- instance_eval とは?
- class_eval とは?
- eval とは?
- include と extendの違いは?
- scopeが変わる場所はどこ?何種類ある?
- 特異メソッドはどこに存在する?
おそらく、それぞれの説明は、Googleにでも聞いてどっかのページを見ればわかると思うのだけど、なぜそうなっているのか、それぞれがどう関係しているのか、ということをこの本はとてもわかりやすく説明してくれる。そして、これらのそれぞれの技術が独立したものではなくて、関連し協調しあって、「Rubyらしさ」を作っていることもよくわかる。
読んでいるときには「ここまでの知識が必要?」と感じたところもあったけど、読み終わってみると、すべてがしっかりとつながっていて、余分かなと思ったところは実は伏線で、最後までにはしっかりと回収されている感じになっている。
この本を読んでから、Railsのコードとかを見ると、一行一行はっきり意味がわかるようになって、今までコードを読んでいるつもりだった自分はなんなんだったんだろう、という気分にすらなった。ちょっと大袈裟だけど、わりと本気。
1.9 対応
ちゃんとRuby 1.9にも対応している。たとえば、Objectの解説のところでは、BasicObjectのこともしっかり説明されていて、どういうときに使うのが便利か、ということまで解説している。
英語
今のところ英語版しかないようなんだけど、英語苦手な人でもコードの例もたくさんあるし、そんな難しくない英語なので大丈夫だと思う。BillとBobという二人の登場人物が対話する形でRubyの物語が進んで行く。読み物としてもよくできている。
そういう自分も英語だと日本語で読むより数倍遅い。でも日本語で読んだ時よりも頭の中に残っている感じはある。日本語だと字を追っているだけで頭に入ってないときとかあるんだけど、英語だと一文ずつしっかり読むので頭に残りやすいのかも。
お勧め
そんなわけで、Rubyをなんとなくやっているけど、深いところまでびしっと理解はしていない、という人にすごくお勧めな本でした。
日本語訳も出るようなのでそちらも期待!
resque-scheduler (resqueでcronみたいなことをする)
Tweet
resqueシリーズ。 前回 も resque-scheduler について少し触れたのだけど。resque-schedulerのメインであるcron的に使う機能について書いてなかったのでメモ。deployについても書いた。
cronと比較して
*nix系のシステムだと、伝統的に、何かを定期的に実行したいcronを使うのだけど、アプリケーションからcronを使う場合、いくつかの使いづらい点がある。
- アプリケーション本体から離れた場所でcronの動きを管理しないといけない。通常、/etc/crontab 等をメンテナンスしないといけないのだけど、そこはアプリケーションの外側なので、メンテナンスするときにアプリケーション側で使っている方法を使いづらい(SCMもそうだし、deployとかも別で考えないといけない)。
- cronから起動する場合、起動する環境について考えないといけない。環境変数が代表例。アプリケーションと同じような環境になるように注意しないといけない。
resque-schedulerを使うと、cronでするようなことをアプリケーション内で完結することできる。crontabのようなものをアプリケーション内に置けるので、アプリケーション本体を管理するSCMを使うこともできるし、deployもアプリケーションと常に同じ方法でできるようになる。
ただ、基本的にはcronの動きを踏襲しているので、cronの以下の(場合によっては)負の特徴も引き継いでいる。
- cronと同じように、指定した時間にresque-schedulerが立ち上がってない場合、その処理は行われない。
- cronと同じように、同一のタスクが同時に走ってしまう場合がある。例えば、毎分実行するような設定を書いた場合、その処理に1分以上かかってしまった場合、前の処理が終わる前に次の処理が走ってしまう。それが致命的な場合、自分でロック処理を書く必要がある。
- 複数のresque-schedulerを走らせることが考えられていない。複数のresque-schedulerを実行すると、同じ時刻に同じタスクが複数回走ってしまう。つまり、「冗長化のために、2台のアプリケーションサーバ上でresque-schedulerをそれぞれ動かす」ということができずに、冗長化した構成が取れない。
最後のは、嫌なので、複数のところで走らせても同一のタスクが複数回走らないようなパッチを書いて作者に投げてある。しかし、「冗長化構成が取れるように」ということには賛同を得たのだけど、そのパッチについてはいくつか課題がありそうなので保留になっている。気が向いたらもうちょっと頑張るつもり。
使い方
resque-scheduler を見るのが早いが、crontabで定義するようことをyamlで定義する。そのドキュメントの例を使わせてもらうと、
queue_documents_for_indexing:
cron: "0 0 * * *"
class: QueueDocuments
args:
description: "This job queues all content for indexing in solr"
こんな感じになる。この場合、毎日0時0分に、QueueDocumentsというResqueのJobがQueueに入ることになる。ただし、ResqueのQueueに入るだけで、resque-schdulerは実際の実行はしない、ということに注意。Queueを処理するのはResque自体のWorkerの仕事になる。
deployについて
resqueと同じように、resque-scheduler自身には、起動スクリプトみたいなものがついていないので、godやmonitのようなもので管理するのがいいっぽい。
自分はmonitを使って、こんな感じのwrapperを書いて、monitではそのwrapper経由で、起動、停止をしている。
なぜMongoDBなのか
Tweet
ここを見てもらってる人に、「MongoDBって何がいいの?」と改めて聞かれてしまって、ああ、そっか、そういうこと書いてなかったな、と思ったので、なぜ自分がMongoDBに興味を持っているのか、ということを書いてみた。いざ自分の思いを書いてみたらRails中心の話になってしまったけど、モダンなフレームワークならそんなに話は変わらないのかな、と思っている。
そもそものきっかけは、ここ半年間くらいRuby on Rails(以下RoR)で開発していることにある。
ここ半年弱ほどRoRで開発をして、それなりに満足しているのだけど、ActiveRecordに関しては色々とひっかかるところがあった。
「ActiveRecordがRoRの素晴らしいところそのものだ」と評価している人もいるが、自分の中では逆で、ActiveRecordはRoRの中でもかなりいまいちな部分。
いや、ActiveRecordというよりも、O/Rマッパーの限界なのかな。実際、ActiveRecordはO/Rマッパーの中では、いい方だと思う。今まで、自分用O/Rマッパーを作ったり、それなりの数のO/Rマッパーを見てきたけど、そういうのに比べても、確かにActiveRecordはよくできている。
でも、RoRがよくできたフレームワークなだけに、O/Rマッパー経由RDBMSを使うことに、色々な無駄を感じてしまう。
たとえば、1つのテーブルから同じ条件で検索し、複数のインスタンスを作るために、primary key(id)を検索条件として、何度も検索するとか。「そこはSQL一発で持ってこいよ」と思ってしまう。またそこからlazyに外部キーで他のModel(テーブル)のオブジェクトを持ってこようとすると、またprimary keyでの検索が走る。
一人のユーザが一つの画面を開くだけでSQLを何十回も発行することも普通に起こる。これはどう考えてもSQLの使い方として間違っている。SQLはそういう風に作られていないので、結局(RoR自体を含めた)アプリケーション側の別のレイヤーでインスタンスをCacheしたりと、どんどん複雑になっていく。
そういうのが嫌で、RDBMSとして正しく使おうと、find()で:includeとか:joinsとか使いだすと、今度はどんどんRoR(Ruby)のコードのSQL成分が増していく。全然楽しくない。
つまり、
- SQLっぽく書かずに、Ruby(ActiveRecord)っぽくきれいに書くように頑張る
- でも、ロジック的/パフォーマンス的に頑張るのが限界だった場合に、SQLっぽいコードをRubyに書くことになる
- そうこうしているうちにRubyらしい可読性の高いコードは消えSQLっぽいコードにまみれる
この流れにうんざりする。そもそも1.の段階でも脳内では普通にSQLのことを考えている。だったら最初からSQL書けよ、と思ってしまう。
結局、O/Rマッパーで、きれいに書けるところは、検索した結果でオブジェクトを作る、または、primary *key*指定で持ってきてそれを元にオブジェクトを作る。これだけ書けて、十分な性能が出るなら、RDBMSでなくても、(そこそこの機能を持った) Key Value Storageで十分なんじゃないのかな、とぼんやりと思っていた。
もう一つActiveRecordで嫌いなのが、Modelのコードにカラムの情報を持たない、というとこと。DRYのためだかなんだか知らないが、これはどうみても欠点だと思っている。Modelのコードを見ただけでどういう情報が入っているかわからない。migrationのコードはツギハギだらけで後から見るには向いていない。どういうModelか、ということを知るためには、動いているデータベースにアクセスしてスキーマ情報を見るしかない。かっこ悪い。
ってなことを、漠然と思っていた頃に、タイミングよくNoSQL(KVS)ブームが到来。「ああ、これだ」と思って、ブームに乗っかって、色々なKVSを試している中で、
「これなら最近のWeb開発の多くの部分でRDBMSを捨ててもいけるんじゃないかな」
と感じさせてくれたのがMongoDBだった。
数あるKVSの中でMongoDBの何がよかったのかと言うと、
- RoRのModelにぴったりと合いそう。上にも書いたように、KeyとValueの関係が、そのままModelのIdとInstanceの関係になりそう。
- Valueの部分はスキーマフリー&ダイナミックに作成可能なので、Ruby側のコード主導でカラムの追加をすることができる。Rubyのコードでのカラムの定義が「正」になる。もちろんDRYも守られたまま。
- SQL-likeなこともできる。where句(のようなもの)を指定したり、maxとかminとかgroup byも使えるし、やろうと思えばストアドプロシージャ(Javascript)のようなこともできる。この辺は、SQL脳の人(チーム)にとっては移行するためのハードルが低そう。
- 上記を踏まえた上で、KVSらしく、RDBMSでは厄介なスケールさせることも考えられていて、Shardみたいなこともデフォルトで用意されている。
とまあ、こんなところ。
つまり、最近のリッチなフレームワークの中では、DB側にそれほどリッチな機能はいらない。RDBMSはそのいらないリッチさがあり、そのせいでスケールさせるのが難しくなったりもしている。だったら、軽量でコンパクト、でもそれなりの機能を持って、そしてスケールしやすいMongoDBで十分なんじゃないのかな、と思った。
もしMongoDB始めてみようって人がいたら、こちらをどうぞ。
追記: CouchDB編もできたみたいです。私は、Railsは好きです。念のため。
RspecとCucumberでTDD/BDDを極める (The Rspec Bookの紹介)
Tweet
本の紹介第2弾。少し前、Twitter上でTDD/BDDについて盛り上がっていたので、この本を紹介してみたくなった。
「The Rspec Book: Behaviour Driven Development With Rspec, Cucumber, and Friends」という本。
この本は、RspecとCucumberを使い、どう考え、どうシステムを作っていくか、というをチュートリアルを交えながら紹介する構成になっている。
ただUnit Testを紹介するだけではなく、Unit TestツールであるRspecに、BDDツールであるCucumberを組み合わせて使うことで、Unit Testでカバーできない部分をCucumberで補い開発をする、というところがこの本の肝になっている。
この本を読み、実践することで、Unit Test*だけ*を書いてシステムを作っているときのモヤモヤ感をかなり解消できる。また、RspecもCucumberも知っているけど、両方をどうやって使っていくかよくわからない、という人もこの本を読むとかなりすっきりできるのでお勧め。
以下に、自分なりに理解したところを書いておく。本の内容から少し離れているところもあるので、詳しくは本を読んでみて欲しい。
また、ここでは触れないが、この本では、RspecとCucumberの基本的な使い方や、webratやseleniumなどのWeb系のテストツールと一緒に使う方法や、Railsとの組み合わせ方なども含まれているので、Rubyでのテストに関するかなりの部分がカバーされていてかなりお得な作りになっている。
Unit Testとは
改めて書くまでもなく、Unit Testというのは(自動化するかどうかは関係なく)、とにかくコンポーネントとしての精度を高めるものである。また、Unit Testの優れている点として、Unit Testは「テスト」だけのためではなく、「設計」のためであるということが言える。テストを書くということ自体が設計となる。テストが書きにくい=設計が悪い、ということでもあるので、テストをきれいに書くことで、自然といい設計になるように導いてくれる。
しかし、Unit Testは、コンポーネント単位での動作は保証するが、それらを組み合わせたときに、最終的にシステムがどのような物になるか、ということまでは基本的保証しない。そして、Unit Testは、基本的にプログラマの自作自演ということもあり、外部から、システムとして見たときにどう動くかということも保証しない。また、非エンジニアの人にどういうシステムか、というのを説明するのにもUnit Testは向いていない。
これらは別にUnit Testの欠点というわけではなくて、Unit Testはそういうものなのである。
「Unit Testだけ」で開発するモヤモヤ
Unit Testでカバーできる範囲は「設計」を設計としてそのままコードに落とし、そしてそれがそのままテストにもなり、自動化もでき、再利用もできる。
しかし、Unit Testの対象外である、システムを外部から見たときの振る舞いに関して、設計やテストはコード化されずに、BTSや色々なツールで管理しても、結局「使い捨て」になりやすい。ドキュメントをコードとは別に書いても、プロジェクトが進むごとにコードとどんどん乖離していってしまう。
今まで、この部分を解消するためにいろいろなツールやプラクティスを試してみたが、どうも自分的にしっくりくるのがなかった。それは、おそらく、そういうものの多くは、ドキュメント(紙)ベースなものが多いからだと思っている。やはりプログラマとしては、そこも「コード」で定義したいし、コードに書いてあることをドキュメントにも書くということはしたくない。
Cucumberとは
このモヤモヤを解決してくるのがCucumberというツールと、それを使ったプラクティスになる。Cucumberでは、外側の設計(とテスト)をコードに落とすものである。Cucumberを使うと、ユーザから見た動作に関しての設計を、そのままコードにすることができる。Unit Testでコンポーネントに対してテストを書くのと同じ構図になる。
また、Cucumberは非常に自然言語に近い形で書けるので、非エンジニアの人が読むことが(場合によっては書くことも)可能だ。
RspecとCucumberの連携
RspecとCucumberは、それぞれ単体で使っても、もちろん便利なのだが、この本では、それらを組み合わせることで、さらに強固で柔軟なシステムを作ることができると説明している。
コンポーネントレベルでの動作の保証にはRspecを使い、ユーザから見た動作に関しての保証にはCucumberを使うということになる。
この本では、1つの機能毎に下記の各フェーズを繰り返し開発を進めて行くことを提案している。
- Cucumberを使い外部の設計(=テスト)を書く - この段階では実装がないのでCucumberに実行結果はRed(失敗)になる。
- Rspecを使い内部の設計(=テスト)をする - この段階では実装がないのでRspecを実行するとRedになる。
- Rspecが通るように実装する。この時点でRspecはGreen(成功)になる。
- Cucumberが通るようにCucumberの実装を書く。ここで、2-3で作った部分がうまく1.の設計にあわない場合、2.に戻る必要がある。場合によっては1.に戻る。
つまり、外側 (Red) - 内側 (Red) - 内側 (Green) - 外側 (Green) という順序で開発して行くことになる。
この手法は、非常にアジャイル開発とも相性がいい。上記の1から4の一回で作る機能の単位として、アジャイルの一つのユーザストーリはぴったり当てはまる。
本の入手方法
ここまで書いておいてなんなんだが、まだこの本は紙の本としては発売されてない。延期につぐ延期で、次の予定は2010/4になっている。しかしとりあえず現時点ではベータ版をPDFで買うことができる。上のリンクから買える。
一度購入しておくと、新しいベータ版が出る度にダウンロードできるので、個人的にはここ半年位楽しんでいる。英語だが、チュートリアル形式でコードも多いので英語にあまり自信がなくてもなんとかなるだろう。
Amazon日本でも紙の本の予約はできるみたい。
RailsでResque使い始めた
Tweet
これとこれの続き。この後、もう少し調査して、Resqueを実際のシステムの一部で使い始めてみたのでその感想とメモ。
前回までのあらすじ
Resqueはバックグラウンドでジョブの実行をするもので、かなりの大規模サイトでかつ更新系の処理が多そうなシステムであるGithubで開発され使われている。よくある使い方としては、「Web UIを軽く見せるため、処理の依頼だけを受け付け、実際の処理はバックグラウンドで実行」「バッチ処理などで、大量のJobをQueueに突っ込んでおいて、(複数の)workerで並列で効率よく処理」などがある。
不安なところ
Resqueの大きな特徴は、QueueをRDBMSではなくRedis上に作るところにある。Redisは、Memcacheのようにシンプルに使え、すべてのデータはメモリ上に展開されるのでとても速く、データはディスク上にも永続化されるので、何かあったときにもMemcacheと違いデータが揮発しないのが特徴のkey-value型のデータベース。
今回Resqueを導入したWebシステムは、よくあるRDBMSを使ったものなので、Resqueのために新たに別な種類のデータベースを導入することへの躊躇いが多少あった。サーバ設定の手間も増えるし、各開発者の開発環境のセットアップの手間も増える。また、常時使うデータベースのようなものが一つふえると言うことはメンテナンスや障害時の手間も増える。また、RDBMSならすぐ中を確認できるが、Queueの状況が見づらいのではないか、と言った心配もある。
正直なところ、上記の不安はまだ全部それなりに残っている。が、魅力も見えてきたのでとりあえず使ってみることにした。実際に使ってみないとわからないこともあるしね。
良さそうなところ
上記のような不安がある中で、導入に踏み切った大きな理由として、QueueをRDBMSに置かずにRedisに置く、というのが実際の運用ではかなり良さそうというのがある。
メインで使っているRDBMSと同じところにQueueを置くと、RDBMSの影響をモロに受けてしまう。せっかく重い処理をバックグラウンドでするためにResqueを使っても、そのQueueの登録部分が重くなってしまっては意味がない。Queueの部分が別DB(Redis)だとこの辺のことに影響されず「とりあえずQueueに入れる」「後はサーバの状況に合わせQueueを順番に処理する」といったことができるようになる。
後は、Resque自体の作りが非常に良さそう。例えば、 convention over configuration な感じがよくできていて、新しいQueueやJobを作るときにあまり難しいことを考えずに、Rubyのコードを書くだけで済む。
Deployに関して
Resqueには、起動スクリプトのようなものが添付されていないので、何か自分で起動させる方法を考えないといけない。Githubではgodを使っているようだ。今回うちでは普段使っているCapistranoとMonitを組み合わせてDeploy時に(再)起動とプロセスの監視をすることにした。また、Resque(のworker)は、自分のプロセスとしてRailsの環境を読み込みので、アプリケーションのコードが変わった際には再起動の必要がある。
まず、Resqueはpidを保存してくれたりしないので、monitのFAQに従い、簡単な起動用のWrapperを書きそれをmonitに監視させている。Capistranoでのdeployプロセスでは、monitのrestartコマンドを呼ぶだけでResqueのworkerも再起動してくれるようになった。
ちょっとこの辺が最初だけとは言えめんどくさい。
監視に関して
ここがまだ弱いのだけど、とりあえずResqueに添付されているWebアプリケーションを目視するという弱い方法で行っている。ここは改善予定。待て次号!
とりあえず、こんなところ。
RSSリーダで読む