markdown 红宝石の条件分岐

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了markdown 红宝石の条件分岐相关的知识,希望对你有一定的参考价值。

## Rubyの条件分岐に関するオレオレまとめ

### 前提
* 分岐は少なければ少ないほうがよい
* とはいえ絶対に必要なのでどうすればよいか?
  * ネストを減らす
    * 分岐を仮に愚直にやっていくとn乗オーダーで処理が分かれていき,とてもじゃないが読めない
  * 統一感,対称性を持たせる
  * 適切なメソッド化をする
  * 分岐は処理の流れが変わるという重要な役割をもつため.分岐をするのであれば分岐をするという役割でメソッドを1つ切る(dispatcherが典型)
    * 深いネストやスパゲティを防止できる 

### if ... end

* 特定の場合のみ追加的な処理をしたい場合

```ruby
if File.exist?(path)
  File.delete(path)
end
#...
```

### 後置if

* 特定の__例外的な__条件を事前に除きたい場合(いわゆるガード節)

```ruby
class Node
  def parent
    return nil if self.root?
    
    # ルートノードのめんどくさいことは気にせず純粋に親ノードをとる処理に集中できる
  end
end
```

### if ... else ... end

* 明確に処理が2分割される場合
  * if の処理とelseの処理は対等な抽象度であり,相反するものが望ましい
  * 対等な抽象度になっていない場合,メソッド化やクラスを見直したほうがいい

```ruby
def index
  if smart_phone?
    render 'sp_index.html'
  else
    render 'pc_index.html'
  end
end
```

```ruby
# こういうのはふさわしくない
def index
  if smart_phone?
    return render 'sp_index.html'
  end
  render 'pc_index.html'
end

# 一方で,こういう場合は後置ifを使うほうがのぞましい
# (リダイレクトが例外的な処理である場合)
def index
  return redirect_to 'top_page' if smart_phone?
  
  render 'index.html'
end
```

### if ... elsif ... else ... end

* if ... else ... endの時と同様に対等であること
* 単純に数が多い場合などはcaseを検討してもよいが,複数の条件が絡み合っている場合,2つ以上の役割をもたせている場合がほとんどなので,メソッドやクラスを考えなおす
* 最後のelseに例外的な処理を書かないといけない場合,ガード節でシンプルになる場合がある

```ruby
def cheese_io(arg)
  if arg.is_a?(IO)
    arg
  elsif arg.is_a?(String)
    File.open(arg)
  else
    StringIO.new(arg)
  end
end
```

```ruby
# elseに例外
if xxx
  #xxx
elsif yyy
  #yyy
else
  raise ArgumentError
end

# 例外の条件を先に排出可能であれば
raise ArgumentError if zzz

if xxx
  #xxx
else
  #yyy
end
```


### if?, unless?

* if, unlessどっちを使うのか?
  * 基本的にはシンプルな方

```ruby
# return [] if !users.empty?
return [] unless users.empty?
```
  
* そもそも論理演算が複雑になっているものはその論理演算を一つのメソッドにするなどを検討する

```ruby
# つらい
if user.logged_in? && !user.admin?
  # ...
end

# 非特権ユーザの判定であることがすぐにわかる
class User
  def non_privilege_user?
    logged_in? && !admin?
  end
end

if user.non_privilege_user?
  # ...
end
```

* 反転した場合もシンプルさがかわらない場合,後続に続く処理にとって自然なほうを選ぶ
  * 下記の例の場合,siblingsにとって関心のあるのは自分がルートであるかどうかよりも,親がいるかどうかのほうが気になるのでBのほう
  
```ruby
class Node
  def parent
    # 親ノードをとってくる処理
  end
  
  def root?
    parent == nil
  end
  
  def has_parent?
    !root?
  end

  # A
  def siblings
    return [] if root?
    parent.children.delete_if {|sibling| sibling == self }
  end
  
  # B
  def siblings
    return [] unless has_parent?
    parent.children.delete_if {|sibling| sibling == self }
  end
end
```

### 三項演算子( ... ? ... : ... )

* ある条件に対し2つに値が別れ,その値を使用する場合.スイッチのイメージ
* 条件文,true, falseの場合の処理が十分にシンプルになっていない場合,メソッドやクラスを考える
* 成功したらオブジェクト,失敗したらnilのような場合のメソッドをtrue, falseに置き換える場合
  * ただしフラグ的になりがちなので気をつける

```ruby
# それぞれのクラスに同一メソッドを定義しておいてポリモーフィックにできるとイイ感じになる
travel_plan = has_money? ? ExpensiveTravelPlan.new : CheapTravelPlan.new
travel_plan.price
travel_plan.term
```

```ruby
# ?系メソッドを新たに生やす
def find?(element)
  find(element) ? true : false
end
```

### case ... when

* かなり柔軟に機能をもつので詳細は http://docs.ruby-lang.org/ja/2.2.0/doc/spec=2fcontrol.html#case 参照
* if文となどとの大きな違いとして,__whenで指定したものを左辺,caseに指定したものを右辺__とし===演算子で比較される点
  * ===演算子はクラスによって定義がことなり,==と同じものもあれば全然違うものもある
  * 一般的には==はオブジェクトの等価性を評価するために使い,===は範囲をもたせたものに使うことがが多いので,多くの場合===のほうがtrueとなる範囲が広いことが多いが,全ては定義次第なので必ずしもそうとは限らない
  * 多くのオブジェクトでは==はequal,===はincludeやcoverのイメージで使われていることが多い
  
```ruby
# Regex
# http://docs.ruby-lang.org/ja/2.2.0/method/Regexp/i/=3d=3d.html
# http://docs.ruby-lang.org/ja/2.2.0/method/Regexp/i/=3d=3d=3d.html
/^http/ == "http" #=> false
/^http/ === "http" #=> true

def protocol(url)
  case str
  when /^https/ then "HTTPS"
  when /^http/ then "HTTP"
  when /^ftp/ then "FTP"
  end
end
protocol("https://google.com") #=> "HTTPS"
protocol("http://example.com") #=> "HTTP"
protocol("unknown") #=> nil

# Range
# http://docs.ruby-lang.org/ja/2.2.0/class/Range.html
(1..10) == 0 #=> false
(1..10) == 1 #=> false
(1..10) == 2 #=> false

(1..10) === 0 #=> false
(1..10) === 1 #=> true
(1..10) === 5 #=> true

def rank(score)
  case score
  when (80...100) then "A"
  when (70...80)  then "B"
  when (60...70)  then "C"
  when (0...60)   then "D"
  end
end

rank(90) #=> "A"
rank(70) #=> "B"
rank(55) #=> "D"
```

### その他
直接的には条件分岐ではないが || などもあったりする

## 条件分岐の粒度

### (例)
あるゲームを考える.ゲームには100点満点のscoreがある.80点以上ならAランク, 70点以上80点未満ならBランク, 60点以上70点未満ならCランク,60未満ならDランクであり,A,Bランクの場合次のステージはボーナスステージになる.それ以外のC, D の場合通常のステージになる.

この時,スコアを受け取り,次のステージ情報を返すメソッドnext_stageを作成する
(ボーナスステージ,通常のステージを表すクラスはBonusStage, NormalStageで予め定義済みとする)

愚直に書くとこんな感じ.
```ruby
def next_stage(score)
  if score >= 80
    BonusStage.new
  elsif score >= 70
    BonusStage.new
  elsif score >= 60
    NormalStage.new
  else
    NormalStage.new
  end
end
```

あるいは,考慮外の数字などを考えたり,どうせ70点以上は全部BonusStageだからなどと考えたらこうなる
```ruby
def next_stage(score)
  if score >= 70
    BonusStage.new
  elsif score >= 0
    NormalStage.new
  else
    raise "out of range"
  end
end
```

さらにはcase文をつかったりetc...

### 分岐の考え方
* 何のための分岐なのか?
  * 違う意味の分岐を混ぜない

### 与えられた問題の分解
* 想定外の値は?
* 実は仕様はいくつかある
  * ランクを決定すること
  * ランクによって次のステージが変わること

上記の愚直に書いた例の場合ランクを決定することと,ランクによって次のステージが変わることが混ざってしまっている.さらに入力が想定外の範囲だった場合のバリデーションエラーも同一の分岐に混ざってしまっている.
どこが主題なのかがわかりづらくなってしまい,コードからランクの情報が欠落してしまっている

```ruby
# ゲームロジックのための分岐
def grade(score)
  # バリデーションのif(ここはガード節早期リターン or 例外)
  raise 'out of range' unless (0..100).include?
  
  case score
  when 80..100; 'A'
  when 70...80; 'B'
  when 60...70; 'C'
  when  0...60; 'D'
  end
end

# フローのための分岐
def dispatch_stage(grade)
  case grade
  when 'A', 'B'
    BonusStage.new
  when 'C', 'D'
    NormalStage.new
  end
end

def next_stage(score)
  dispatch_stage(grade(score))
end
```
    

以上是关于markdown 红宝石の条件分岐的主要内容,如果未能解决你的问题,请参考以下文章

markdown 红宝石オブジェクトに加载ActiveModelの机能を追加する

markdown ActiveRecord的地方の句の条件を动的にしたい

ruby 红宝石の例外处理の基本

html 即のバージョンによる条件分岐文

ruby 导轨のテスト用宝石リスト(デフォルトでインストールされているものを除く)

markdown Linux的下安装红宝石