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