前回、Rubyのリファクタリングでイケてないコードを美しいオブジェクト指向設計のコードへ改良するための方法という記事を書いて、いい反響をいただいたので第2弾を書いた。
Ben Orenstein氏の講演で話されていた前回のとはまた別のリファクタリング方法。元ネタはこちら。
github.com
【リファクタリング前のコード】
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クラスに定義がまとめられているので「そこさえ変更すれば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
- メディア: ペーパーバック
- この商品を含むブログを見る
tango-ruby.hatenablog.com