今回のバグは「フィールドの初期位置が表示されないことがある」です。
今回の問題と前回の違いは、「配列fieldの初期値」と「プレイヤーの開始地点の設定方法」です。「今回のソースコードは前回の改造版」と書いてあることから、仕様書とソースコードを読む前に、「前回との仕様の違いを洗い出す」「ソースコードの違いも洗い出す」を実施したエンジニアは大正解です。おめでとうございます。これは、極めて正しい保守のプロセスです。「たかが練習問題なので、力まかせに解いてやる」は、エレガントではありません。常に、「最小の労力と時間で課題を解決する」ことを意識しましょう。
今回のプログラムは、配列fieldの境界は、全て1にセットしてあるため、領域外にアクセスすることはなさそうです※2)。また、今回はプレイヤーの初期位置をランダムで決定します。実装では、rand関数を使い、filed[0〜9][0〜9]のどこかに、プレイヤーの初期位置を示す「2」を設定していますね。パッと見て、これでよさそうですが、プログラムの記述に問題があります。この方法では、fieldの初期値によっては、正しくありません。リスト2の迷路探索プログラムの冒頭にある、下記のリスト3をご覧ください。
void SetField(int x, int y){ if (field[y][x] != 1) field[y][x] = 2; }
リスト3は、プレイヤーの開始地点をセットする箇所です。2行目のif文をご覧ください。「もし、field[y][x]が1以外だったら、2をセットする」という記述があります。問題点は、以下の2つです。
開始地点のフィールドの値が0だったら、開始地点は正常に設定できます。このバグを回避するには「フィールドの初期値がセットできるまで、繰り返す」「0の箇所だけをランダムで選択する」などの方法があります※3)。他にも、良い方法はあるでしょう。模索してみてください。
※2)もちろん、初期値の設定を間違える可能性があります。詳しくは、前回の記事をご覧ください。
※3)なお、修正の際には、配列fieldの境界に開始地点を設定しないように注意が必要です。
このバグは、前回から潜在的にあったバグです。前回も、配列fieldは「globaldata.hの値以外は使用しない」という制限事項はありましたが、壁を開始地点にはしたくないと、問題文を考えた結果、このバグを見逃してしまいました。昔のバグは、ものすごく生命力が強いと痛感する瞬間です。
今回の自己採点シートを以下に示します。
問題 | 内容 | 配点(点) |
---|---|---|
迷路探索プログラム | 問題文とソースコードを一通り読んだ | 20 |
ソースコードから、前回との違いを洗い出した | 20 | |
デバッグをした | 20 | |
プレイヤーの開始地点を設定できないことがあるバグを見つけた | 20 | |
その他のバグを見つけた | 5×件数 | |
リスト4 自己採点シート |
実は、これら以外にもう1つバグがあります。問題のある部分を抜粋します。
現在地を(x,y)とすると、プレイヤーは以下の手順で移動する。
上記は、2.2.2の仕様の一部を抜粋したものです。「現在地を(x,y)とすると」と書いてありますが、その下を見ると、「フィールド座標(y-1,x)の値を調べる……」のようにx,yの順番が入れ替わっています。数学的には、座標(x,y)の順番で記述しますが、実装に使用する配列field[HEGIHT][WIDTH]は、つまり(y,x)で記述します。非常に紛らわしいのですが、仕様として書く場合、「フィールド座標(x,y-1)の値を調べる……」のように、数学の慣例通り、逆順に書くのが正解です。これに気づいた方には、20点を加点します。なお、修正する場合は、2.2.2の座標を反転すればOKです。
このバグは、前回からありました。自分が正しいと思い込むと、目の前にぶら下がっていても、見えません。後に、別の技術者が仕様書やソースコード読むと、「???」になります。昔のバグは、生命力が最強ですね。
今回は、迷路探索プログラムの修正版を出題しました。皆さんは、バグを見つけられましたか。機能追加などでプログラムを見直してみると、今回のように、見過ごしていた思わぬバグに出くわすことがあります。注意してみてください。
本シリーズ10回目となりますが、過去の問題文にも未発見のバグが残っていると思います。お時間のある方は、あらためてバグ検出にチャレンジしてみてください。
東海大学 大学院 組込み技術研究科 非常勤講師(工学博士)
Copyright © ITmedia, Inc. All Rights Reserved.