Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails: Value Objectを検討してみよう(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

Rails: Value Objectを検討してみよう(翻訳)

ふと気がつくと、アプリケーションで同じようなコンセプトのビューヘルパーをいくつも作ってしまっていることがよくあります(複雑な計算メソッドや、1つ以上の値をいくつものメソッドに渡すなど)。

こんなときは、Active Recordモデルより小規模なシンプルなオブジェクトでその正体を明らかにしてみましょう。

今回の場合は、機能をリファクタリングして、Martin Fowlerが提唱するValue Object」にすることを検討します。

(Value Objectとは)通貨や日付のrangeのようなシンプルで小さなオブジェクトであり、(オブジェクトの)同一性に頼らずに等価性をチェックする。
martinfowler.comより

次のように書くのではなく

普通にヘルパーを書く。

def css_color(r, g, b)
  "##{r.to_s(16)}#{g.to_s(16)}#{b.to_s(16)}"
end

次のように書く

アプリケーションで使われているコンセプトを正しく捉える「Value Object」に切り出す。

class RGBColor
  def initialize(red, green, blue)
    @red = [[0, red].max], 255].min
    @green = [[0, green].max], 255].min
    @blue = [[0, blue].max], 255].min
  end

  def to_hex
    [@red, @green, @blue].each_with_object("") do |part, to_hex|
      to_hex << part.to_s(16)
    end
  end
end

そして以下をヘルパーに書く

def css_color(r, g, b)
  "##{RGBColor.new(r, g, b).to_hex}"
end

そうする理由

アプリケーションで使われているコンセプトがデータベースに保存されないからといって、それをアプリケーションのオブジェクトにすべきということにはなりませんそれがオブジェクトになってはならない理由にはなりません。

追記(2020/12/02): ご指摘をいただいて上記誤訳を修正いたしました。ありがとうございます🙇。

リファクタリングしてValue Objectにすればコンセプトを表現するコードを分離でき、さまざまなメリットが得られます。「関心の分離(separation of concerns)」という言葉をご存じの方もいることでしょう。このコンセプトの振る舞いのテストも分離されるので、網羅的かつ効率のよいテストを書けるようになります。

コードの整理が進むことで理解もはかどります。このコードとコンセプトは再利用しやすく、オブジェクトの機能を拡張するための格好の場所が手に入ります。

そうしない理由があるとすれば

Value Objectの導入は時期尚早の場合も考えられます。コンセプトをValue Objectに切り出すタイミングを見い出すのは簡単ではありません。Value Objectの導入が早すぎると、コードが無駄に複雑になって混乱を招く可能性もあります。逆に導入が遅れれば、ぐちゃぐちゃのコードを書くはめになります。

上の例で書いたようなクラス定義による方法以外に、Struct、場合によってはOpenStructを使う手もありますが、これらのオブジェクトはミュータブルであり、プロパティを変更できてしまいますので、コードがシンプルになるどころか余計複雑になることもあります。

関連記事

肥大化したActiveRecordモデルをリファクタリングする7つの方法(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。