Best Practice Ruby on Rails Refactoringは、Rails開発を初めて半年のエンジニアにピッタリの本だった
- 作者:Chad Pytel,Tammer Saleh
- 出版社/メーカー: Addison-Wesley Professional
- 発売日: 2010/11/09
- メディア: Kindle版
Best Practice Ruby on Rails Refactoring
のモデル章を読ました。
本書は、Railsガイドの知識を読んだが実践でどうもいいコードにならないと思っているエンジニアにオススメできます。
学んだことを使える知識にしたいと思ったので言語化することにします。
デメテルの法則
デメテルの法則は、モジュール間の結合度を下げる手法で最小知識の原則とも呼ばれいます。基準となるオブジェクトは、隣接するオブジェクトの情報のみを知っているべきであるということです。Rubyの場合、オブジェクト間の通信において.
の数が一つだけになるようにすればいいです。
書籍では、以下のコードをリファクタすることでデメテルの法則が紹介されていました。
class Customer < ActiveRecord::Base has_many :invoices end class Invoice < ActiveRecord::Base belong_to :customer end
InvoiceクラスからCustomerクラスの情報を取得する場合、以下のようになります。
@invoice.customer.name
これをデメテルの法則に従うようにリファクタリングすると、@invoice.customer_name となります。
class Customer < ActiveRecord::Base has_many :invoices end class Invoice < ActiveRecord::Base belong_to :customer def customer_name customer.name end end
しかし、この方法には欠点があります。取得した属性値に対して線形的にメソッドが増加するためModelが肥大化してしまいます。
class Invoice < ActiveRecord::Base belong_to :customer def customer_name customer.name end def customer_street customer.street end def customer_city customer.city end end
この肥大化は、delegate
メソッドで解消できます。
class Invoice < ActiveRecord::Base belong_to :customer delegate :name, :street, :city, to: :customer, prefix: :customer end end
Fat Model
適切な責務が表現された状態で、Fat Modelになるのは致し方ありません。書籍では、分割統治法のアプローチとしてModuleに切り出すことでFat Modelに混在する役割を切り分けていました。 この手法はFat Modelを解決したことにはなりませんが、そもそもFat Modelであることは問題でないと思うので個人的に良い方法であると思います。
上記は、適切な責務を実現している場合に有効です。しかし、現実のプロダクトコードでは適切な責務が表現されていないことがあります。書籍では、この時の対処法が紹介されていました。新しいクラスに責務を委譲することです。
注文クラスであるOrderモデルが例として紹介されていました。
class Order < ActiveRecord::Base def self.find_purchased ... end def self.find_wating_for_review ... end def to_xml ... end def to_json ... end def to_csv ... end end
上記のコードの問題点は、単一責任の原則(SRP)に反することです。to_xml
やto_json
注文クラスは、どのような形式で出力するかに責務を持っています。以下のように、別クラスに切り出すことで責務を明らかにできます。
class Order < ActiveRecord::Base def self.find_purchased ... end def self.find_wating_for_review ... end def converter OrderConverter.new(self) end end class OrderConverter attr_reader :order def initialize(order) @order = order end def to_xml ... end def to_json ... end def to_csv ... end end
@order.converter.to_pdf
のように呼び出すことができました。
これは、デメテルの法則に違反しています。はじめに紹介した方法でどのようにリファクタできるか考えてみてください。答えは、書籍に記載されています。
- 作者:Chad Pytel,Tammer Saleh
- 出版社/メーカー: Addison-Wesley Professional
- 発売日: 2010/11/09
- メディア: Kindle版
Association extensions
class PetsController < ApplicationController def show @pet = Pet.find(parames[:id]) @toys = @pet.find_cute_for_pet end end class Pet < ActiveRecord::Base has_many :toys def find_cute_for_pet self.where(cute: true) end end
上記のようなコードがある時、has_many
の中にメソッドを押し込むことでどのように利用するかを明らかにできます。また、メソッド名もシンプルになります。
class Pet < ActiveRecord::Base has_many :toys do def cute self.where(cute: true) end end end
toys
に対して複数のメソッドが定義されるようになったとき、Association extensionsが有効です。
class Pet < ActiveRecord::Base has_many :toys, -> { extending ToyAssocationMethods } end module ToyAssocationMethods def cute where(cute: true) end end
まとめ
Railsのモデルに関するアンチパターンを紹介しました。
しかし、安易にリファクタリングに取り掛かってはいけません。
リファクタリングを行うことで、本当に生産性が上がるのかROI(投資利益率)を意識してチームやプロダクトオーナに提案する必要があると思います。
他に興味のあるトピックのみを読んでブログにしたい。
- Testing
- Using Third-Party Code
- Databases