メソッド(もしくは関数)というのは、inとなる情報とoutとなる情報の型が決まっています。ここで、簡単に以下のような宣言型のメソッドを想定しましょう。
public static boolean hoge(byte b) {
このstaticなメソッドは、他のstaticなフィールドなどを参照/変更することがないこととします。また、ファイルやDBなどの外部のリソースへのアクセスもないこととします。
すると、このメソッドにはbyte型のinに限られますから、入力値のバリエーションは256通りとなります。対して、出力値はbooleanですから2通りとなります。
このhogeメソッドの実装がどうなっているかはわかりませんが、256種類全ての入力値を試しに入力して見て、出力結果を控えたとします。
Map map = new HashMap();
for (int i = 0; i<256; i++) {
byte b = (byte)(i & 0xFF);
map.put(b, hoge(b));
}
そして、今度はメソッドpiyoを作り、先に確認した出力結果に応じて値を返すようにします。
public static boolean piyo(byte b) {
return map.get(b);
}
これで、メソッドhoge()とパフォーマンスを除き完全に動作が同じメソッドが出来ました。
同様の手法で、引数がintのメソッドも挙動をコピーすることができます。しかし、この場合32bit分の状態(約40億通り)をあらかじめ走査して保管しておかなければなりません。また、引数がintで2つになれば、とりうる組み合わせは40億の2乗で1600京と、爆発的に増えてしまいます。これを「次元の悪魔」といいます。
入力 - 出力表の圧縮を試みる
先のように、値と結果を1対1で保管していると莫大なメモリを消費しますから、ランレングス法(同じ結果が並んでいる場合にn個並んでいる、という情報の持ち方をしてデータ圧縮する方法) などを用いて圧縮を試みることにしましょう。
今、入力のint値と結果のboolean値を眺めていたら
-2147483648 |
false |
-2147483647 |
false |
: |
-1 |
false |
0 |
false |
1 |
true |
2 |
true |
: |
2147483646 |
true |
2147483647 |
true |
といったものだったとします。これは
i > 0
という計算式と合致します。すると、非常に簡素に入力と出力の結果を表現できました。いうなれば、40億通りのデータを表現方法の工夫でうまく圧縮できたわけです。このような視点で眺めると、プログラムというのは圧縮方法を考えることと言えるのです。
テストはいかに行われているか
さて、全てのパターンについて、どういう答えがあるべきなのか?先の例では元になるメソッドが存在しましたから、中身のコードは不明ですが、その挙動そのものが仕様でした。しかし、現実にはそのような既存の実装がなく、新たに作る場合が非常に多い。
では、入力値の全ての組み合わせについて、「意図したとおりに動くのかどうなのか」をどうやって確かめるのでしょうか?
実は、確かめていないんです。
実際には幾つかのサンプル的な値、とくに、値が切り替わる前後の特徴的な値を選んで、ピックアップして挙動の確認をしているにすぎません。 int値ふたつの組み合わせというだけで、1600京ものパターンを試行する必要があるわけですが、そのパターン個別にどういう結果であるべきかを人間が判断していたら、宇宙が生まれて今の形になるほどの時間を費やしてもテストを完了させることができません。
この次元の悪魔こそが、「バグのないプログラムを作ることは不可能」と言わしめる元凶なのです。
投稿日時 : 2007年10月22日 13:46