rails Gem入れすぎ問題について。負債化するGemとは
こんにちは。tkotです。今回はrailsのgemについて。
オススメのgemについて紹介する記事は頻繁に取り上げられるのですが、その反対に使うと弊害が出てしまうgemや、gemを多用することのデメリットについて解説されているものはなかなか無いように思います。 今回やや古くなったRailsアプリケーションの保守を行った際に感じた問題点について紹介したいと思います。
サンプルとなる実際のGemfile
Rails3.2.xx, ruby2.0.0-pxxxで動作するアプリケーションです。これを古いと感じるか新しいと感じるかは人によると思いますが、よくあるRails4アップデート前のGemfile構成ではないでしょうか。
gem 'haml-rails' gem 'enumerize' gem 'devise' gem 'mysql2' gem 'bcrypt-ruby', '~> 3.0.0' gem 'omniauth' gem 'omniauth-facebook' gem 'omniauth-twitter' gem 'omniauth-mixi' gem 'omniauth-yahoojp' gem 'omniauth-google-oauth2' gem 'simple_form' gem 'kaminari' gem 'execjs' gem 'therubyracer' gem 'attr_encrypted' gem 'comma' gem 'rmagick' gem 'carrierwave' gem 'moji' gem 'net-sftp' gem 'whenever', require: false gem 'active_decorator' gem 'friendly_id' gem 'legato' gem 'wicked_pdf' gem 'wkhtmltopdf-binary' gem 'exception_notification' gem 'exception_notification-rake' gem 'letter_opener' gem 'dalli' gem 'double-bag-ftps' gem 'holiday_japan' gem 'multi_db' gem 'ruby-duration' gem 'gravtastic' gem 'turnout' gem 'roadie' gem 'twitter' group :assets do gem 'sass-rails', '~> 3.2.3' gem 'coffee-rails', '~> 3.2.1' gem 'uglifier', '>= 1.0.3' gem 'compass-rails' gem 'underscore-rails' gem 'turbo-sprockets-rails3' gem 'font-awesome-rails', tag: 'v3.2.1.3' end group :test do gem 'capybara', '2.0.3' gem 'capybara-email', '2.0.3' gem 'factory_girl_rails' gem 'guard' gem 'guard-spring' gem 'guard-rspec' gem 'spring', require: false gem 'simplecov', require: false gem 'simplecov-rcov', require: false gem 'ci_reporter' gem 'database_cleaner' gem 'timecop' gem 'rack_session_access' gem 'mimemagic' gem 'rspec-rails' gem 'launchy' gem 'poltergeist' gem 'rspec-console' gem 'fuubar' end group :development do gem 'rails-erd' gem 'brakeman', require: false gem 'rails_best_practices', require: false gem 'erb2haml' gem 'thin' gem 'better_errors' gem 'binding_of_caller' gem 'capistrano', '~> 2.15' gem 'capistrano_colors' gem 'quiet_assets' gem 'rack-mini-profiler' gem 'rails-footnotes' gem 'letter_opener_web' gem 'shout-bot' end group :test, :development do gem 'pry-rails' gem 'jasmine' gem 'jasmine-headless-webkit' end group :darwin do gem 'rb-fsevent' end gem 'jquery-rails'
情報がない状況でこれを保守していくのは相当大変です。そこで上をサンプルにどのようなGemが負債化しやすいか, Gemを負債化させないベストプラクティスについて考えてみます。
よくわからない1つのgem、そのgemのソースコードを自分で書いた時の倍くらいの負債を生むと思うの。
— hito_asa (@hito_asa) 2014年3月14日
負債化の臭い
巨大である 危険度★★☆
この中で私が個人的に最も忌避すべきと思うのがDeviseです。DeviseはRailsにサインアップ・ログイン機能を簡単に実装してくれるエンジンです。 Deviseは次のようなケースで非常に面倒です。
- ログインフォームの表示やサインアップ時に追加で処理を走らせたい時、controllerのカスタマイズが難しい
- Userモデルの定義を少しカスタマイズしたい。例えば「(メールもしくはユーザ名)とパスワードが必須」など。
- 複数のログイン処理をもたせたい。UserのログインとAdministratorのログイン。
- Userのログインでも、
@user.user_type
によって処理を分岐させたい。
要するに少し混み合ったものを作ろうとするだけでとたんに面倒になります。Deviseのコードを読んで内容を理解しながらメソッドをオーバーライドしないといけないようになると最悪です。 大きい機能を持っている、ビジネスロジックに深く関わるGemの使用は避けましょう。
deviseとrails_adminは悪魔の囁き。ちょっとだけなら大丈夫さ!と思って手を出すと、アプリの色んな所が侵されて止められなくなるんだ。ダメ絶対!あなたは人間辞めますか、それともレールズ辞めますか?という気分がここ数日支配的
— joker1007 (@joker1007) July 4, 2013
スターが少ない 危険度★☆☆
githubのスターが少ないと開発が止まるリスクがあります。マニアックなgemの使用は慎みましょう。例えばci-reporter
とか止まってますね。
コア部分を拡張している 危険度★★★
例えばactive-record-xxx
のような名前の、ActiveRecordに機能を拡張するようなgemです。
上のGemfileだと
- simple_form
- friendly_id
あたりが該当します。
かつてSqueelというgemがあり、
Article.where('created_at >= ?', 2.weeks.ago)
と書くところを
Article.where { created_at >= 2.weeks.ago }
こんな感じにActiveRecordの検索を直感的に書けるgemがありました。 最初この機能で作られたコードを読んだ時、「ActiveRecordのメソッドにブロック渡せたのか。知らなかったな。」と思ったんですが、コア部分を拡張して上のような機能を持たせています。 確かにArelなどで無理やりに書かれたクエリよりもわかりやすいのですが、コア部分の拡張はなるべく避けなければいけないものですし、squeelはrails5への対応の前後に開発が止まったようです。したがってsqueelで書いたコードをすべてネイティブのコードに直さないとrails5にあげられなくなっているかと思います。
標準を変更する 危険度★☆☆
上でいうと
- haml-rails
- rspec
あたりです。これは賛否両論だと思いますが、確かにこのコードが書かれた2013年ごろはhaml・rspecの支持派が強かったと思います。 しかし今ではslimのほうが人気ですし、rspecも以下のような議論がありrails標準のテストライブラリの良さが再評価されています。
もっといえばフロントエンドの関係で最近はReact・Vue.jsなどでテンプレートを描画することも多くなっています。このときにslimで書かれたコードにReactのコンポーネントを混在させたり、Rails側のviewのコードの一部をReactのコンポーネントに移そうと思った時、ピュアhtmlでないslimは見通しが悪くなります。
普段JSX書いてるとSlimやHamlよりERBのほうが一貫性あるし良いという気持ちになってくる
— ホームページビルダー (@r7kamura) 2016年7月12日
私はerb => haml => slimと使ってきましたが、最近は再びerbを使い、emmetなどを使ってエディタ側でタイピングの煩わしさを解消するようにしてます。
asset系のgem 危険度★★☆
bootstrap-rails
のような、css・JavaScriptを提供するgemです。
rails5.1からはnpmが使えるようになるそうです。rubyのライブラリにはgemを、フロントエンドライブラリはnpmを使うという住み分けになるでしょう。
標準ライブラリでできることをgemでやる 危険度★☆☆
たとえばこういうの。
普通にNet::HTTP
でできます。
負債gemをなくすには?
gem使わないでできないか考えよう
ちょっとしたことにgemを使うのは慎むべきでしょう。少し前ですが、JavaScript側のgem的な存在であるnpmで、left-pad
というパッケージが削除され混乱がありました。
依存関係は、その名の通り、コードが動くために必要になってしまうものです。より多くの依存関係があればあるほど、より多くの欠陥が生まれ得ます。エラーが生じる可能性が増えることは言うまでもありません。あなたが日々用いている依存パッケージに関して、その関数を書いたプログラマについて精査したことがありますか?
ここの内容に私も同意です。
依存gemを減らしたい。減らす努力をしていかないとアプリケーションを永く保守することはできない
— null (@yuroyoro) 2016年4月1日
定期的にアップデートする
gemは
$ bundle update
でアップデートできます。このような取り組みもあります。
どこに使っているか、gemにコメントをつけていく
使っているのか使ってないのかすぐわからないのでとりあえず残しているというケースもよくあります。そのgemが使われているかはgithubでソースコードを確認し、クラス名などでgrepすればわかるのですが、gemfileのすべてのgemにそれをやるにはけっこうな時間が必要です。
開発者用のドキュメントを作成し、Gemfileの中のgemがどういう用途でどこで使われているか、メモしておくのは有効です。
例
弊社では上のようなドキュメントをmarkdownで作成し、RAILS_ROOT/docs/gems.md
として保存しています。
目先の実装コストよりも中長期の視点を。
Railsアプリを作る時、個人的に絶対避ける3大gemはdevise、activeadmin、ransack。目先のラクさと将来遭遇する面倒事のバランスがとれてない。
— Junya Ogu®a (@junya) 2015年1月23日