elsifで条件分岐すると辛くなると言う話
例えば、マラソン大会を行なって1位になったら金メダル、2位は銀メダル、3位は銅メダル、それより下は参加賞を渡したい時、if文をベタ書きしたら以下のようになります。【ケース①とします】
def get_award(ranking)
if ranking == 1
"金メダル"
elsif ranking == 2
"銀メダル"
elsif ranking == 3
"銅メダル"
else
"参加賞"
end
end
get_award(1) #=> "金メダル"
get_award(2) #=> "銀メダル"
get_award(3) #=> "銅メダル"
get_award(4) #=> "参加賞"
これだけでもelsifが重なって少々見づらいですね。
さらに、ここから4位〜8位の人は参加賞じゃなくて入賞に変えるとします。【ケース②とします】
def get_award(ranking)
if ranking == 1
"金メダル"
elsif ranking == 2
"銀メダル"
elsif ranking == 3
"銅メダル"
elsif ranking >= 4 && ranking <= 8
"入賞"
else
"参加賞"
end
end
get_award(1) #=> "金メダル"
get_award(2) #=> "銀メダル"
get_award(3) #=> "銅メダル"
get_award(4) #=> "入賞"
get_award(9) #=> "参加賞"
前回のコードにelsifが追加されてより見づらくなってしまいました。もちろん、動作的には何の問題もありません。
ただ、条件が変わるたびにelsifを増やすと言うのは後々の保守を考えたらなるべく避けた方が良いでしょう。
見づらいし、変更もしづらいのでバグが発生する可能性が高まります。
リファクタリングするよ(ケース①)
リファクタリングのポイントとしては、ロジックとデータを分けることです。
まずは、ケース①からリファクタリングします。
AWARDS = [
{ ranking: 1, award: "金メダル" },
{ ranking: 2, award: "銀メダル" },
{ ranking: 3, award: "銅メダル" }
]
def get_award(ranking)
award_data = AWARDS.find{|hash| hash[:ranking] == ranking }
if !award_data.nil?
award_data[:award]
else
"参加賞"
end
end
こんな感じでデータとロジックを分割します。データは処理の中で動的に変化することのない値です。
今回だと、1位は金メダル、2位は銀メダル、3位は銅メダルというのはあらかじめ決まっている値なのでデータとして定数AWARDSに入れました。
また、if文の部分がかなりスッキリしたので三項演算子を使うことができますね。
AWARDS = [
{ ranking: 1, award: "金メダル" },
{ ranking: 2, award: "銀メダル" },
{ ranking: 3, award: "銅メダル" }
]
def get_award(ranking)
award_data = AWARDS.find{|hash| hash[:ranking] == ranking }
!award_data.nil? ? award_data[:award] : "参加賞"
end
get_award(1) #=> "金メダル"
get_award(2) #=> "銀メダル"
get_award(3) #=> "銅メダル"
get_award(4) #=> "参加賞"
どうでしょうか、最初のコードと比べて非常にスッキリして見やすくなったと思いませんか。
データとロジックを分けることでスッキリして保守しやすいコードが書けるようになりました。
リファクタリングするよ(ケース②)
次にケース②のリファクタリングを行いますが、ケース①で直した続きからやります。
AWARDS = [
{ ranking: [1], award: "金メダル" },
{ ranking: [2], award: "銀メダル" },
{ ranking: [3], award: "銅メダル" },
{ ranking: [4, 5, 6, 7, 8], award: "入賞" }
]
def get_award(ranking)
award_data = AWARDS.find{|hash| hash[:ranking].include?(ranking) }
!award_data.nil? ? award_data[:award] : "参加賞"
end
get_award(1) #=> "金メダル"
get_award(2) #=> "銀メダル"
get_award(3) #=> "銅メダル"
get_award(4) #=> "入賞"
get_award(9) #=> "参加賞"
変わったところは、
- AWARDS内のrankingの値を配列にした
- get_award関数のfindメソッドの中でinclude?を使った
の2点です。
データとロジックを分けることで、データに変化があったとしてもロジック部分が冗長になることはなくなりました。
長期的な視点だと保守しやすいコードを書いた方が、開発コストが下がるので綺麗でスッキリしたコードを書いていきましょう〜