ゲーム開発でバグの原因を見つけるために自分がしてきたこと


開発効率を上げ不具合を減らすためにテストをしましょう、ってのは非常によく分かるしできれば積極的に取り組みたいのですが、ゲーム開発だと100%取り入れるにはなかなか難しい事情があります。

例えばオブジェクトごと独立して動くように作ることができればテストもしやすいんでしょうけど、ゲーム制作の場合だと全部くっつけて動かしてみて初めて分かるバグってのが結構あります。
絵が表示されない、音が鳴らないなど、機械的に判定の難しいものもよくあります。

それら、ゲーム開発のバグ対応における人力で頑張って何とかする部分について、これまで私がやってきたことをメモっておきます。

もし何か良いアイデアがあるようでしたら教えてもらえると嬉しいです。

再現

再現性を求める

どういう状況でバグが出るのかを調べます。
それが分かったら苦労しない!っていうケースも多々ありますが、再現性が無いというのもそれはそれでヒントです。
本当に再現性が無いのかどうか、きちんと確認しましょう。

ちなみにプログラマ以外がバグを見つけることも少なくないのですが、その場合に再現性を聞いても「うろ覚えでよく分かりません」みたいな事が結構あるので、プロジェクトに関わる人すべてにバグの再現性の重要さを伝えて、協力を得られるようにしましょう。

再現の手順を確立する

再現性が分かればあっという間に修正可能なケースもありますが、それでも分からず長期戦になりそうなら再現するための最短手順を確立しましょう。
特定シーンへのジャンプ、フレームスキップによる早送りなどは最初から実装しておくと便利です。
また、疑似乱数はseed値を固定化したり、現在日時の取得はラッパをかませて任意の日時を固定で返せるようにしておくと再現させる手間が省ける場合があります。

エージングテスト対策をする

アーケードゲームは、電源を入れたら停電になるか故障するまで動き続けなければ行けないみたいな過酷な環境に晒されることがあります。

そのため、電源を投入したら ロゴ−タイトル−デモプレイ−スコアランキング 等の一連のタイトルループで何日も放置するエージングテストをします。

このテストの結果、「3日目で不具合が発生しました!」とか言われても、再現させるために毎回3日待つとか事実上無理なわけです。

なのでこういうケースの場合は再現性を求めるよりは、再現した場合に得られる情報量が多くなるようにします。
具体的には、ログ出力をいっぱい仕込んでおきます。(ストレージがあふれない程度で・・・)
たいていはメモリに起因する不具合でのフリーズなので、メモリ空き容量やフラグメンテーション状況などを随時出力しておくと役立ちます。

ビデオデッキやビデオカメラを使って画面を録画し続ければ、不具合が発生した瞬間の状況が確認できます。
以前CEDECか何かで見たのは、FPSが落ちたら画面のスクリーンショットを保存する機能を実装したという事例もありました。

また、以前私が体験した事例としては、3日くらい起動すると原因不明でフリーズするという症状があり、さんざん調べた挙げ句、原因はハードウェア側のメモリの不良だったという事がありました。
テスト環境が少ないとなかなか「ハードウェアが原因」と結論づけるのは難しいので、複数環境を用意し、すべてで同じような再現がされたか確認するのも良いと思います。

確認

症状を確認する

不具合の症状から、想定される原因をざっくりと想定します。

  • フリーズ
    • メモリリーク
    • リソースリーク
    • 無限ループ離脱条件の不具合
    • 再帰呼び出し条件の不具合
    • セグメンテーションフォールト
    • マルチスレッドの排他制御まわり
  • パフォーマンスの低下
    • メモリ不足によるスワップの発生
    • ディスクアクセス
    • ネットワークアクセス
    • ボトルネックとなるAPIの大量呼び出し
    • デザインデータやサウンドデータの作り
    • マルチスレッドの排他制御まわり
  • 動作はするが意図しない結果になる
    • ロジックの不具合
    • 外部ライブラリの使用方法の誤り
    • 設定ファイル
    • マルチスレッドの排他制御まわり

他にも色々とあるかと思いますが、ざっと思いつくところを挙げてみました。

マルチスレッドまわりの不具合は場合によっていろいろな症状が出るので大変ですね・・・

ログを確認する

バグとログの内容に関連があるか時系列的に判別が着くように、ログには出力日時を必ずつけるようにします。
関連がありそうな場合、それが社内で作られた箇所のログであればソースコードの確認をします。
場合により担当者を捕まえて尋問、ではなくヒアリングします。

パブリックな外部ライブラリの場合はググると色々と情報が出てくる可能性が高いです。
ただ、お行儀の良いライブラリの場合はいちいち標準出力にエラーなんか吐きませんから、エラーが返ってきたらエラーメッセージ取得関数を呼び出してログに出力という処理が必要になります。

プロプラエタリな外部ライブラリの場合は開発元のドキュメントを検索するか、直接問い合わせましょう。

いつのバージョンから再現し始めたのか確認する

一番怪しいのはやはり不具合が発覚した直近のcommitです。
commit logやdiffを確認しましょう。

