はじめに
条件分岐はプログラミングの基本です。
しかし、複雑な条件分岐が出てくると非常にコードが読みにくくなります。
さらに、その複雑な条件が unless と組み合わされていたりすると、ぱっと理解するのが非常に困難になります。
そこで、この記事では複雑な unless の条件を攻略する方法を説明します。
質問: "unless person.married? && !person.rich?" が真になるケースは?
ifとunless
たとえば以下のコードは「personが結婚していたら'Yo!'と声をかける」コードです。
puts 'Yo!' if person.married?
if文(if式)はほぼ全てのプログラミング言語に存在すると思いますが、それに加えてRubyでは false であれば真になる unless も使えます。
たとえば以下のコードは「personがお金持ちじゃなかったら'Yo!'と声をかける」コードです。
puts 'Yo!' unless person.rich?
ifで複数の条件を組み合わせると・・・?
条件分岐ではand(&&
)やor(||
)を使って、複数の条件を組み合わせることができます。
では、このコードはどういう場合に'Yo!'するでしょうか?
puts 'Yo!' if person.married? && !person.rich?
・・・はい、プログラミングの経験がある方ならわかりますよね。
「personが結婚していて、なおかつお金持ちでない場合」です。
つまり、married?がtrueで、rich?がfalseになる場合です。
unlessで複数の条件を組み合わせると・・・わからん!!
それでは、このコードはどうでしょう?
puts 'Yo!' unless person.married? && !person.rich?
真になる条件がぱっとわかりますか?
'Yo!'するケースを次の中から選んでください。
- married?もrich?もtrue
- married?がtrue、rich?がfalse
- married?がfalse、rich?がtrue
- married?もrich?もfalse
この条件を日本語で表現しようとしても「personが結婚していて、なおかつお金持ちでない場合でない場合・・・???」みたいになってしまって表現しづらいです。
ド・モルガンの法則を使って unless を if に変換する
こういう場合はド・モルガンの法則を使って、if文に変換するとわかりやすくなります。
unless person.married? && !person.rich?
は
if !(person.married? && !person.rich?)
となり、以下のように表現されます。
これをド・モルガンの法則を使って書き換えると次のようになります。
つまり、先ほどのコードは次のようなコードに書き換えることができます。
# puts 'Yo!' unless person.married? && !person.rich?
puts 'Yo!' if !person.married? || person.rich?
日本語で書くなら「personが結婚していない場合、もしくはお金持ちである場合」に'Yo!'することになります。
もう一度先ほどの選択肢を見てみましょう。
- married?もrich?もtrue
- married?がtrue、rich?がfalse
- married?がfalse、rich?がtrue
- married?もrich?もfalse
「personが結婚していない場合」に該当するのは選択肢3, 4です。
「personがお金持ちである場合」に該当するのは選択肢1, 3です。
つまり、「3または4」または「1または3」となり、ひとまとめにすると 正解は「1または3または4」 となります。
これは言い換えると、「2以外ならば真」とも言えます。
「2(married?がtrue、rich?がfalse)以外ならば真」と考えたときは、以下のようなコードがイメージに近いかもしれません。
puts 'Yo!' if !(person.married? && !person.rich?)
unlessからifへ置き換える8パターン
unless a && b
を if !a || !b
のように置き換える場合、組み合わせは8パターンあります。
それぞれのケースを一覧化すると次のようになります。
また、参考までに条件が真になる a と b の組み合わせも書いておきます。
unless + and(&&)
unless a && b <=> if !a || !b
- 真になるのは a が false または b が false の場合
- 言い換えると、偽になるのは a が true かつ b が true の場合のみ
unless !a && b <=> if a || !b
- 真になるのは a が true または b が false の場合
- 言い換えると、偽になるのは a が false かつ b が true の場合のみ
unless a && !b <=> if !a || b
- 真になるのは a が false または b が true の場合
- 言い換えると、偽になるのは a が true かつ b が false の場合のみ
unless !a && !b <=> if a || b
- 真になるのは a が true または b が true の場合
- 言い換えると、偽になるのは a が false かつ b が false の場合のみ
unless + or(||)
unless a || b <=> if !a && !b
- 真になるのは a が false かつ b が false の場合のみ
unless !a || b <=> if a && !b
- 真になるのは a が true かつ b が false の場合のみ
unless a || !b <=> if !a && b
- 真になるのは a が false かつ b が true の場合のみ
unless !a || !b <=> if a && b
- 真になるのは a が true かつ b が true の場合のみ
Personに'Yo!'するケースを総当たりで確認してみる
最後に、冒頭で紹介したPersonに'Yo!'するプログラムをRubyで書いてみます。
このプログラムではmarried?とrich?がtrue/falseになる組み合わせと、上で挙げたunlessの8パターンを総当たりで実行し、条件が真になるケースを出力します。
ソースコード
class Person
attr_reader :married, :rich
alias_method :married?, :married
alias_method :rich?, :rich
def initialize(married, rich)
@married, @rich = married, rich
end
def to_s
"@married=#{@married}, @rich=#{@rich}"
end
end
people = [true, false].product([true, false]).map{|married, rich| Person.new(married, rich) }
conditions = [
"person.married? && person.rich?",
"!person.married? && person.rich?",
"person.married? && !person.rich?",
"!person.married? && !person.rich?",
"person.married? || person.rich?",
"!person.married? || person.rich?",
"person.married? || !person.rich?",
"!person.married? || !person.rich?"
]
conditions.each do |condition|
puts "puts 'Yo!' unless #{condition}"
people.each do |person|
puts "Yo! (#{person})" unless eval(condition)
end
puts
end
出力結果
puts 'Yo!' unless person.married? && person.rich?
Yo! (@married=true, @rich=false)
Yo! (@married=false, @rich=true)
Yo! (@married=false, @rich=false)
puts 'Yo!' unless !person.married? && person.rich?
Yo! (@married=true, @rich=true)
Yo! (@married=true, @rich=false)
Yo! (@married=false, @rich=false)
puts 'Yo!' unless person.married? && !person.rich?
Yo! (@married=true, @rich=true)
Yo! (@married=false, @rich=true)
Yo! (@married=false, @rich=false)
puts 'Yo!' unless !person.married? && !person.rich?
Yo! (@married=true, @rich=true)
Yo! (@married=true, @rich=false)
Yo! (@married=false, @rich=true)
puts 'Yo!' unless person.married? || person.rich?
Yo! (@married=false, @rich=false)
puts 'Yo!' unless !person.married? || person.rich?
Yo! (@married=true, @rich=false)
puts 'Yo!' unless person.married? || !person.rich?
Yo! (@married=false, @rich=true)
puts 'Yo!' unless !person.married? || !person.rich?
Yo! (@married=true, @rich=true)
まとめ: とはいえ脳に優しいコードが一番!unlessの使用は必要最小限に
というわけで今回は unless で複数の条件を組み合わせた場合にどうなるかをいろいろなパターンで検証してみました。
ですが、我々プログラマはそもそも「わかりやすいコード」「脳に優しいコード」を書くことを目標にすべきです。
この記事は積極的に unless を使うことを推奨するために書いたわけではありません。
そうではなく、この記事は他の人が書いたコードにこういう条件分岐が出てきた場合に、どう読み砕けば良いのかを説明するために書きました。
個人的には unless を使う場合は、unless person.married?
や unless person.rich?
など、ごく単純なケースにとどめておいた方が良いと考えています。
もし自分が unless person.married? && !person.rich?
みたいな条件を書こうとした場合は、一歩立ち止まって if !person.married? || person.rich?
のようなif文に置き換えることを検討してみましょう。
おまけ: 本当は条件をメソッド化してわかりやすい名前を付けるのがベスト
さらに言えば、if !person.married? || person.rich?
で終わるのではなく、条件をメソッド化してわかりやすい名前を付けておくと、より「脳に優しいコード」になります。
たとえばこんな感じです。
class Person
# ...
def yo_target?
!self.married? || self.rich?
end
end
puts 'Yo!' if person.yo_target?
良いコードや良い設計に対する理解を深めるためには、こういった技術書で勉強するのが有効です。
日経BP社
売り上げランキング: 22,218
日経BP社
売り上げランキング: 42,181
オライリージャパン
売り上げランキング: 1,568