LoginSignup
39
30

More than 5 years have passed since last update.

RubyのリファクタリングでNull Objectを使ってコードをスッキリさせる方法

Posted at

RubyのリファクタリングではNull Objectを使うと、コードがスッキリして可読性が上がって使い勝手が良くなりますよ、という話。元ネタはこちら。
Refactoring from good to great: Ben Orenstein

【リファクタリング前のコード】


class JobSite
  attr_reader :contact

  def initialize(location, contact)
    @location = location
    @contact = contact
  end

  def contact_name
    if contact
      contact.name
    else
      'no name'
    end
  end

  def contact_phone
    if contact
      contact.phone
    else
      'no phone'
    end
  end

  def email_contact(email_body)
    if contact
      contact.deliver_personalized_email(email_body)
    end
  end
end

class Contact < OpenStruct
  def deliver_personalized_email(email)
    email.deliver(name)
  end
end

 

これはどんなプロジェクトでもよく散見される種類のコードだと思う。インスタンスを初期化して作成した後、それぞれの要素があるかどうか判断して、ある場合はAを返す。無い場合はBを返す、というパターン。
コードで言うとここ。

  def contact_name
    if contact
      contact.name
    else
      'no name'
    end
  end

contact_nameではif contactとしてcontactが入っていたらnameを返し、もし無ければ'no name'としている。同じようにcontact_phone、email_contactがあって、そのメソッドの中身はif文で分岐されている。

Railsなんかでもよくあるのがcuurent_userの有り無しによって分けるパターン。
例えばこういうの。

if current_user
  AAA
else
  BBB
end

 
で、そこは「聞くな、言え」の法則に反している。
毎回contactがあるかどうかを「聞いて」から処理をするのではなく「ただ言う」だけにした方がいいですよ、と。

その改良方法がNull Objectになる。まずは初期化のところを変更する。
【変更後】

  def initialize(location, contact)
    @location = location
    @contact = contact || NullContact.new
  end

initializeにNull Objectを追加した。名前はNullContact。したがってもし引数のcontactが入ってなければ@contactにはNullContactのインスタンスが入ることになる。

そのNullContactの定義がこれ。
【変更後】

class NullContact
  def name
    'no name'
  end

  def phone
    'no phone'
  end

  def deliver_personalized_email(email)
  end
end

つまり「もしcontactが無ければ返していたモノが全部入っているクラス」になる。

後はリファクタリング前のコードには何回も入っていたif文を全て取り除く。

【リファクタリング後の全体像】


class JobSite
  attr_reader :contact

  def initialize(location, contact)
    @location = location
    @contact = contact || NullContact.new
  end

  def contact_name
    contact.name
  end

  def contact_phone
    contact.phone
  end

  def email_contact(email_body)
    contact.deliver_personalized_email(email_body)
  end
end

class NullContact
  def name
    'no name'
  end

  def phone
    'no phone'
  end

  def deliver_personalized_email(email)
  end
end

class Contact < OpenStruct
  def deliver_personalized_email(email)
    email.deliver(name)
  end
end

 
すごいシンプルで読みやすい。全10行もあったif文の分岐が全て無くなり、ただ「言う」だけで処理が完結している。
さらに変更の容易さも上がっている。リファクタリング前のコードではcontactが無い場合の処理が分散していた。例えば「contactが無い場合の表示がno nameとか愛想が無さすぎだから、もうちょっとマシなのに変えようかな」となったとする。するとそれぞれのif文のelseの箇所を探して、そこだけを変更していかなければならなかった。

ところがリファクタリング後のコードはNullContactクラスに定義がまとめられているので「NullContactを変更だけすればOk」となっている。

単一責任の小さなクラスを実装、というオブジェクト指向デザインの基本そのままになっている。

ということで「RubyのリファクタリングでNull Objectを使ってコードをスッキリさせる方法」でした。
 
 
実はここに書いたリファクタリングの解説とオブジェクト指向デザインの内容はほとんど「Practical Object-Oriented Design in Ruby」の受け売り。詳しい本の内容はこちらに書いた。
英語とプログラミングを同時に勉強するなら「Practical Object-Oriented Design in Ruby」の一択

エンジニアの皆様へ

ほとんどのエンジニアには解けるが、下位10%のダメなエンジニアにだけ解けないパズル?」なるものをシリーズ化して8パズル作成した。もしご興味あれば解いてみてください。
http://tango-ruby.hatenablog.com/entry/2015/11/30/122814

39
30
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
39
30