アトラシエの開発ブログ

株式会社アトラシエのブログです

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を負債化させないベストプラクティスについて考えてみます。

負債化の臭い

巨大である 危険度★★☆

この中で私が個人的に最も忌避すべきと思うのがDeviseです。DeviseはRailsにサインアップ・ログイン機能を簡単に実装してくれるエンジンです。 Deviseは次のようなケースで非常に面倒です。

  • ログインフォームの表示やサインアップ時に追加で処理を走らせたい時、controllerのカスタマイズが難しい
  • Userモデルの定義を少しカスタマイズしたい。例えば「(メールもしくはユーザ名)とパスワードが必須」など。
  • 複数のログイン処理をもたせたい。UserのログインとAdministratorのログイン。
  • Userのログインでも、@user.user_typeによって処理を分岐させたい。

要するに少し混み合ったものを作ろうとするだけでとたんに面倒になります。Deviseのコードを読んで内容を理解しながらメソッドをオーバーライドしないといけないようになると最悪です。 大きい機能を持っている、ビジネスロジックに深く関わるGemの使用は避けましょう。

スターが少ない 危険度★☆☆

githubのスターが少ないと開発が止まるリスクがあります。マニアックなgemの使用は慎みましょう。例えばci-reporterとか止まってますね。

コア部分を拡張している 危険度★★★

例えばactive-record-xxxのような名前の、ActiveRecordに機能を拡張するようなgemです。

上のGemfileだと

  • simple_form
  • friendly_id

あたりが該当します。

かつてSqueelというgemがあり、

github.com

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標準のテストライブラリの良さが再評価されています。

togetter.com

もっといえばフロントエンドの関係で最近はReact・Vue.jsなどでテンプレートを描画することも多くなっています。このときにslimで書かれたコードにReactのコンポーネントを混在させたり、Rails側のviewのコードの一部をReactのコンポーネントに移そうと思った時、ピュアhtmlでないslimは見通しが悪くなります。

私はerb => haml => slimと使ってきましたが、最近は再びerbを使い、emmetなどを使ってエディタ側でタイピングの煩わしさを解消するようにしてます。

asset系のgem 危険度★★☆

bootstrap-railsのような、css・JavaScriptを提供するgemです。 rails5.1からはnpmが使えるようになるそうです。rubyのライブラリにはgemを、フロントエンドライブラリはnpmを使うという住み分けになるでしょう。

標準ライブラリでできることをgemでやる 危険度★☆☆

たとえばこういうの。

github.com

普通にNet::HTTPでできます。

負債gemをなくすには?

gem使わないでできないか考えよう

ちょっとしたことにgemを使うのは慎むべきでしょう。少し前ですが、JavaScript側のgem的な存在であるnpmで、left-padというパッケージが削除され混乱がありました。

postd.cc

依存関係は、その名の通り、コードが動くために必要になってしまうものです。より多くの依存関係があればあるほど、より多くの欠陥が生まれ得ます。エラーが生じる可能性が増えることは言うまでもありません。あなたが日々用いている依存パッケージに関して、その関数を書いたプログラマについて精査したことがありますか?

ここの内容に私も同意です。

定期的にアップデートする

gemは

$ bundle update

でアップデートできます。このような取り組みもあります。

qiita.com

どこに使っているか、gemにコメントをつけていく

使っているのか使ってないのかすぐわからないのでとりあえず残しているというケースもよくあります。そのgemが使われているかはgithubでソースコードを確認し、クラス名などでgrepすればわかるのですが、gemfileのすべてのgemにそれをやるにはけっこうな時間が必要です。

開発者用のドキュメントを作成し、Gemfileの中のgemがどういう用途でどこで使われているか、メモしておくのは有効です。

f:id:tkot:20161218025847p:plain

弊社では上のようなドキュメントをmarkdownで作成し、RAILS_ROOT/docs/gems.md として保存しています。

目先の実装コストよりも中長期の視点を。