ド・モルガンの法則でunlessのややこしい条件をifに読み替えよう


# はじめに

条件分岐はプログラミングの基本です。
しかし、複雑な条件分岐が出てくると非常にコードが読みにくくなります。

さらに、その複雑な条件が unless と組み合わされていたりすると、ぱっと理解するのが非常に困難になります。
そこで、この記事では複雑な unless の条件を攻略する方法を説明します。

# 質問: "unless person.married? && !person.rich?" が真になるケースは？

## ifとunless

たとえば以下のコードは「personが結婚していたら'Yo!'と声をかける」コードです。

````ruby
puts 'Yo!' if person.married?
````

if文(if式)はほぼ全てのプログラミング言語に存在すると思いますが、それに加えてRubyでは false であれば真になる unless も使えます。

たとえば以下のコードは「personがお金持ちじゃなかったら'Yo!'と声をかける」コードです。

````ruby
puts 'Yo!' unless person.rich?
````

## ifで複数の条件を組み合わせると・・・？

条件分岐ではand（`&&`）やor（`||`）を使って、複数の条件を組み合わせることができます。

では、このコードはどういう場合に'Yo!'するでしょうか？

````ruby
puts 'Yo!' if person.married? && !person.rich?
````

・・・はい、プログラミングの経験がある方ならわかりますよね。
「personが結婚していて、なおかつお金持ちでない場合」です。
つまり、married?がtrueで、rich?がfalseになる場合です。

## unlessで複数の条件を組み合わせると・・・わからん！！

それでは、このコードはどうでしょう？

````ruby
puts 'Yo!' unless person.married? && !person.rich?
````

真になる条件がぱっとわかりますか？
'Yo!'するケースを次の中から選んでください。

1. married?もrich?もtrue
1. married?がtrue、rich?がfalse
1. married?がfalse、rich?がtrue
1. married?もrich?もfalse

この条件を日本語で表現しようとしても「personが結婚していて、なおかつお金持ちでない場合でない場合・・・？？？」みたいになってしまって表現しづらいです。

## ド・モルガンの法則を使って unless を if に変換する

