さくら美緒(さくら・みお)

今号の問題
駅にある切符の券売機をシミュレーションするプログラムを作ってください。10円から500円までのコインを入れ,120円,150円,180円,210円,260円の五つのボタンから選ぶと,その切符が発券されるというものです。もちろんお釣りも戻ります。コインの種類の確認や釣り銭切れなどのエラー・チェックも入れてください。

 面倒なこと抱えていませんか? 何が面倒といえば人間関係が最たるものかもしれません。私も今まさに困っています。プロダクト・マネジメントもしているので,「あちらを立たてればこちらが立たず」を毎日繰り返しています。Aさんが作ったものを組み込むと問題が起きて,Bさんに文句を言われるのだけど,それはCさん担当部分が原因だとAさんは主張する。でも,Bさん担当部分にも問題があるし,工程上修正は難しいし…。

 そうした「複雑で面倒なもの」をプログラムとして作るにはどうすればいいでしょうか? 今回はそうしたものの中から処理を具体的にイメージしやすい切符の券売機を例にとって解決方法をお教えします。

処理を作るのが面倒なのは
処理の流れが複数あるから

 駅にある券売機で切符を買うときのことを思い出してみてください。行き先の駅までの値段を案内板で確認して,券売機にお金を入れたあとボタンを押せば,目的地までの切符が券売機から出てきます。

 これを券売機が行う一連の動作として想像してみます。すると図1のようになるでしょう。でも,これをそのままプログラムにはできません。なぜならこの流れ以外にもいろいろな流れがあるからです。例えば,1円玉や5円玉を入れた場合,普通の券売機なら,それがそのまま戻ってきます。この処理を図1の中に組み込んでみてください。次にお釣りがなくなったときのことを考えます。切符を買う人が誤ってコインを投入しないように,コインが投入される前に検出する必要があります。こうしたイレギュラーな処理を増やしていくと,どんどん複雑になっていくのがわかるでしょう。

図1●券売機のおおまかな処理の流れ
図1●券売機のおおまかな処理の流れ

 こうした場合には,処理の流れに沿ってそのままコーディングしてもうまくいきません(リスト1)。リストではコインを得てボタンが押されるまでループさせていますが,もしボタンの金額に達していなかったらもう一度コインを得るためにgoto文を使っています。ループが絡み合うあまりよくないコードです。

リスト1●図1の処理の流れをそのままコードにしたために複雑になった例
リスト1●図1の処理の流れをそのままコードにしたために複雑になった例

 このように複数の流れがあるものでは,「一つの本流となる流れ」を基に「エラーやほかの出来事が起きたら別の流れにする」というスタイルでは対応が難しくなります。処理の流れを想像しづらいので作るのも大変です。

「場面が移り変わっている」と考えてみる

 ここで,考え方を少し変えてみましょう。先ほどは,券売機の「動作」に注目していましたが,今度は券売機の「状態」に注目してみます。「状態」と言ってわかりにくければ,「場面」と考えてください。券売機には,「コインの投入を待つ場面」「切符を選ばせる場面」「切符を発券する場面」などのさまざまな場面があります。このほか,「コインの種類を確認する」といった券売機の内部の処理も,一つの場面とみなすことでプログラミングが楽になる場合があります。

 券売機は,これらの場面を行ったり来たりしながら動作します。例えば「コインの投入を待つ場面」でコインが投入されると,「コインを確認する場面」に移ります。この場面では,コインが「正しい」ものなら再び「コインの投入を待つ場面」へ戻りますが,間違っているのなら「入力エラーの場面」に移ります。このように,各場面は,何らかの出来事が起こったり,特定の条件が満たされるときに,別の場面に移ります。この様子を表現したのが図2です。場面ごとに分けて考えることで,すっきりした気がしませんか?

図2●券売機の場面の移り変わり。STATE_INPUTWAITなどの文字列はリスト2以降で使う定数
図2●券売機の場面の移り変わり。STATE_INPUTWAITなどの文字列はリスト2以降で使う定数
[画像のクリックで拡大表示]

場面を特定の値としてプログラミングする

 先ほどの券売機のプログラムを,処理の流れを「一つひとつの場面の切り替え」としてとらえて実装してみましょう。まず,各場面に対してそれらを識別するための整数値を割り振ります。「コインの投入を待つ場面」は1,「コインの種類を確認する場面」は2,という具合です。次に,この値を入れておく変数を一つ用意します。ここではstate変数としておきます。このstate変数の値を変化させることで各場面を移動させるのです。

 続いて,各場面に必要な情報を変数として定義します。とっかかりとして前の場面から入力されるもの,次の場面へ受け渡すものを考えてみるといいでしょう。どの場面でも「現在投入しているコインの種類と合計金額」「それに応じた行き先を示すボタン」などが必要になりそうです。

 ここまでの考えを基に作ったのがリスト2です。state変数の値に従って,場面ごとに用意したstate_XXXという関数を呼び出します。ここではswitch文を使って各処理へ分岐させています。呼び出された関数では,その場面に必要な処理を行います。前の場面で変化した変数があれば,それに従った処理を実行し,次の場面に備えて変数の変更が必要ならそれを行います。

リスト2●図2をコードに書き下したもの。state変数で場面を指定,それに対応した関数を呼び出す
リスト2●図2をコードに書き下したもの。state変数で場面を指定,それに対応した関数を呼び出す
[画像のクリックで拡大表示]

 処理を終えたら「state = STATE_ERROR;」のようにstate変数へ次の場面に対応する値を入れ,ループ1回ぶんが終わります。次のループではstate変数に設定された場面が呼び出されます。このループが繰り返されることで処理が進みます*1