もうすぐ完成! テスト駆動開発によるボウリングのスコア計算プログラム

2015年2月19日(木)
吉谷 愛

はじめに

前回は、「静的設計のブレイクスルー」をテーマに、「設計の洗練」とそれに伴うリファクタリングについて初学者の「古谷」がTDDの達人である「高梨先輩」に教えを乞う形で進めてまいりました。今回は前回「責務の分担」を行ったクラスに実装を行う過程についてご説明します。なお、この連載ではプログラミング言語RubyでTDDを実現しておりますが、必ずしもRuby経験者が対象ではありません。Ruby未経験者でもプログラミング経験者であれば、ある程度理解できるように考慮して進めてまいります。

______________________________________

  1. [古谷]古谷です。前回は、ありがとうございました。正直、前回学んだ範囲について、ちゃんと理解できているとはいいがたい状態です。設計のスキルも、MECE(Mutually Exclusive and Collectively Exhaustive:重複なく、漏れなく)で今までの設計を俯瞰するスキルも、どちらも私はまだまだです……
  2. [高梨]高梨です。前回は盛りだくさんで大変でしたからね。ですが、私が伝えたかったのは、実は以下の2点だけなのです。
  1. TDDを進めていると、今回のように「手詰まり感」が出てくることがあります。そのタイミングで、一度静的な設計を見直しましょう。
  2. TDDを行う際には、「動的設計」と「静的設計」を交互に行き来することで、全体的な設計を洗練していきます。
  1. [高梨]今回は、前回手掛けたBowlingGameクラスとFrameクラスの「責務の分担」の実装を進めることで、古谷さんの不安を一掃していきましょう。大丈夫です、今の不安を自信に変えて行くのがTDDですから。
  2. [古谷]不安を一掃……
    なんとなく倍増しそうな気がしなくもないですが、わかりました。頑張ります!
  3. [高梨]大丈夫です。今回も今まで通りインクリメンタルに進めましょうね。前回どこまで進んだか覚えていますか?
  4. [古谷]前回はここまでの設計を行いました。そして、投球を実際に記録するところまでは一応実装しています。
前回までの成果(クリックで拡大)

図1:前回までの成果(クリックで拡大)

  1. [高梨]ありがとうございます!
    それでは前回に引き続き今回もFrameの実装を続けましょう。まずは「Frameの完了を判定する」このテストケースを作ってみてください。
  2. [古谷]フレームの完了?
    えっと、投球が2回完了しているかどうかの判断だけで大丈夫ですか?
  3. [高梨]まずはそれからで大丈夫ですよ。メソッド名はfinished?でどうでしょう?
    今回のテストケースでは、refuteメソッドを使用してください。refuteは、結果がfalseである場合に成功します。これまで使っていたassertは、結果がtrueである場合に成功を返すメソッドなので、正反対ですね。
  4. [古谷]はい、テストケースを作りました。まだfinished?メソッドをFrameクラスに組み込んでいないので、エラーになりますが。
  5. [高梨]大丈夫です!
    それではFrameクラスの実装を行いましょう。
  6. [古谷]しつこいようですが、今は2投=フレーム完了だけ考えていたら良いですよね?
  7. [高梨]大丈夫です!
  8. [古谷]それでいいのであれば……
  • initializeメソッドに@shot_count = 0を定義
  • record_shotメソッドで@shot_countのインクリメント
  • finished?メソッドの実装
  1. [古谷]上記で良いでしょうか?
  2. [高梨]やってみてください。
  3. [古谷]できました!
フレームの完了を判定する。2回投げたら終了(クリックで拡大)

図2:フレームの完了を判定する。2回投げたら終了(クリックで拡大)

  1. [高梨]大丈夫です!判定もできていますね!
    では次に、ストライクへの対応も行ってください。
  2. [古谷]ストライクストライク、うーん……
  3. [高梨]単純に「10ピン倒したらフレーム終了」と判断して大丈夫ですよ。
  4. [古谷]あれ、そんなに単純で良いのかな?
    なら、finished?メソッドの条件も「@scoreが10以上だったら完了」で大丈夫ですか?
  5. [高梨]大丈夫です、実装してみてください。
  6. [古谷]こんな感じになりました!
フレームの完了を判定する。ストライクなら1投目でも完了(クリックで拡大)

