著者:かずひこ
Rubyist のみなさん、こんにちは。 私事ですが、先日 4 年間務めていた日本 Ruby の会理事の職を離れ、晴れて「ただの日本 Ruby の会会員」になりました。 そこで、「ただの日本 Ruby の会会員」というか「ただの Rubyist」にできることって一体何だろう、と考えてみることにしました。
日本 Ruby の会 公式 Wiki - 日本 Ruby の会とはによると、日本 Ruby の会の目的は、
だそうですので、だったら私にもいろいろできることはあるのかなと、この 1 ヶ月間にいろいろやってみたことを紹介したいと思います。
Ruby を使うのは当然として、世の中にたくさんある「Ruby で書かれたフリーソフトウェア」を使ってみましょう。 もちろん、使うだけでなく、フリーソフトウェアならソースを見て勉強できますし、自分好みにカスタマイズしたり、新しい機能を追加したりできますから、「何となく使う」ではなく、「使って使って使い倒す!」勢いが肝心です。
私が使っている主な Ruby のフリーソフトウェアは、7 年半くらい前から使っているブログじゃなくて Web 日記システムの tDiary と、6 年半くらい前から使っている Wiki エンジンの Hiki です。 そして、自分で使うだけでなく、tDiary と Hiki のホスティングサービスを提供することで、さらにたくさんの人にも Ruby のフリーソフトウェアとの出会いの場を提供しています。
さて、この 1 ヶ月の間に、私が新たに使い始めた Ruby で書かれたフリーソフトウェアは、ただただしさん作の amazon-auth-proxy です。 これは、2009 年 8 月 15 日から Amazon の API 利用に認証が必要になったため、(tDiary や Hiki などの) フリーソフトウェアから使う場合は秘密鍵をいっしょに配布するわけにいかず、利用者一人一人が API の認証鍵の取得を強いられてしまいかねなくなったことに対し、認証部分を代行する proxy を (これまでのと同じ API で) 提供することで、元のフリーソフトウェア利用者に新たな負担をかけない、というアイデアのソフトウェアです。
tDiary や Hiki の大口 (?) 利用者である私としては、この amazon-auth-proxy を大いに必要とする立場ですので、さっそくインストールするとともに、さらに他のユーザの役にも立てるように、風柳さん作の Product Advertising API用リバースプロキシ1に参加しました。 ちなみに、Product Advertising API用リバースプロキシに参加している人の多くは、私とおなじく tDiary ユーザだそうです。 こんなところでも、Rubyist は Nice ですね。
Ruby で書かれたフリーソフトウェアの情報は、例えば RAA (Ruby Application Archive) や RubyForge で得ることができますので、みなさんも自分の好みにあったものがないか、ぜひ探してみてください。
次に、使うからには常に最新版を追いかけたいものです。 古いバージョンにあったバグが最新版では直っているかも、というのも理由の一つですが、それよりも、最新版は開発している人たちが通常もっとも情熱を注いでいるバージョンですので、最新版を追いかけることで、そんな開発者の情熱をより熱く感じられることができるからです。
そんなわけで、私もこの 1 ヶ月間で、手元の環境をいろいろアップデートしてみました。
まずは、Ruby 本体を 1.8.5 から 1.8.7 にアップデートしました。 これは、私がインストールした当初の amazon-auth-proxy が、Array#tap を使っているために古い Ruby で動かなかったからというのが本当の理由なのですが、1.8.5 という今はもうメンテナンスされていないバージョンを使いつづけるのも不安ですから、ちょうどいい機会でした。
続いて、tDiary や Hiki も久しぶりにアップデートしました。 しばらくアップデートをさぼっていたので、随分いろんな変更がありましたが、その間の変更箇所を斜め読みすることで、だんだん勘が戻ってきました。
プログラミング言語の学習は、英語やフランス語などの自然言語の学習と同じく継続がとても大切ですので、そういう意味でもこまめなアップデートをして継続的にコードに触れる機会を作るのは良いのではないでしょうか。
そして、アップデートと言えば、もちろん Ruby 1.9 を忘れるわけには行きません。 これから Ruby を学ぼうという人は、過去に縛られることなく最初から Ruby 1.9 を使えばいいのですが、tDiary や Hiki のように大昔 (Ruby on Rails 以前!) からあるソフトウェアでは、Ruby 1.9 ではそのまま動かないものもたくさんあります。
もし、今使っているソフトウェアが Ruby 1.9 に対応していない場合、これも嘆くところではなく、むしろ「じゃあ Ruby 1.9 に対応させてあげようじゃないか!」と心踊らせるべきところです。 そんなわけで、この 1 ヶ月間で Hiki の Ruby 1.9 対応に挑戦してみました。
Ruby 1.9 への移行については、まずは同じ「るびま」の記事、Ruby 1.9.1 の歩き方の中の、松江 Ruby 会議でのパネルディスカッションが参考になります。
まず、Ruby 1.8.6 を使っているプログラムは、Ruby 1.8.7 に対応させる。 Ruby 1.8.7 で警告を表示するオプションをつけてプログラムを起動すると、標準エラー出力などに、例えば、String#each や String#map など、 Ruby 1.9 で動かなくなる機能について警告が出る。それらについては、Ruby 1.9 でどうなっているかを調べて修正していく。
これは最初の一歩としてとても有効なのですが、警告が出るのは残念ながら「Ruby 1.9 で動かなくなる機能」だけであって、「Ruby 1.9 では仕様が異なる機能」については警告してくれません。 例えば、String#size は Ruby 1.8 ではバイト数を返すが Ruby 1.9 では文字数を返す、とか、Array#join は Ruby 1.8 では Array#join($,) と同じだが Ruby 1.9 では Array#inspect と同じ、とかです。
でも、人間の目でそういう場所を探すのには限度がありますし、grep などで検索しても動的に呼び出している場合などひっかからないケースもあります。 そこで、これまでどおり Ruby 1.8 で動かしながら、そういう「やばそうなメソッド」が呼ばれた時に呼び出し元の情報付きで警告を出すライブラリを作ってみました。
def add_warning(method)
owner = method.owner
name = method.name
orig_name = "orig_#{name}"
str = <<-SRC
#{owner.class.to_s.downcase} #{owner}
alias :#{orig_name} :#{name}
def #{name}(*args)
log_warning "#{method.receiver.class}##{name}"
#{orig_name}(*args)
end
end
SRC
eval str
end
$log_warning_hash = {}
def log_warning(method_name)
log_msg = %Q!(compatibility warning) #{method_name} used in #{caller[1]} !
return if $log_warning_hash.has_key?(log_msg)
$log_warning_hash[log_msg] = true
$stderr.puts log_msg
end
# String#size has a different behaviour on Ruby-1.9
add_warning "".method(:size)
# String#length has a different behaviour on Ruby-1.9
add_warning "".method(:length)
# String#slice has a different behaviour on Ruby-1.9
add_warning "".method(:slice)
# String#slice! has a different behaviour on Ruby-1.9
add_warning "".method(:slice!)
# Array#to_s has a different behaviour on Ruby-1.9
add_warning [].method(:to_s)
# String#to_a is not defined on Ruby-1.9
add_warning "".method(:to_a) rescue nil
使い方は、動かすプログラムの適当な場所で require “compatibility_warning” と加えるだけで、例えば Hiki を Ruby 1.8 で動かすと、こんな error_log が得られます。
これを見ながら、String#to_a は str.split(/^/) に書き換えるとか、String#size は String#bytesize にすべきかそのままでいいか確認するとか、していけるわけです。 とりあえず、Hiki で使っていそうな機能でぱっと思いついたメソッドで警告が出るようにしましたが、ソースの末尾の add_warning メソッドの要領で、ニーズにあわせて簡単に拡張することができます。
ただし、compatible_warning ライブラリのやり方では、メソッドを実際に呼び出した時点で警告するので、めったに実行されないコードをこの方法で検査するのは無理があります。 ちゃんと隅々までカバーされたテストがあればいいのですが、現状の Hiki は残念ながら包括的なテストがありません。
ちなみに、似た目的の別アプローチの実装に、id:takehikom さんによる Ruby 1.9移行のための簡易lintを作ってみたがあります。 こちらは、grep でソースコードを検索するようなスタイルですので、動的なメソッド呼び出しに対応できませんが、指定されたソースコードを隅々まで検索しますから、どういうケースに実行されるコードかどうかに関係なく見つけることができます。
Hiki を Ruby 1.9 で動かすのにはたくさんの課題があるかと思いましたが、tDiary がすでに Ruby 1.9 対応を果たしていたこと、そして xibbar こと藤岡さんが「tDiaryなどのレガシーウェブアプリをRuby1.9で動かす方法」のスライドを公開で既にかなりの部分を試みてくれていたこともあり、この compatible_warning ライブラリも使いながらだいたい 1 週間くらいでほぼ対応させることができました。
フリーソフトウェアなら、ソースコードを読み放題です。 しかし、そうは言っても、ある程度以上の規模のソフトウェアのソースコードをいきなり全部読むのは大変です。 そこで、まずは「日々、どんな変更が行われているのか」を中心にコードを読んでみるのがお勧めです。
多くのフリーソフトウェアで、「コミット通知メール」と呼ばれるメーリングリストが提供されています。 これを購読すると、コミット、つまり誰かがソースコードを変更した際に、「コミットログ」と呼ばれるその変更の意図を説明した文章と一緒に、その変更箇所だけが配信されます。
例えば tDiary の場合、tdiary-svn ML というメーリングリストがあります。 そのメールの一例を紹介します。
これくらいの量でしたら、ちょっとコードを読んでみようかなという気になりますし、コミットログもあるので「こうしたいからこう変更する」というのを小さい単位で知ることができます。 そして、もしこのコミットに興味がひかれたら、もう少しまわりのコードを読んでようとか、どうやってこのメソッドを呼び出しているのか調べてみよう、という風に、そこを入口にしてさらに広いコードの世界を探検することができます。
こうやってコードを読む人が増えれば、その分、間違えたコードをコミットした時に気づきやすかったり、読みやすさや処理速度の面などでさらにいいコードを提案する人が出てきたりしますし、コミットする側も、たくさんの人がコードをレビューしていると思えば、自然と「よりよいコードを書きたい」と感じるでしょう。
そんなわけで、積極的にコードレビューをすることは、あなたの実力を伸ばす助けになるのはもちろんのこと、それだけでなく他の人が書くコードの質が上がる助けにもなるのです。
さて、ソフトウェアを使っていると、何らかのバグを見つけることがあります。 そんな時、この世の不幸が自分だけにやってきたような気持ちになる必要はありません。 むしろ、「バグ報告できるチャンスが来た! つまり、私のお陰で世界は前よりもよくなる!」と前向きに受け止めましょう。
では、この 1 ヶ月で私が遭遇したバグを紹介します。
まず、Ruby を 1.8.5 から 1.8.7 に上げると、同じサーバで稼動している tDiary のホスティングサービスで、エラーが出るようになったと報告がありました。 元のバグ報告をした方が詳しく報告してくれたおかげで、ほどなくこれは Ruby 本体のバグだと感じましたので、Ruby の ChangeLog を見てあたりをつけながらリビジョンを遡って行って、1 年以上も前のコミット以降バグが発生するとわかったので、Ruby のバグ報告システムに報告しました (Ruby-1.8.7 $SAFE=4 の array/hash の recursive 比較で SecurityError)。
「使う」そして「アップデート」という二つのプラクティスのコンボで、1 年以上存在しつづけたバグが発見されて修正されたのは、とても感動的でした。
つぎに、tDiary と同じサーバでホスティングしていた quickml というメーリングリストサーバが動いていないと報告がありました。 調べてみると、確かにプロセスは起動しているのですが、listen しているポートにアクセスしても応答がありません。 試行錯誤の結果、バックグラウンドで起動しないようにすれば動くのに気づいたのですが、IRC で相談したところ、すでに報告されている Kernel#system doesn’t work in forked process というバグと同じ原因だと指摘してもらいました。 このバグは、この原稿の執筆時点でまだ解決しておらず、とりあえずは Ruby 1.8 を ‘–disable-pthread’ でビルドすることで回避しています。
ただ、この問題は Ruby 1.9 では再現しませんので、このバグを踏むソフトウェアを片っ端から Ruby 1.9 対応にする、というのがあるべき Rubyist の姿かもしれません。
気づいたバグに対して、その条件を詳しく調べ、再現する短いコードを書き、さらにはバグまで直してしまいパッチを投稿する、まで一人でできれば、それはもちろん素晴らしいことですが、そうでなくても他の人と力をあわせることで解決できることはたくさんあります。
例えば、前述の Ruby-1.8.7 $SAFE=4 の array/hash の recursive 比較で SecurityError というバグは、tDiary のこういう使い方で必ず再現するのは分かっていましたが、再現する短いコードを書くことはできませんでした。
でも、自分なりに分かっている範囲のことをすべて書いてバグ報告を出してみたところ、西山和広さんがバグ修正のパッチを書いてくれて、さらに遠藤さんがこの修正を確認する短いテストコードを書いてくれました。
こうやって、一人では無理なことも誰かが助けてくれたお陰で解決しましたし、「自分なりにやってみたその先」を見るのはとても勉強になりました。
自分が使っているソフトウェアのどの処理で主に時間がかかっているのかとか、Ruby ではどういう処理で時間がかかるのか、というのを知るために、たまには自分が使っているソフトウェアのプロファイルをとってみるといいでしょう。
プロファイラとは効率改善のための調査に用いられるツールのことで、Ruby に標準添付の profile ライブラリ2を使えば、各メソッドの実行時間に関する統計を出力してくれます。
さて、私が久しぶりにプロファイルをとってみようと思ったきっかけは、先日の RubyKaigi 2009 で行われた tDiary 会議です。 そこで、「tDiary を Ruby 1.9 で動かすと妙に遅い」という発言があったので、じゃあ Ruby 1.8 と Ruby 1.9 とでプロファイルを取って見比べてみよう、と思ったのでした。
まずは、Ruby 1.8 でプロファイルを取るとこんな感じです。
つぎに、Ruby 1.9 でプロファイルを取るとこんな感じです。
Ruby 1.9 は、いろんな部分が Ruby 1.8 より速くなっていますが、逆に有意に遅くなっている主なものは、eval と文字列処理です。 確かに、Ruby 1.8 でも 1.9 でも、instance_eval が上位に君臨しています。 そこで、まずは instance_eval をちょっとでも減らせないかと、コードを追いかけ始めました。 その結果、プラグインの読み込みが 1 アクセスあたり 4 回も行われていることが分かり、無駄な重複がなくなるようにコードを修正することで、なんと 4 割くらい速くなりました。
次に、Ruby 1.9 では、Kernel.require が一番合計時間を食っているのが気になります。 Ruby on Rails のように、一度プロセスが起動したらあとは起動したままアクセスをさばくアプリケーションですと、require はプロセス毎に一度ずつ行われるだけですが、tDiary は基本的にはリクエスト毎にプロセスが起動されるただの CGI アプリケーションですので、require もリクエスト毎に毎回行われてしまいます。
実際、今まで require されていないライブラリを require する変更がコミットされた時に、2% ほど動作が遅くなりました。そこで、似た機能を提供するライブラリは一つだけ使うようにするとか、めったに使わない機能でだけ必要なライブラリは、autoload を使って必要な時だけ require されるようにするとか、考える必要があります。
もっとも、最近コミットされた FastCGI 版を使えば、require のコストはプロセス毎に一度だけですし、予定されている Rack 対応が完了すれば、FastCGI も含めて tDiary でも常時起動が当たり前になるかも知れません。 そうなれば、require を必死になって減らしても関係無いので、こっちはあまりムキにならないでおこうと思っています。
おかげで、Ruby 1.9 での動作は確かに速くなったのですが、同時に Ruby 1.8 での動作も速くなってしまい、「Ruby 1.9 で動かすと妙に遅い」という当初の問題は完全には解決されていません。 ただ、Ruby 1.9 で動かす方が遅いとは言っても、未来はこっちにあるわけですから、私自身の日記を Ruby 1.9 に移行して、今後も Ruby 1.9 でのパフォーマンス向上に取り組もうと思います。
FastCGI のようにプロセスが起動したまま動作するソフトウェアは、安定したサービス提供のためにも、メモリ使用量とその変化をチェックすることが大切です。
そんなわけで、私も tDiary を Ruby 1.9 + FastCGI の環境で動かして、たくさんのアクセスをしながらメモリ使用量の変化をチェックしてみることにしました。 たくさんのアクセスをするために、今回はお手軽に Apache HTTP サーバに付属する ab (Apache HTTP server benchmarking tool) を使いました3。
すると、明らかにアクセスごとにメモリ使用量が増えていきました。 これはおかしいと思い、Ruby 1.8 でもチェックしてみると、こちらはほとんど変化がありませんでした。
そこで、まずは以下のような変更をして、アクセス終了後に回収されずに残っているオブジェクトの種類を多い順に出力してみました。
その結果、Ruby 1.9 ではこんなログが得られました。
このデータを元に IRC で相談すると、なかむら(う)さんが短い再現コードとともに Ruby 1.9 のバグとして登録してくれました。
まず、前者は中田さんが修正して、後者はささださんが修正してくれて、今では Ruby 1.9 と FCGI の環境でメモリ使用量の変化がほぼ落ち着きました。
以前から、「Ruby のバグを踏むのは tDiary」と定評がありましたが、今回もまたちゃっかりバグを踏むことで、Ruby 1.9 の安定化に少しでも貢献できてよかったと思います。
ソフトウェアを使うだけでなく、作る側の一員になってみると、ちゃんと他の人の環境でも動いているだろうかとか、どんな風に使われているだろうかとかを知りたくなります。 一昔前ですと、公式メーリングリストがあって、そこにバグ報告や改善案などが自然に集まっていたのですが、今ではブログをはじめとしてユーザの人たちが何かを書くメディアが多様化したこともあり、そういう「オフィシャルなルート」にやってこない声もたくさんあります。 個人的にはそういう状況はちょっと残念にも思いますが、そうは言っても仕方がないので、ときどきこちらからパトロールしましょう。
私がよく使っているのは、ブログ専用の検索エンジンや、twitter 検索です。 実際、twitter で tDiary とか Hiki とか書かれたつぶやきを見つけたら、「そのバグはここを見てください」とか「エラーをもうちょっと詳しく教えて」とか返事をしまくって、必死にユーザを引き止めています。 また、Hiki のリリースの後には、「アップデート手順がわからない」という声をいくつか見かけたので、そのあたりのドキュメントをもう少し改善したいと思っています。
ただ、開発者の側からそういう声を探してまわるのは、効率の悪いことであるのは確かですので、みなさんも不具合に気づいたり改善のアイデアがあったりした場合は、ぜひその声を開発者に直接届くようにしてください。 また、届いてなさそうだけれど大切っぽい声を偶然見かけた場合にも、ぜひタレコミをよろしくお願いします。
さて、私たちが愛してやまない Ruby は、フリーソフトウェアです。 ですから、Ruby の未来も Rubyist の未来も、フリーソフトウェアの更なる発展なしにはありえません。 というか、もしこの世にフリーソフトウェアが無ければ、今読んでいる Rubyist Magazine が存在することもなかったでしょうね。
「フリーソフトウェア=無料=質が劣る」みたいな根拠のない勘違いは、さすがに以前よりは少なくなりましたが、 もしみなさんが仕事で Ruby や Ruby on Rails を使ったシステムを提案しようとして、お客さんに「フリーソフトウェアって大丈夫?」と聞かれるかも知れません。 その時に、例えば Windows パソコンで PowerPoint でプレゼンをして、例えば Mac OS X パソコンで Keynote でプレゼンをして、一方でフリーソフトウェアの Ruby や Ruby on Rails の素晴らしさを熱く語って信用してもらえるでしょうか? 大丈夫、Ruby を使ってフリーソフトウェアでプレゼンをするなら、Rabbit があります!
私たち Rubyist が率先してフリーソフトウェアを使えば、ソフトウェアを使う人にとっても作る人にとっても、フリーソフトウェアがより当たり前な社会になって、その結果きっと利用者も開発者も増えていくでしょう。 逆に、もし私たちが当たり前のようにプロプライエタリ (フリーでない) ソフトウェアを使いつづければ、その逆の結果をもたらすことでしょう。 Ruby を愛するのと同じだけの気持ちで、他のフリーソフトウェアも大切にしてほしい、それが私の願いです。
この原稿を書いている時点で、日本 Ruby の会会員の数、つまり日本 Ruby の会のメーリングリストを購読している人の数は、ちょうど 400 人です。 この 400 人もの Rubyist が、例えばここに書いたようなことを何かやってみて、そしてそれが 1 年間積み重なれば、来年の RubyKaigi の頃には Ruby をとりまく世界はどんな世界になっていることでしょう。 そんな想像をしてみると、とてもわくわくしませんか?
フランス、リール市在住のただの日本 Ruby の会会員。 所属は Nexedi SAで、フリーソフトウェアの ERP である ERP5 の開発をしています。 なので、仕事でよく使う言語は Python です。
Ruby と Python。 O さんが好きな人もいれば、K さんが好きな人もいます。 私はどちらも好きです♪
Product Advertising API用リバースプロキシ は、Ruby ではなく Python で書かれています。 ↩
Ruby 1.8 標準の profile ライブラリは、それ自身のオーバヘッドがかなりあり、実行時間が数倍から数十倍くらい遅くなるので、代わりに ruby-prof ライブラリを使うのがお勧めですが、ここでは同じフォーマットで比較するために、あえて Ruby 1.8 でも標準の profile ライブラリを使っています。 ↩
名前のとおり、本来はパフォーマンスを測定するためのツールで、プロファイルをみながらコードを変更するときも、これを使ってその効果を調べました。 ↩