Ruby
デザインパターン
Rails5

TECH::CAMP Advent Calendar 2017 16日目を飾りますt-kusakabeです。
皆さんは普段コードを書くときにどういうことを意識していますでしょうか。
各種設計を取り入れる、デザパタ、オブジェクト指向、あるいはFWや言語の作法に従うなどいろいろあるかと思います。
また、プログラミングを始めたての人はあまり責務を意識することなくコード書いている人もいるのではないでしょうか。
今回はRoRを書く上での作法(責務とか)を改めて見直してみようかと思います。
だらだら書いても纏まりがつかないので大きく3つのことについて取り上げようと思います。

※個人的にどうかと思っているだけで、諸説あると思いますのでその辺りは大目に見ていただけますと幸いです。

Agenda

  • ビジネスロジックについて
  • Service層について
  • SRPについて

結構雑に扱われているビジネスロジック

以下のような実装を目にすることが多々あります。

model/user.rb
class User
  def full_name
    "#{last_name} #{first_name}"
  end
end
views/users/index.html.erb
<%= @user.full_name %>

RoRではcontrollerにはロジックは置かず、modelに置きましょうといった記事がよく見受けられます。
そのためかviewからmodelで定義したメソッドを叩いてしまっている実装を目にすることがあります。
実際はcontrollerとかで使っていたのを誰かが便利メソッドと勘違いしviewから呼び出したのかもしれませんが、どっちにしろよくないことだと思います。

MVCが破綻している

上記のコードのよくないところはMVCが破綻してしまっていることにあります。
MVCのフローを簡単に纏めると
リクエスト -> controller -> model -> controller -> view -> レスポンス
が王道だと思うのでひとまずこのフローに従うのが1つの解と思われます。
またRoRは正確にはMVCではないと言われています。
その詳細は以下の記事を見ていただく方がよいかと思われます。
Webアプリケーション開発者から見た、MVCとMVP、そしてMVVMの違い

どうするのが最適なのか考えてみる

では、どうすればよいのでしょうか。
ひとまずviewからmodelにを見に行かなくてすむ方法を考えてみます。

helperを使う

RoRにはhelperというものが用意されています。
form_for とか link_to とかですね。
このhelperは自分で定義することもできます。

helpers/user_helper.rb
module UserHelper
  def full_name(user)
    "#{user.last_name} #{user.first_name}"
  end
end
views/users/index.html.erb
<%= full_name(@user) %>

期待する動きは同じですが、viewからmodelを呼び出すことはなくなりました。
ただhelperには1つ問題があります。
それは全てのhelperメソッドをどこからでも呼べてしまう点です。

デザパタを使う

デザインパターンの1つにDecoratorパターンというものがあります。
Decoratorパターン
RoRでDecoratorパターンを用いるのにDraperやActiveDecoratorなどが有名です。

今回はActiveDecoratorを使って見ます。

decocators/user_decorator.rb
module UserDecorator
  def full_name
    "#{last_name} #{first_name}"
  end
end
views/users/index.html.erb
<%= user.full_name %>

decoratorの便利なところはコンテキストを持っていないと呼び出すことができないところです。
例えば以下の場合は full_name メソッドは呼び出すことが出来ません

views/admin/index.html.erb
<%= @admin.full_name %>

helperメソッドはどこからでも呼べてしまうため意図していない使われ方をされることがあったりします。
(userで使うはずだったhelperメソッドをadminで使用しているなど)
これを許可してしまうと問題が発生したり拡張したくなったときなどに非常に不便です。。。(IDEを使えばいいなどはありますが)

Service層に関して

結構意見が別れるService層に関してです。
ビジネスロジックを定義したい、が複数のmodelに依存する、またはどのmodelにも依存しないものをがあったときにService層を使いたくなる時があると思います。
極論ですがよっぽどの理由がない限りService層は導入しない方がいいと考えています。

なぜRoRにService層を導入すべきでないか

  • メソッドの置き場所に困ったときにService層に置きがちになる
  • 意味を持たないmodule群ができてしまう
  • 他に代替できるものがある

上記の理由からRoRにService層は導入しない方が良いと考えます。

汎用的なメソッドを定義するときにどこに定義すればいいのか迷う時があります。(userでもadminでも使えるメソッドを定義したいなど)
そういう時に一旦Service層に定義されてしまうことが多いと考えます。

また名前をつけるのも困難です。
ユーザーに関する物をService層に置きたい時に UserService などとしてしまうと何をするところなのかわかりにくくなります。
(Service自体の意味が汎用性が強すぎるためなんでもServiceにできてしまう気がする。。。)

またRubyにはMix-inというものがあり、RoRにはMix-in用にconcernsというものが用意されています。
わざわざService層を使わずとも汎用性の高いものを作りたいときはMix-inを使うことで解決すると考えています。

SRP

プログラマがコーディングする時に最も気をつけていることの1つに単一責務の法則(SRP)があげられると思います。
オブジェクト指向をあまり意識してこなかった人がclassを設計すると結構大変なことになっていたりすることがあります。。。

とにかく依存

よくなにか依存した実装を目にすることがあります。
- 特定のデータがDBに無いといけない
- 外部サービスを利用していてkeyを持っていないといけない

そのために色々な問題が引き起こされます。
- ドキュメントが無いと開発ができない
- 新しく開発者を加えた時に混乱する
- 毎回データを用意するのが冗長

keyを持ってないといけなかったりするのは非常に厄介ですね。。。

これらの解決方法は以下の記事がわかりやすいかと思います。
プログラマが知るべき97のこと/単一責任原則

またkeyに関しては config_for などが有効ではないでしょうか。


いかがでしたでしょうか。
デザインパターンを用いるだけでもいろいろ解決できることが多いと思います。
また、責務を意識することで開発効率が上がるだけでなく、読みやすく拡張しやすいコードになります。
ただ形にするだけでなく中身にも拘りが持てるようになると素敵ですね。