もうすぐ完成! テスト駆動開発によるボウリングのスコア計算プログラム
2015年2月19日(木)
はじめに
前回は、「静的設計のブレイクスルー」をテーマに、「設計の洗練」とそれに伴うリファクタリングについて初学者の「古谷」がTDDの達人である「高梨先輩」に教えを乞う形で進めてまいりました。今回は前回「責務の分担」を行ったクラスに実装を行う過程についてご説明します。なお、この連載ではプログラミング言語RubyでTDDを実現しておりますが、必ずしもRuby経験者が対象ではありません。Ruby未経験者でもプログラミング経験者であれば、ある程度理解できるように考慮して進めてまいります。
______________________________________
- [古谷]古谷です。前回は、ありがとうございました。正直、前回学んだ範囲について、ちゃんと理解できているとはいいがたい状態です。設計のスキルも、MECE(Mutually Exclusive and Collectively Exhaustive:重複なく、漏れなく)で今までの設計を俯瞰するスキルも、どちらも私はまだまだです……
- [高梨]高梨です。前回は盛りだくさんで大変でしたからね。ですが、私が伝えたかったのは、実は以下の2点だけなのです。
- TDDを進めていると、今回のように「手詰まり感」が出てくることがあります。そのタイミングで、一度静的な設計を見直しましょう。
- TDDを行う際には、「動的設計」と「静的設計」を交互に行き来することで、全体的な設計を洗練していきます。
- [高梨]今回は、前回手掛けたBowlingGameクラスとFrameクラスの「責務の分担」の実装を進めることで、古谷さんの不安を一掃していきましょう。大丈夫です、今の不安を自信に変えて行くのがTDDですから。
- [古谷]不安を一掃……
なんとなく倍増しそうな気がしなくもないですが、わかりました。頑張ります! - [高梨]大丈夫です。今回も今まで通りインクリメンタルに進めましょうね。前回どこまで進んだか覚えていますか?
- [古谷]前回はここまでの設計を行いました。そして、投球を実際に記録するところまでは一応実装しています。
- [高梨]ありがとうございます!
それでは前回に引き続き今回もFrameの実装を続けましょう。まずは「Frameの完了を判定する」このテストケースを作ってみてください。 - [古谷]フレームの完了?
えっと、投球が2回完了しているかどうかの判断だけで大丈夫ですか? - [高梨]まずはそれからで大丈夫ですよ。メソッド名はfinished?でどうでしょう?
今回のテストケースでは、refuteメソッドを使用してください。refuteは、結果がfalseである場合に成功します。これまで使っていたassertは、結果がtrueである場合に成功を返すメソッドなので、正反対ですね。 - [古谷]はい、テストケースを作りました。まだfinished?メソッドをFrameクラスに組み込んでいないので、エラーになりますが。
- [高梨]大丈夫です!
それではFrameクラスの実装を行いましょう。 - [古谷]しつこいようですが、今は2投=フレーム完了だけ考えていたら良いですよね?
- [高梨]大丈夫です!
- [古谷]それでいいのであれば……
- initializeメソッドに@shot_count = 0を定義
- record_shotメソッドで@shot_countのインクリメント
- finished?メソッドの実装
- [古谷]上記で良いでしょうか?
- [高梨]やってみてください。
- [古谷]できました!
- [高梨]大丈夫です!判定もできていますね!
では次に、ストライクへの対応も行ってください。 - [古谷]ストライクストライク、うーん……
- [高梨]単純に「10ピン倒したらフレーム終了」と判断して大丈夫ですよ。
- [古谷]あれ、そんなに単純で良いのかな?
なら、finished?メソッドの条件も「@scoreが10以上だったら完了」で大丈夫ですか? - [高梨]大丈夫です、実装してみてください。
- [古谷]こんな感じになりました!
- [高梨]素晴らしい!
それでは、またFrameをBowlingGameクラスに組み込んでいきましょう。 - [古谷]ええっ!?
すみません、今の私にはどうやって組み込んだら良いのか見当もつきません! - [高梨]落ち着いてください。方針はもう決まっています。
- 現在のフレームを知っている
- 投球を適切なFrameに記録させる
- [高梨]今回の組み込みの際に意識するのは、この2点だけです。
- [古谷]そ、そういわれても……
- [高梨]ボーリングは10フレームあります。つまり10個分のフレームオブジェクトをこのBowlingGameで管理するわけですが、古谷さんは今回どういう方法で管理するつもりですか?
- [古谷]えっと、そうですね、配列が良いかなと思っていますが……ダメですか?
- [高梨]大丈夫です!
では配列で管理する場合、「現在のフレーム」は配列のどこに格納されますか? - [古谷]どこ?どこって……うーんフラグを……
- [高梨]「現在のフレーム」は常に「最新のフレーム」です。ですから、投球完了後に次の投球を入力するFrameオブジェクトを生成して配列の最後尾にセットしていたら、最後尾のフレーム=現在のフレームと考えて大丈夫です。まあ、まずはBowlingGameのテストクラスにテストケースを実装してみましょう。そうですね、「全ての投球を1にしてすべてのフレームの点数が2になるか」というテストケースでお願いします。
- [古谷]はい、期待値は2なのですが、今のところBowlingGameクラスはFrame単位で得点を管理してないので、20が返ってきてエラーになります。
- [高梨]はい、それではいよいよ、BowlingGameクラスへの組み込みを進めていきましょう。まずは今まで通り、initializeメソッドにフレームを管理する変数を定義し、初期化します。
- [古谷]はい、それでは10フレーム分管理する変数を、@framesと言う名前で定義します。ただ……
すみません、Rubyでの配列初期化の仕方がわかりません! - [高梨]@frames = [ Frame.new ]としてください。ちなみに、@framesの最後の要素を取得する場合はlastメソッドを使用します。また配列の要素を追加する場合は、@frames
- [古谷]そこまで教えてもらったら、できそうな気がします。
えーと……はい、これでどうでしょう?
- [高梨]大丈夫です!よく頑張りました、ここまでの成果を反映したクラス図を見てみましょう。
古谷さん、ここまで実装できましたよ!
- [古谷]ありがとうございます。ひとえに高梨先輩がいらっしゃるからなのですが、やってみたら何とかなるものですね。この勢いのまま頑張ります!
えーと、次は「投球結果を判定する」ですね? - [高梨]おっ、やる気スイッチが入ったようですね!
そうですね、次は投球結果の判定をFrameに実装します。まずはスペアから始めましょう。finished?と同じ要領で、spare?メソッドを実装してください。今までと同じように、テストケースからお願いします。テストケース名は「test_2投目で10ピン倒すとスペア」でお願いします。 - [古谷]あ、これはfinished?と同じように作れます!
- [高梨]素晴らしい!それでは全く同じ要領で、ストライクの判定も実装しましょう。テストケース名は「test_1投目で10ピンを倒すとストライク」でどうでしょう?
- [古谷]スペアと違って、1投球目だったらストライクですよね……
はい、できました!
連載バックナンバー
Think ITメルマガ会員登録受付中
Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。