こういう場合は[ド・モルガンの法則](http://ja.wikipedia.org/wiki/%E3%83%89%E3%83%BB%E3%83%A2%E3%83%AB%E3%82%AC%E3%83%B3%E3%81%AE%E6%B3%95%E5%89%87)を使って、if文に変換するとわかりやすくなります。

`unless person.married? && !person.rich?` は
`if !(person.married? && !person.rich?)` となり、以下のように表現されます。

![Screen Shot 2014-08-27 at 7.41.05.png](https://qiita-image-store.s3.amazonaws.com/0/7465/a6997fca-0306-b115-3b9b-d8a98cd0281f.png "Screen Shot 2014-08-27 at 7.41.05.png")
※「∩」は「and」、上の横棒は「not」の意味です。

これをド・モルガンの法則を使って書き換えると次のようになります。

![Screen Shot 2014-08-27 at 7.42.47.png](https://qiita-image-store.s3.amazonaws.com/0/7465/05bc083b-437d-4905-993c-10d6dffebb3b.png "Screen Shot 2014-08-27 at 7.42.47.png")
※「∪」は「or」の意味です。


つまり、先ほどのコードは次のようなコードに書き換えることができます。

````ruby
# puts 'Yo!' unless person.married? && !person.rich?
puts 'Yo!' if !person.married? || person.rich?
````

日本語で書くなら「personが結婚していない場合、もしくはお金持ちである場合」に'Yo!'することになります。

もう一度先ほどの選択肢を見てみましょう。

1. married?もrich?もtrue
1. married?がtrue、rich?がfalse
1. married?がfalse、rich?がtrue
1. married?もrich?もfalse

「personが結婚していない場合」に該当するのは選択肢3, 4です。
「personがお金持ちである場合」に該当するのは選択肢1, 3です。
つまり、「3または4」または「1または3」となり、ひとまとめにすると **正解は「1または3または4」** となります。

これは言い換えると、「2以外ならば真」とも言えます。
「2（married?がtrue、rich?がfalse）以外ならば真」と考えたときは、以下のようなコードがイメージに近いかもしれません。

```ruby
puts 'Yo!' if !(person.married? && !person.rich?)
````

# unlessからifへ置き換える8パターン

`unless a && b` を `if !a || !b` のように置き換える場合、組み合わせは8パターンあります。
それぞれのケースを一覧化すると次のようになります。

![Screen Shot 2014-08-27 at 8.18.30.png](https://qiita-image-store.s3.amazonaws.com/0/7465/1ac1cad1-021d-bab3-8794-b784a7e1dd72.png "Screen Shot 2014-08-27 at 8.18.30.png")


また、参考までに条件が真になる 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パターンを総当たりで実行し、条件が真になるケースを出力します。

## ソースコード

````ruby
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
````

## 出力結果

````ruby
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?` で終わるのではなく、条件をメソッド化してわかりやすい名前を付けておくと、より「脳に優しいコード」になります。

たとえばこんな感じです。

````ruby
class Person
  # ...
  def yo_target?
    !self.married? || self.rich?
  end
end
````

````ruby
puts 'Yo!' if person.yo_target?
````

良いコードや良い設計に対する理解を深めるためには、こういった技術書で勉強するのが有効です。

<div class="amazlet-box" style="margin-bottom:0px;"><div class="amazlet-image" style="float:left;margin:0px 12px 1px 0px;"><a href="http://www.amazon.co.jp/exec/obidos/ASIN/489100455X/junic05-22/ref=nosim/" name="amazletlink" target="_blank"><img src="http://ecx.images-amazon.com/images/I/51E1DYKHW7L._SL160_.jpg" alt="CODE COMPLETE 第2版 上" style="border: none;" /></a></div><div class="amazlet-info" style="line-height:120%; margin-bottom: 10px"><div class="amazlet-name" style="margin-bottom:10px;line-height:120%"><a href="http://www.amazon.co.jp/exec/obidos/ASIN/489100455X/junic05-22/ref=nosim/" name="amazletlink" target="_blank">CODE COMPLETE 第2版 上</a><div class="amazlet-powered-date" style="font-size:80%;margin-top:5px;line-height:120%">posted with <a href="http://www.amazlet.com/" title="amazlet" target="_blank">amazlet</a> at 14.07.11</div></div><div class="amazlet-detail">スティーブ マコネル <br />日経BP社 <br />売り上げランキング: 22,218<br /></div><div class="amazlet-sub-info" style="float: left;"><div class="amazlet-link" style="margin-top: 5px"><a href="http://www.amazon.co.jp/exec/obidos/ASIN/489100455X/junic05-22/ref=nosim/" name="amazletlink" target="_blank">Amazon.co.jpで詳細を見る</a></div></div></div><div class="amazlet-footer" style="clear: left"></div></div>

<div class="amazlet-box" style="margin-bottom:0px;"><div class="amazlet-image" style="float:left;margin:0px 12px 1px 0px;"><a href="http://www.amazon.co.jp/exec/obidos/ASIN/4891004568/junic05-22/ref=nosim/" name="amazletlink" target="_blank"><img src="http://ecx.images-amazon.com/images/I/510NHJ2MX1L._SL160_.jpg" alt="CODE COMPLETE 第2版 下" style="border: none;" /></a></div><div class="amazlet-info" style="line-height:120%; margin-bottom: 10px"><div class="amazlet-name" style="margin-bottom:10px;line-height:120%"><a href="http://www.amazon.co.jp/exec/obidos/ASIN/4891004568/junic05-22/ref=nosim/" name="amazletlink" target="_blank">CODE COMPLETE 第2版 下</a><div class="amazlet-powered-date" style="font-size:80%;margin-top:5px;line-height:120%">posted with <a href="http://www.amazlet.com/" title="amazlet" target="_blank">amazlet</a> at 14.07.11</div></div><div class="amazlet-detail">スティーブ マコネル <br />日経BP社 <br />売り上げランキング: 42,181<br /></div><div class="amazlet-sub-info" style="float: left;"><div class="amazlet-link" style="margin-top: 5px"><a href="http://www.amazon.co.jp/exec/obidos/ASIN/4891004568/junic05-22/ref=nosim/" name="amazletlink" target="_blank">Amazon.co.jpで詳細を見る</a></div></div></div><div class="amazlet-footer" style="clear: left"></div></div>


<div class="amazlet-box" style="margin-bottom:0px;"><div class="amazlet-image" style="float:left;margin:0px 12px 1px 0px;"><a href="http://www.amazon.co.jp/exec/obidos/ASIN/4873115655/junic05-22/ref=nosim/" name="amazletlink" target="_blank"><img src="http://ecx.images-amazon.com/images/I/51MgH8Jmr3L._SL160_.jpg" alt="リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)" style="border: none;" /></a></div><div class="amazlet-info" style="line-height:120%; margin-bottom: 10px"><div class="amazlet-name" style="margin-bottom:10px;line-height:120%"><a href="http://www.amazon.co.jp/exec/obidos/ASIN/4873115655/junic05-22/ref=nosim/" name="amazletlink" target="_blank">リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)</a><div class="amazlet-powered-date" style="font-size:80%;margin-top:5px;line-height:120%">posted with <a href="http://www.amazlet.com/" title="amazlet" target="_blank">amazlet</a> at 14.07.11</div></div><div class="amazlet-detail">Dustin Boswell Trevor Foucher <br />オライリージャパン <br />売り上げランキング: 1,568<br /></div><div class="amazlet-sub-info" style="float: left;"><div class="amazlet-link" style="margin-top: 5px"><a href="http://www.amazon.co.jp/exec/obidos/ASIN/4873115655/junic05-22/ref=nosim/" name="amazletlink" target="_blank">Amazon.co.jpで詳細を見る</a></div></div></div><div class="amazlet-footer" style="clear: left"></div></div>