Ruby Advent Calendar 2017 13日目の記事になります。
使っているRubyGemの挙動や仕組みを理解するためによくコードを読んでいるのですが、今回はコードリーディグについて記事を書いていこうと思います。
RubyGemのコードを読む利点
- Rubyのコードの書き方の勉強になる
- RubyGemの書き方の勉強になる
- モノによるが、gemのアップグレードなどでgemが動かなくなってもメンテナンスできるという安心感を手に入れることができる
- ブラックボックスではなくなるので、技術選定のハードルが下がる
- 使っているライブラリの中身を知ることが出来るので、納得感を持ってそのgemを使うことができる。コードベースで把握しているので使い方も忘れにくい
- 仕組みをストックしておくことで、他言語・他環境への横展開ができる
- 不具合や欲しい機能があればプルリク送ってマージされるかもしれない
ということで普段何気なく使っているRubyGemを、使うだけでなく読んで理解することはとても意義があると思っています。
どうやって読んでいるか
だいたいこんな感じでやってます。
- 適当なディレクトリを作ってその中で
bundle init
- Gemfileに対象のgemとpryを入れる
bundle install –path vendor/bundle
- gem内の読みたい個所に
binding.pry
を入れる
- irbや
rails console
を実行したり、適当なスクリプトを作って bundle exec
で実行する。binding.pryを入れたところで止まるのでステップ実行したりバックトレースを追って全体のコードを把握。
- そこを起点にコードを読んで理解を深める
ひたすらにコードを読んでも良いんですが、変数の値やバックトレースを逐次確認することで理解するスピードも上げつつ、誤読も少なくなるというメリットがあったりします。
コードリーディングをおすすめしたいOSS
ということで、ここからはコードリーディングをおすすめしたいOSSを紹介していきます。オススメ度に関してはこれまでの私の経験に依存しているので参考程度で拝読ください。あまり読んだことがない or Ruby初心者の方は難易度1のものから読んでいくと良いと思います。
1ファイル200行以下のコードでとても読みやすいです。method_missingを使いつつ、class_eval経由で動的にメソッドを定義するスタイルはRubyならではという感じで、Rubyのメタプログラミングの実用例の入門としては良いと思います。とてもシンプルなのでRubyGemの書き方を学ぶ上でも有用です。
core_extといっても色々あるんですが、String、Array、Integerあたりはスっと読めると思います。基本、Rubyのオープンクラスを使って新たにメソッドを定義している感じです。
Rackミドルウェアですがファイル数・コード行数も少なくて読みやすいです。Thread.currentの動作やRackミドルウェアの書き方を学ぶには良いです。
Rubyスクリプトではないですが他の言語にも適用可能な仕組みとして一読しておくと良いと思います。phpenvもrbenvと同じ仕組みなはず。実体はシェルスクリプト群です。パスの通ったshimsのスクリプト経由でバージョンに応じたRuby関連の実行ファイルの呼び出しを動的に行うことでバージョン切り替えを行っています。日常的によく使うツールだと思うので、理解を深めていくとトラブルシューティングに役立つと思います。
一般的なアプリサーバの作り方を学べます。作りもacceptしたら都度Threadを作ってそのThreadがHTTPの処理を行って終了したらThreadを閉じる、というベーシックな構成なのでアプリサーバとしてはシンプルで読みやすいと思います。Rubyの場合はRackでアプリサーバとアプリケーションフレームワークを繋いでいるので、rackのwebrickハンドラ部分も読むと理解が深まります。コードを読んで、
webrickはそもそもマルチスレッドであって、Rails4のwebrickがシングルスレッドになっていたということに気づきました。
ActiveRecord::Baseなどにextendしていて、ActiveRecord本体のメソッドを呼び出したいときはdelegateメソッドで委譲しています。何気なく最初に呼び出している
delegate_all
の中身がわかるとテンションが上がります。
読み切るにはActionView周りのコードを読む必要があるのでその部分は難易度が高いですがViewのハンドラ自体は難しくないです。renderingの仕組みがわかると、Viewのハンドラーが何をしているのかがわかるのでビューを書くときに「jbuilderのjsonってどこから出てきた?キモいわー」とか思わずにスッキリ書けるようになります。ちなみにこれを読んで
YAMLでJSONを書けるハンドラや
jbuilerより速いかもしれないハンドラを作りました。
こちらもViewハンドラのgemでAPIなどのJSONを高速にレンダリングします。generatorやテストコードを除くと100行以下のコードなんですがやっていることはとても濃く、仕組みを理解するにはActionViewを読む必要があります。コレクションレンダリングのjson、html_safeを局所的にモンキーパッチすることでコレクションレンダリング時にjsonの配列をそのまま返しているところが面白いです。
非同期処理の定番gem。RedisをキューのDBとして使い、管理用・ワーカーのThreadを立てて実装しています。RedisにはJSONでシリアライズされたデータが入るので中身の仕様をわかっていると、デバッグが捗るかもしれません。Redis自体の勉強にもなります。
Sidekiqのアダプタ周りを読むと、JobWrapperというSidekiqの何でもワーカークラスに処理を投げているせいでSidekiqの性能を使い切れていない様子がわかりますw Redisに入るデータもJobWrapperに処理を委譲している分ネストしてます。なのでコードを読むとActiveJobを使いたくなくなります。
あらかじめRailsアプリケーションを読み込ませたプロセスで実行するから速い、と言われていて頭では理解していたもののじゃあどうやって実装しているんだろう?と気になったので読みました。とても面白かったのでおすすめ度★5です。UDSやUNIXソケットペアを駆使してRailsアプリをあらかじめ読み込ませたプロセスに対して処理を委譲する仕組みになっています。UDS経由で色々とやっているので仕組みがわかるとローカルのDocker開発環境での開発もspringを導入したりできるようになるので、かなり捗ると思います。
Rakeを継承した作りになっていて、ステージ名が動的にタスクとして定義されて実行されます。これによってステージのタスクで各変数を設定しています。
Rackミドルウェアです。特定のパスに対するリクエストをインタラプトしてOAuthのAuthorize URLへのリダイレクトをしたりコールバックURLの処理(stateの検証やトークンの発行とか)をして認証処理(正確にはWebアプリへの認証の前段の処理)を実現しています。
JSON SchemaやOpen APIの形式で書かれたドキュメントを読み込んで、そのドキュメントからリクエスト/レスポンスのバリデーションを行うRackミドルウェアです。実装とドキュメントの乖離を防ぐ意味で使おうと思いましたが、コードを読んでいくとOpen APIのリクエスト/レスポンスバリデータとして使うのは難しいかなーという印象を受けました(そもそもJSON Hyper Schema用に作られたっぽいのですが)。といっても利用できるかどうかは設計次第、プロジェクト次第だと思います。rspecでレスポンスのバリデータとして使うのは良さそうです。
Railsの拡張機能を作る場合は、Rails::Railtieを継承したクラスを実装して拡張機能の初期化処理を実装したりするんですが、そこらへんの仕組みがわかります。initializerの順番はtsortを使っており、トポロジカルソートの勉強にもなります。Railtieも含めて、Railsアプリを起動した時に読み込まれる順番を理解しておくとデバッグが捗るかもしれません。
最後に
難しいと思っても局所的にであれば意外と読めてしまうものだったりしますし、チリツモで全部読めちゃったみたいなこともあります。なので最初は
binding.pry
で地道に読んで理解して、理解できないものは翌日に回す、みたいな進め方が良いと思います。翌日になったらスラスラ読めるとか結構あります。
また、このブログでもコードリーディングはいくつか記事化していて、これからも主に自分が思い出すための用途でgemの仕組みを記事化していくつもりなので、コードを読んでも理解不能な部分があれば参考いただければと思います。