意図的にプログラムの動きをランダムにしてバグを早期発見するテクニックについて

プログラムを書いていると、素直に実装した結果として毎回特定の条件が満たされているけど、本来それは誰も保証してないという場面に出くわすことがよくある。保証されていない偶然の動作に依存することで生じるバグというのはかなり多い。

例えば最近では、ドラゴンボールZ ドッカンバトルというゲームで、2回SQL文を実行した結果が同じ順序で並んでいるという誤った期待をしているコードがあったせいで、ガチャの確率表示がめちゃくちゃになってしまって、運営が確率操作しているのではないかという騒動が発生したことがあった [1]。データベースでは空のテーブルにデータを追加してその直後に読み返すと、データを追加した順番に結果が返ってきたりしがちなので、問題のコードはきれいなテスト環境では偶然うまく動いてしまったのだろうと思う。

上のようなバグを防ぐために最近よく使われているのは、本来保証しないところをわざと壊すという方法だ。

例えばGoでは、言語組み込みのハッシュテーブルをイテレートするときの順番が、意図的に乱数を使ってランダム化されていて、同じプログラムを実行しても、要素を訪れる順番が毎回異なるようになっている。ハッシュテーブルの内部の順番というのは実装依存で、Goのバージョンによって異なるかもしれないので、Goではあえて毎回暗黙の前提を壊すことで、ハッシュテーブルの順番に依存したコードをプログラマがうっかり書いてしまわないようにしているのだ。件のガチャでもデータベースが結果をランダム化してくれていれば、おそらくバグは未然に防げただろうと思う。

ネットワークプロトコルでも最近ではランダム化が利用されている。例えば現在開発中の、次世代TCPともいうべきQUICというプロトコルでは、プロトコルのバージョン番号としてかなり広い領域がダミー番号のために予約されている。コネクション確立の最初のネゴシエーションのときに、サーバはランダムなダミー番号をレスポンスに入れることが推奨されている [2]。これにより、自分のサポートしていないバージョンを無視するという動作をクライアントがきちんと実装することを保証しているのだ。

カーネルのスケジューラをランダム化することで、マルチスレッドのバグを見つけやすくするという研究もあるようだ [3]。

ランダム化するためには追加のコードを書かないといけないし、最初はある意味単に迷惑なだけなので、そこまで広まっていないテクニックだと思うけど、このテクニックはもう少し広く使われてもよいように思う。

この記事が気に入ったらサポートをしてみませんか?