- Published on
RubyのリファクタリングでNull Objectを使ってコードをスッキリさせる方法
- Authors
- ジャバ・ザ・ハットリ
前回、Ruby のリファクタリングでイケてないコードを美しいオブジェクト指向設計のコードへ改良するための方法という記事を書いて、いい反響をいただいたので第2弾を書いた。
Ben Orenstein 氏の講演で話されていた前回のとはまた別のリファクタリング方法。元ネタはこちら。
Refactoring from good to great
【リファクタリング前のコード】
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: An Agile Primer (Addison-Wesley Professional Ruby) |
作者: Sandi Metz |
出版社/メーカー: Addison-Wesley Professional |
発売日: 2012/09/14 |
メディア: ペーパーバック |
オブジェクト指向に沿ったイケてるコードを書きたい全てのエンジニアにとって「買っても絶対に損は無い」と断言できるオススメの良書。詳しくはこちらの記事に書いた。
英語とプログラミングを同時に勉強するなら「Practical Object-Oriented Design in Ruby」の一択