ただ、デザインデータやパラメータのデータなども含めると、ある程度時間が経ってしまってから過去のバージョンを再現するのは困難なケースもあります。
そんなときはJenkinsなどを使って逐一パッケージを出力して世代管理するようにすれば役立つと思います。
この方法は受け売りなので自分自身実践したことがまだ無いのですが、コンソールタイトルだとファイルコピー時間やディスク容量がばかにならないので、ケースバイケースでの工夫は必要だと思われます。

いつバグに気づいたのかを確認する

バージョン管理的なタイミングだけではなく、実時間的にいつ発生したのかもヒントになることがあります。

日付や時間がバグトリガーになっている可能性があるので、何年何月何日何時何分何秒地球が何回回ったとき・・・までは不要ですが、可能な限り細かい情報があると良いです。

月や時間の切り替わる境界的な問題、または閏年などにまつわる問題など、ロジック的な不具合に対するヒントになります。

また、不具合かどうか分からないけれどどうも特定の時間帯に調子が悪いといった場合には、社内のシステムメンテナンスなど外的要因の可能性もあるので、そのような場合の確認にも日時の情報は有用です。

メモリやディスクの使用量を確認する

メモリ使用量がいっぱいなら原因はメモリリークです。
その場合ゲームはたいていフリーズしたりsegentation faultで落ちたりするので、一定時間ごとにメモリの消費量がログ出力されていると原因追及に役立ちます。

また案外忘れがちなのがディスクの使用量で、ファイル出力が失敗した場合の対応が不十分で不具合に繋がる事があります。
ちなみに、ゲームのセーブデータは保存先の空き容量が足りませんでした〜で終わってしまってはダメなので、あらかじめ想定されるセーブデータサイズを初期情報で確保しておくのが一般的かと思います。

再現する環境を確認する

動作環境に依存する外的な要因が原因である可能性もあります。
原因がいまいち特定出来ない場合は、他の環境でも同じように再現するか確認すると良いでしょう。

特定の環境で動かないような場合は、OSのバージョンや常駐アプリといったソフトウェア的なものと、ハードウェアスペック、特定機種依存、故障といったハードウェア的なものが考えられます。

PCに比べればゲームコンソール機は比較的一定のハードウェアスペックを保っているので問題は起こりにくいですが、微妙なところで型番ごと挙動が違う事があったりするので油断は禁物です。

調査

デバッガを使う

プラットフォームごと様々なデバッガがありますが、必要とされる機能は結局どれも一緒で、

  • ブレークポイント
  • ステップ実行
  • 変数のウォッチ
  • 関数コールのスタックトレース

このあたりがあればどのデバッガを使っても同じ手法でデバッグが出来ます。

基本は、怪しい箇所にブレークポイントを仕掛けて実行、引っかかればそこから調査開始といった具合です。

プロファイラを使う

パフォーマンスに問題がある場合はプロファイラを使います。
関数ごと呼び出し回数や処理時間を表示してくれたりします。

ただ、プロファイラを使うとターゲットマシンの負荷が高くなる事があるので、おおむね見当がついたら自前で時間計測してprintf()するような処理を挟んだ方が良い場合もあります。

新しいプラットフォームの場合、デバッガがあったとしても、まともに使えるプロファイラがまだ存在しないような事もあります。その場合もprintf()です。

ツールを使う

デバッガ、プロファイラ以外にも様々なツールがあります。

メモリリーク検出のためのツール、ネットワークのパケットキャプチャなど、用途が特化されたものです。
前述のプロファイラもそうですが、モニタリング系のツールは負荷が高くなる事があるので、都合に応じてprintf()デバッグと併用すると良いと思います。

行き詰まったら

誰かに相談する

無関係のプログラマに相談するとあらゆる前提条件を説明しなくてはならず煩雑なので、相談相手としてはプロジェクト内のプログラマが良いと思います。

また、その際に気をつけなければ行けないのが、自分の中にある先入観、思い込みを捨てると言うことです。

事実に基づいて「その処理は問題なかった」と言うなら良いのですが、「その処理は問題ない”はずだ”」はダメです。
行き詰まっているまさにそのときに自分のカンを信用しても仕方が無いです。

視点を変える

デバッガで1行ずつステップ実行をしたりして調査をしていると、ミクロな視点での挙動に意識が取られがちで、全く別の箇所に本当の原因があった場合にはなかなか気づくことが出来ません。

ある程度調べてもダメな場合は、ブレークポイントを一旦すべて削除して改めて最初から実行してみると、新たな原因の可能性に気づけることがあります。

バグの原因究明はあらゆる可能性の枝葉を潰して一つの事実にたどり着く作業ではありますが、本当に行き詰まったときは、むしろあらゆる可能性を排除しない事こそ大事だと思っています。

気分を変える

他のことを始めたり、ご飯を食べたり、明日に持ち越して帰宅したり・・・

自分の場合はこの方法が一番効果的な気がします。
同時に前述の視点を変える効果も得られますし。

以上

泥臭さ満点でお送りいたしました。

Leave a Comment