6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Mix-inはやめてくれ。「関数的モジュール」だ!

6
Posted at

TL;DR

  • Mix-inは危険な機能だ
  • Mix-inは必要なシーンにだけ使おう
  • 関数だけを提供するだけのモジュールを作るのが、弊害が少なくシンプル

Mix-in は弊害が多い

Mix-inはRubyの基本的な機能であり、入門書でも序盤で紹介されます。

それゆえか単に処理をクラス間で共有するためだけにMix-inを使おうとしているのをしばしば見かけます。

実際には、Mix-inにはうかつに使うと後々痛い目に会う機能であり、大抵のケースではMix-inを使うのは適切ではありません。

落とし穴1: インスタンス変数やメソッドが上書きされる

includeがやっているのは、「単にMix-inのメソッドをクラスにコピーする」ことだけです1。

なので同じ名前のメソッドをクラス側で定義すると、Mix-inのメソッドを警告なしに上書きしてしまいます。同様にMix-inのインスタンス変数を、クラス側で参照したり変更したりできてしまいます。

module HttpRequestable
  def get(uri)
    request(:get, uri)
  end

  private

  def request(http_method, uri)
    # HTTPリクエストする処理
  end
end

class StoreAPI
  include HttpRequestable

  ...

  # 商品の入荷をリクエストする機能を追加しよう!
  # (実はHttpRequestableのメソッドを上書きしてしまっている)
  def request(product_id, count)
  end
end

落とし穴2: メソッドの関係が複雑になる

クラスからMix-inのメソッドを呼び出せるだけでなく、Mix-inからクラスのメソッドを呼び出したり、Mix-inから別のMix-inのメソッドを呼び出すことができるため、

クラスのメソッド => Mix-in Aのメソッド => Mix-in Bのメソッド => クラスのメソッド => ...

のような複雑な参照関係が生じてしまうことがあります。

呼び出しが適切に管理されていればTemplate Method パターンと呼べるのですが、

おっ、親クラスの〇〇っていうメソッドを呼べば動かせるぞ。よし!!

と、深く考えずに呼んでしまうことが、稀によくあります。

落とし穴3: メソッドの定義場所が分かりにくい

最近はIDEが良くなったので問題になりにくいのですが、includeするモジュールが増えてくると、メソッドの定義箇所がわかりにくくなりがちです。

RailsのActionMailer::Baseには12個のモジュールがincludeされているのですが、この中からrelative_url_rootの定義箇所をIDEなしで見つけるのは一苦労です。

落とし穴4: 確信が持てない

最大の問題は、上記のような落とし穴を踏んでいないか確信を持てないことです。Rubyは動的言語なので、静的なチェックが難しく、

これは private メソッドだから、変更しても安全だな!!

と思っていたら、実はMix-inで誤って呼び出していないとも限らないのです。

そこで「関数的モジュール」

(以下の「関数的モジュール」は私の造語です。もっと適切な用語があるのかもしれませんが、調べ方が悪いのか見つからなかったので、ここでは「関数的モジュール」と呼びます)

関数的モジュールとは、特異メソッドだけを提供するモジュールのことです。

module HttpRequestable
  class << self
    def get(uri)
      request(:get, uri)
    end

    private

    def request(http_method, uri)
      # HTTPリクエストする処理
    end
  end
end

使うときは、includeせずメソッドを直接呼び出します。

class StoreApi
  # メソッド名が被っても大丈夫!!
  def request 
    HttpRequestable.get(uri)
  end
end

関数的モジュールでは、Mix-inのような問題は起きません。

  • インスタンス変数を使わない(使えない2)
  • メソッドがコピーされることが無い
  • クラス側のメソッドを呼び出すことがない(できない)
  • どのモジュールのメソッドを呼んでいるかは明らか

それでもMix-in を使うケース

そうはいっても、Mix-inを定義するのが便利なケースもあります。

Mix-inを定義する際にはTemplate Method パターンとして正しく定義することを心がけましょう。

具体的には・・・

  • Mix-inがクラスのどのメソッドを呼ぶか明らかにする
  • クラスからはMix-inのどのメソッドを呼んでよいのか明らかにする
  • インスタンス変数を避ける
  • privateメソッドを避ける(RubyのprivateはC++やJavaのprivateと異なることに気をつける)
  1. 掘り下げるともっと複雑でしょうが、基本的な理解としては、「メソッドをコピーするだけ」。 ↩

  2. インスタンス変数を使うこともできるが、Mix-inとは振る舞いが異なるので、Mix-inでのような問題は起きません。 ↩

6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?