Rubyっぽくリファクタリングするためのポイント(3)
久々にネタを拾ったのでメモ...φ(・ω・`)
今回はちょっと仕様が複雑です
しかも、あまりRubyと直接関係ない部分もありますが、
Rubyだと簡潔に書ける例ということで
<今回の仕様>
- 各データには値A、フラグB、フラグCが存在する
- データの状態に合わせて実行されるメソッド、method_[a..f]がある
- 値Aに対して範囲の境界値x, y, z (x < y < z)が存在する
- 以下の条件に従って、メソッドにデータを渡して処理させる(-は条件を指定しないの意)
No. | A | B | C | method |
---|---|---|---|---|
1 | - | true | false | method_a |
2 | x <= A < y | false | false | method_b |
3 | y <= A < z | false | false | method_c |
4 | A >= z | false | false | method_d |
5 | - | - | true | method_e |
- 条件に当てはまらないデータはmethod_fを実行する
まず、これを単純に並べてみますと・・・
if data.B && !data.C method_a(data) elsif data.A >= x && data.A < y && !data.B && !data.C method_b(data) elsif data.A >= y && data.A < z && !data.B && !data.C method_c(data) elsif data.A >= z && !data.B && !data.C method_d(data) elsif data.C method_e(data) else method_f(data) end
・・・こうなりますが、まさかこんな風に書く人はいませんよね?
(正直、この時点でコードが合ってるのかとても疑問ですが・・・(´・ω・`))
さすがにこれは冗長なので、条件を整理しましょう
フラグB/Cに着目すると・・・
if data.C method_e(data) else if data.B method_a(data) else if data.A >= x && data.A < y method_b(data) elsif data.A >= y && data.A < z method_c(data) elsif data.A >= z method_d(data) else method_f(data) end end end
・・・こうなりますね
これも間違ってはいないのですが、値の順序に着目すると・・・
if data.C method_e(data) else if data.B method_a(data) else if data.A >= z method_d(data) elsif data.A >= y method_c(data) elsif data.A >= x method_b(data) else method_f(data) end end end
・・・この辺が限界ですか
少なくとも、Javaならこう書くでしょう
(このコードも合ってるかいまいちわからん・・・(´・ω・`))
さて、ここまでの話はあまりRubyと関係がなく、
情報処理の基本的な話でしかありません
ここからが「Rubyっぽいコード」です
そもそも、Rubyで条件判定したい場合、
if〜elsif〜endや三項演算子なんかがメジャーですが、
意外と忘れられているのがcase〜when〜end文です
普通だと・・・
case val when 'a' # do something when 'b' # do something else # do something end
・・・こんな感じで、値の比較に使います
Javaだとここの条件には数値しか使えませんが、
Rubyは「===」で比較ができれば何でも対象にできます
(後述のおまけ参照)
このとき、case句に値を与えないと、各when句の文の真偽で値が決まります
つまり・・・
case # 何も与えない when false puts "ここは実行されない" when true puts "ここが実行される" else puts "ここも無視" end # => ここが実行される
・・・こう書けます
ということで、先ほどのぐちゃっとしたコードを書きなおしますと・・・
case when data.C method_e(data) when data.B method_a(data) when data.A >= z method_d(data) when data.A >= y method_c(data) when data.A >= x method_b(data) else method_f(data) end
・・・素晴らしい(`・ω・´) b
case文は「最初に条件を満たした文」を実行するので、
こういった流れのコードが簡潔に書けるわけです
ということで、Rubyでelsifが多様されるようなら、
case文を使えないか考えてみてはいかがでしょう
<おまけ1>
Rubyにおいて「真偽」とは、「nilとfalseが偽で、他は全部真」ですので、
例えば・・・
case when nil puts "when1" when 'a' puts "when2" end # => when2
・・・こういうのもありです
<おまけ2>
case文の応用で、例えば
「数値が来たらそれを2倍するが、数値でなければそのまま返す」
なんてのが一文で書けます
def case_test(val) case val when Numeric # Numericを継承or実装したクラスのオブジェクト val * 2 else val end end p case_test(2) #=> 4 p case_test("test") #=> "test"
つまり、比較対象にクラスを渡すと、
そのクラスに属するかを比較するわけです
これを応用しますと・・・
case target when Player # 対象がプレイヤーなら回復 heal_to(target, atk) when Monster # 対象がモンスターならダメージ damage_to(target, atk) end
・・・こんなコードが書けまして、
実際に「ROgue(仮)」で使ってます