チェリー本第7章の改札機の例題に機能追加をするモブプロを開催した

昨年Rubyの勉強に入りたての頃、FBC卒業生のふーがさんが主催するりんどく.rbという輪読会に参加していました。

自分が初めて参加した時、ちょうどチェリー本の第7章の例題である改札機のプログラムにSuicaを使う場合の機能追加のモブプロをやっていました。

fuga-ch85.hatenablog.com

1年経って、FBC内で開催されているチェリー本輪読会に改めて参加をしているのですが同様にチェリー本題7章を読んでいる段階です。

改札機の例題を終えたタイミングで1週間お休みの期間があったため、ふーがさんがやられていた機能追加のモブプロを改めて自分で開催してみることにしました。

計3日かけて開催したため、ふーがさんのブログとリポジトリを参考にどのように進めたかをまとめておきたいと思います。

Day1

初日はいきなりコードを書くことはせず、Suicaとは何か の認識を参加メンバー同士で合わせるところから行いました。

HackMDというドキュメントツールを使ってみんなでSuicaの詳細を書き出していきました。

例えば以下のように箇条書きで書いていきました。

  • お金をチャージできる
  • チャージしたお金で電車に乗れる
  • チャージした金額が足りないと改札から出られない
  • Suicaにお金が入っていないと改札に入れない
  • Suicaを使って買い物ができる

書き出した後は、まずは参加者同士で共通している部分を抽出していきました。

その上で今回の機能追加で実現したい ゴール は何かを話し合いました。

今回は以下をゴールに設定しました。

  • Suicaで乗車と降車ができること

そのため、みんなで書き出した「Suicaとは」の中でどの要素が実装に必要かを考えて、要件に入れる入れないを決めていきました。

最終的に実装の要件に置いたものは以下になります。

  • チャージできる
    • 手動チャージ◎
  • 電車に乗れる
    • 0円だと改札に入れない◎
  • 乗車時に残高が減る
    • どのタイミングで減る?
      • 降車時

最後にTDDで実装をするために考えられるシナリオをまとめました。

合計8シナリオをテストしながら実装する形になりました。

シナリオ

シナリオ1(1区間)

  • Suicaの残高が1000円
  • 梅田で入場し、十三で出場する
  • 期待する結果:出場できる
  • 期待する結果2:残高が840円になる

シナリオ2(1区間・残高不足)

  • Suicaの残高が100円
  • 梅田で入場し、十三で出場できない
  • 期待する結果:出場できない
  • 期待する結果2:残高が100円のまま

シナリオ3(1区間・チャージする・境界値1)

  • Suicaの残高が0円
  • 160円をチャージする
  • 梅田で入場し、十三で出場できる
  • 期待する結果:出場できる
  • 期待する結果2:残高が0円になる

シナリオ4(1区間・チャージする・境界値2)

  • Suicaの残高が0円
  • 159円をチャージする
  • 梅田で入場し、十三で出場できない
  • 期待する結果:出場できない
  • 期待する結果2:残高が159円のまま

シナリオ5(2区間)

  • Suicaの残高が190円
  • 梅田で入場し、三国で出場する
  • 期待する結果:出場できる
  • 期待する結果2:残高が0円になる

シナリオ6(1区間)

  • Suicaの残高が160円
  • 十三で入場し、三国で出場する
  • 期待する結果:出場できる
  • 期待する結果2:残高が0円になる

シナリオ7(残高0円)

  • 入場できない

シナリオ8(チャージ)

  • マイナスの金額はチャージ不可

Day2

二日目はついに機能追加の実装に入りました。

今回は私自身がモブプロ開催が初めてだったため、自分で進行役とドライバーを務め、参加者みんなで意見を出し合いながら実装をしていく形にしました。

TDDで進めることにしたため、一番初めのクラスやメソッドが定義されていない段階でテストがちゃんと落ちた瞬間にみんなで「よかった!」みたいに喜んだのがちょっと印象的でした。

まずはシナリオを実現するテストを書いて、テストが落ちたらどうやって実装するかをみんなでワイワイ会話しながら試行錯誤しました。

Day1に出したシナリオに沿ってDay2は以下の実装を行いました(大元のサンプルコードは省略)

# lib/suica.rb

class Suica
  attr_accessor :balance, :stamped_at

  def initialize(balance)
    @balance = balance
  end

  def stamp(name)
    @stamped_at = name
  end
end
# lib/gate.rb

def enter_by_suica(suica)
    suica.stamp(@name)
  end

  def exit_by_suica(suica)
    fare = calc_fare(suica)

    suica.balance -= fare
  end
# test/gate_test.rb

def test_umeda_to_juso_by_suica
    suica = Suica.new(1000)
    @umeda.enter_by_suica(suica)
    assert @juso.exit_by_suica(suica)
    assert_equal 840, suica.balance
  end

  def test_umeda_to_juso_by_suica_when_balance_is_100
    suica = Suica.new(100)
    @umeda.enter_by_suica(suica)
    refute @juso.exit_by_suica(suica)
    assert_equal 100, suica.balance

  end

Day2はテストシナリオ2の途中で終わりました。

Day3への宿題として、Suica残高から運賃を引いた際にマイナスの金額になってしまう問題を考えることになりました。

Day3

三日目は前回の宿題であったSuica残高から運賃を引いた際にマイナスの金額になってしまう問題を考えることからスタートしました。

問題自体はみんなで話し合ってスムーズに解消できました!

その後、残高から運賃を引く処理をGateクラスが持つべきかSuicaクラスに持たせるべきかを一度立ち止まって考えました。 (実装当初はGateクラスが減額の処理を行なっていた)

最終的にはSuicaクラスで減額用のメソッドを実装する形で進めたのですが、その理由は以下になります。

  • 今回のゴールはSuicaで電車に乗れること
  • 仮にSuicaで買い物をする機能を追加する場合にGateクラスが減額処理まで行なっていると、買い物時はその処理を呼び出せない

今回のゴールは「Suicaで乗車と降車ができること」なため、Gateクラスで処理をしてしまうことも可能ではありましたが、各クラスの責務を整理してSuicaクラスで実装することにしました。

TDDはペースが掴めるとスムーズに進められる&既存の処理を修正した際にテストが通るか落ちるかでチェックができ、それをモブプロを通じてみんなで共有できたことがとても良い体験でした。

最終的なコードは以下のリポジトリに反映させました。

github.com

まとめ

今回、機能追加のモブプロを通じて、みんなで意見を出し合いながら開発をすると

  • 自分にはない視点が得られる
  • 意見を出し合いながらコードを見直すことでだんだん良いコードになっていく流れを共有できる
  • ワイワイ楽しい!

など一人でコードを書いているだけだと得られない経験ができました!

参加してくださったFBC生の方もありがとうございました!

また頃合いを見て別のテーマでモブプロを開催したいなと思っています。