DRYなコードを書くために気をつけることがつまった章。あとちょっとコードペタペタ貼りすぎて見通悪いと思ったので文章メインです。
ソリューション:モジュールに分けよう
- 共通メソッド:そのままモジュールに書けば良い。
- validationなど:
ActiveSupport::Concern
をextendしてincluded
ブロックで定義すれば共有できる。
一部のロジックを共通化するためにtemplateパターンを一つの参考にすると良い。include先で実装してもらいたいメソッドをモジュール自体の定義で例外を投げるようにしておくと、エラーにすぐ気付ける。Javaのインターフェースのようなものだ。
例としてあげられる、Car,Byccycleクラスでincludeされることを想定されたDrivableモジュール。
module Drivable
extend ActiveSupport::Concern
class TemplateError < RuntimeError; end
included
validates :direction, :presence => true validates :speed, :presence => true
end
def turn(new_direction)
self.direction = new_direction
end
def brake
self.speed = 0
end
def accelerate
self.speed = [speed + acceleration, top_speed].min
end
def top_speed
raise TemplateError
end
def acceleration
raise TemplateError
end
end
よくある話としてはこれらの共通ロジックを親クラスに実装して継承するというものだが、モジュールの方が柔軟性がある(たとえばいろんなロジックを別々のモジュールに分けることによって一部のロジックだけを他のクラスにMix-inしたり、なんてことが考えられる)と書かれている。確かにそうかもしれない。
結論:共通ロジックをモジュール化できないか常に考える
ソリューション:プラグインを書こう
モジュールがアプリケーションレベルでの重複を防ぐ行為だとしたら、プラグインを書くことはコミュニティーレベルでの重複を防ぐ行為だと言えよう。有名なプラグインとしてはたとえば、Capistiranoなどがあるが、あそこまで巨大なものを想像しなくても良い。ほんのちょっと基底クラス(たとえば、ActiveRecord::Base
)に便利メソッドを足すだけでもそれは立派なプラグインだ。
pulginについての詳しい記述はRailsガイドに任せることにする。
結論:抽象性のためにpluginを書くことを考えよう。gemとして公開できるのならなおその方がよい。
メタプログラミングの魔法
たとえば、以下のようにstatusの候補が明確に配列として定義されていてそれに対してメソッドが動的に定義されているというのは良い例だろう。
class Purchase < ActiveRecord::Base
STATUSES = %w(in_progress submitted approved shipped received)
#~中略~
STATUSES.each do |status_name|
define_method "#{status_name}?"
status == status_name
end
end
end
結論:RubyのメタプログラミングはDRYのための非情な強力な力だ。頑張って使おう。
ただしDRY意味を勘違いしてはいけないよ、と最初に注釈がある。それはコードの行数を減らすという意味ではなく、意味の重複、振る舞いの分散を集約するという行為だということを忘れないように、という意味らしい。なんでもかんでもメタプログラミングでまとめてしまうという悪癖がたまに聞かれることがあるが、そのあたりはDRYの理解がまだ足りてないのかもしれない。