図3:フレームの完了を判定する。ストライクなら1投目でも完了(クリックで拡大)

  1. [高梨]素晴らしい!
    それでは、またFrameをBowlingGameクラスに組み込んでいきましょう。
  2. [古谷]ええっ!?
    すみません、今の私にはどうやって組み込んだら良いのか見当もつきません!
  3. [高梨]落ち着いてください。方針はもう決まっています。
  • 現在のフレームを知っている
  • 投球を適切なFrameに記録させる
  1. [高梨]今回の組み込みの際に意識するのは、この2点だけです。
  2. [古谷]そ、そういわれても……
  3. [高梨]ボーリングは10フレームあります。つまり10個分のフレームオブジェクトをこのBowlingGameで管理するわけですが、古谷さんは今回どういう方法で管理するつもりですか?
  4. [古谷]えっと、そうですね、配列が良いかなと思っていますが……ダメですか?
  5. [高梨]大丈夫です!
    では配列で管理する場合、「現在のフレーム」は配列のどこに格納されますか?
  6. [古谷]どこ?どこって……うーんフラグを……
  7. [高梨]「現在のフレーム」は常に「最新のフレーム」です。ですから、投球完了後に次の投球を入力するFrameオブジェクトを生成して配列の最後尾にセットしていたら、最後尾のフレーム=現在のフレームと考えて大丈夫です。まあ、まずはBowlingGameのテストクラスにテストケースを実装してみましょう。そうですね、「全ての投球を1にしてすべてのフレームの点数が2になるか」というテストケースでお願いします。
  8. [古谷]はい、期待値は2なのですが、今のところBowlingGameクラスはFrame単位で得点を管理してないので、20が返ってきてエラーになります。
  9. [高梨]はい、それではいよいよ、BowlingGameクラスへの組み込みを進めていきましょう。まずは今まで通り、initializeメソッドにフレームを管理する変数を定義し、初期化します。
  10. [古谷]はい、それでは10フレーム分管理する変数を、@framesと言う名前で定義します。ただ……
    すみません、Rubyでの配列初期化の仕方がわかりません!
  11. [高梨]@frames = [ Frame.new ]としてください。ちなみに、@framesの最後の要素を取得する場合はlastメソッドを使用します。また配列の要素を追加する場合は、@frames
  12. [古谷]そこまで教えてもらったら、できそうな気がします。
    えーと……はい、これでどうでしょう?
FrameクラスをBowlingGameクラスに連携させる(クリックで拡大)

図4:FrameクラスをBowlingGameクラスに連携させる(クリックで拡大)

  1. [高梨]大丈夫です!よく頑張りました、ここまでの成果を反映したクラス図を見てみましょう。
    古谷さん、ここまで実装できましたよ!
ここまでの成果(クリックで拡大)

図5:ここまでの成果(クリックで拡大)

  1. [古谷]ありがとうございます。ひとえに高梨先輩がいらっしゃるからなのですが、やってみたら何とかなるものですね。この勢いのまま頑張ります!
    えーと、次は「投球結果を判定する」ですね?
  2. [高梨]おっ、やる気スイッチが入ったようですね!
    そうですね、次は投球結果の判定をFrameに実装します。まずはスペアから始めましょう。finished?と同じ要領で、spare?メソッドを実装してください。今までと同じように、テストケースからお願いします。テストケース名は「test_2投目で10ピン倒すとスペア」でお願いします。
  3. [古谷]あ、これはfinished?と同じように作れます!
スペアを判定するspare? メソッドの追加(クリックで拡大)

図6:スペアを判定するspare? メソッドの追加(クリックで拡大)

  1. [高梨]素晴らしい!それでは全く同じ要領で、ストライクの判定も実装しましょう。テストケース名は「test_1投目で10ピンを倒すとストライク」でどうでしょう?
  2. [古谷]スペアと違って、1投球目だったらストライクですよね……
    はい、できました!
ストライクを判定するstrike?メソッドの追加(クリックで拡大)

図7:ストライクを判定するstrike?メソッドの追加(クリックで拡大)

フロイデ株式会社 代表取締役

「最新のアーキテクチャを追及し続ける技術者集団」を目指す、フロイデ株式会社代表取締役社長。現在は、自身のCOBOLからRailsまでの非常に幅広い開発経験や、学生や未経験社員への技術指導経験を糧に、技術講師としてソフトウエアエンジニアの育成に注力している。2013年06月より、初心者向けの「はじめようRuby on Rails開発!」シリーズを考案。“技術者の立場にたった、技術者の心に火をつける”熱い講義をモットーとしている。

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています