Tbpgr Blog

Employee Experience Engineer tbpgr(てぃーびー) のブログ

Ruby | Delivering Result | Call back instead of returning

概要

Call back instead of returning

前提

Confident Rubyではメソッド内の処理を次のように分類しています。
・Collecting Inputs(引数チェック、変換など)
・Performing Work(主処理)
・Delivering Output(戻り値に関わる処理)
・Handling Failure(例外処理)

当記事は上記のうち、Delivering Outputに関する話です。

詳細

状況

クライアントコードは、メソッドがシステムに変化を与えたかどうかで
アクションを切り替える必要があるかもしれない。

概要

戻り値よりも完了を条件付きのblockへyieldする

理由

完了時のコールバックはtrue・falseを返却するより意味深い。

サンプルコード仕様

アンパンマンのガチャガチャを扱います。
欲しいキャラクター名を指定し、該当するキャラクターが取得出来た時のみ
自由に処理を切り替えられるようにしたいです。

サンプル1:欲しいおもちゃだったかどうかtrue,falseで返却
サンプル1:欲しいおもちゃだった場合にブロックを実行できるようにする

補足

true, falseでの返却はCommand/Query Separationの原則(コマンド・クエリー分離原則)に反する場合があります。
つまりクラスの状態を変化させつつ、その結果を返却するということです。

コマンド=状態(値)を変化させる
クエリ=状態(値)を返却する

を守ることで、利用者の驚きを最小化する設計になります。

サンプルコード(適用前)

class AnpanmanCapsuleToy
  ANPANMAN = "アンパンマン"
  BAIKINMAN = "バイキンマン"
  DOKIN = "ドキンちゃん"
  CAPSULES = [ANPANMAN, BAIKINMAN, DOKIN, DOKIN]
  def choise_capsule(want_toy)
    toy = choise_random_toy
    CAPSULES.delete toy
    if toy == want_toy
      true
    else
      false
    end
  end

  private
    def choise_random_toy
      case rand(3)
      when 1
        ANPANMAN
      when 2
        BAIKINMAN
      else
        DOKIN
      end
    end
end

ap = AnpanmanCapsuleToy.new
ret = ap.choise_capsule AnpanmanCapsuleToy::ANPANMAN
if ret
  puts "欲しかった#{AnpanmanCapsuleToy::ANPANMAN}を手に入れた"
else
  puts "#{AnpanmanCapsuleToy::ANPANMAN}が手に入らなかったのでもう一回チャレンジ"
  ap.choise_capsule AnpanmanCapsuleToy::ANPANMAN
end

出力1

欲しかったアンパンマンを手に入れた

出力2

アンパンマンが手に入らなかったのでもう一回チャレンジ

サンプルコード(適用後)

class AnpanmanCapsuleToy
  ANPANMAN = "アンパンマン"
  BAIKINMAN = "バイキンマン"
  DOKIN = "ドキンちゃん"
  CAPSULES = [ANPANMAN, BAIKINMAN, DOKIN, DOKIN]
  def choise_capsule(want_toy, choise_toy_callback = -> (x){}, not_choise_toy_callback = ->(x){})
    toy = choise_random_toy
    CAPSULES.delete toy
    if toy == want_toy
      choise_toy_callback.call(want_toy)
    else
      not_choise_toy_callback.call(want_toy)
    end
  end

  private
    def choise_random_toy
      case rand(3)
      when 1
        ANPANMAN
      when 2
        BAIKINMAN
      else
        DOKIN
      end
    end
end

ap = AnpanmanCapsuleToy.new
ret = ap.choise_capsule AnpanmanCapsuleToy::ANPANMAN,
      -> (want_toy) { puts "欲しかった#{want_toy}を手に入れた" }, 
      -> (want_toy) {
        puts "#{AnpanmanCapsuleToy::ANPANMAN}が手に入らなかったのでもう一回チャレンジ"
        ap.choise_capsule AnpanmanCapsuleToy::ANPANMAN
      }

出力1

欲しかったアンパンマンを手に入れた

出力2

アンパンマンが手に入らなかったのでもう一回チャレンジ

参照

Martin FowlerのCommandQuerySeparation
http://martinfowler.com/bliki/CommandQuerySeparation.html