| Top Page | プログラミング | Perl 目次 | prev | next | 索引 |

6.プログラムのまちがい,データのまちがい.

ひとはまちがいなくまちがえます.プログラムを書くときもまちがえます. データの入力もまちがえます. そのまちがいにどう対処するかという話です.


プログラムのまちがい

プログラムのまちがいには大きくわけて二つあります. 文法のまちがいと論理のまちがいです.

文法のまちがい

文法のまちがいを含んだプログラムを実行しようとすると,Perl の処理系 (Windwos なら perl.exe)がエラーメッセージを表示するだけで止ってしまい, せっかく書いたプログラムを実行してくれません.

文法のまちがいが発生する場合としては,

などのケースがあります.エラーメッセージを見れば,どの行にまちがいが 含まれているか(およそ)分かりますので,両方の可能性を念頭におきながら まちがいさがしをします.Perl でありがちなうっかりまちがいとしては,

$x = 1                 # 文末のセミコロン ';' を書き忘れる.
prunt $x;              # 関数名をまちがえる.
y = $x + 1;            # 変数名のはじめの $ を書き忘れる.
if ($x == 0)           #   { と } の対応があってない.
    print "x is zero;  # 文字列の終わりの引用符を忘れる.
}
if ("aa" == "bb") {    #  文字列を比較したいのに算術比較演算子を書く.
    print "Same Strings!?\n";  #  →"aa" も "bb" も数値としてはゼロで等しい
}
$x = 5;              # 全角の空白文字を混入させる(見えないけど).
$z = 2 * ($x + $y);  # 全角文字を混入させる(この例ではカッコが全角).
print "end\n";           コメントの開始の記号 # を書き忘れる.

などがあります.

論理のまちがい

論理のまちがいを含んだプログラムだと,実行はされるけれど,プログラムの書き手 ないし使い手の意図とは違った動作をしてしまいます (「プログラムは思った通りには動かない.書いた通りに動く」).

一見するとちゃんと動いてるかのように見えることも多いので, 文法のまちがいよりもタチの悪いまちがいです. プログラム中に混入した論理のまちがいをバグ(bug)と呼びます. バグを探し出して正しく直す作業はデバッグ(debug)と呼ばれます.

論理のまちがいが発生する場合を整理してみます.

一見しておかしな結果が出てくる場合は,バグがあるに違いないとすぐ分かります. でも,ちょっと見るとよさそうだけども実はバグが潜んでいるということも 多々あります.そんなプログラムの計算結果を信じてデータ解析を進めてしまったら 大変です. 正しく処理すればこんな結果になるはずだと分かるようなテストデータを用意して 処理させたり,いろいろなパラメータ設定をして挙動を見たりして,プログラムの 正しさをチェックします. 自分は絶対まちがえる,どこかにきっとバグがいる, という強い信念を持ってテストしましょう.

デバッグ:論理のまちがいさがし

バグがあることが分かったら,その居場所を探します. 冷静な頭でプログラムを読み直し,このような処理の流れで正しいのか 考えます.

変数の値を print する文をあちこちに書き加えて, 意図したように処理が進んでるかどうか途中経過をチェックするのは, どこに誤りがひそんでいるかを見つけるのにとても効果的です. print デバッグとも呼ばれるワザです.まあワザというほどのものでは ないけれど,基本のキです.

また,あやしいところを含んだ短いプログラムをつくって動作をテストする, あやしいところのあたまに # をつけてコメントにしてしまう (コメントアウトする)などして, 誤りのひそむ場所を絞りこんでいきます.

あやまりが混入しやすい場所として,いろんなものの「境界」付近があります. 繰り返しの一番最初や一番最後にも正しい処理がされるか,データファイルの 末尾に到達したときに正しく処理されるか,なにかがちょうどゼロになったときや 等しくなったときに正しく処理されるか,繰り返しの回数が1回足りなかったり 多すぎたりしないか,比較演算子は < なのか <= なのか,などです.

このほか,Perl のプログラムでよくあるバグに,変数名の書き間違いがあります. はじめて見る名前が出てきたら,ただちにその名前を変数を作ってくれちゃう ためのバグです. すでに変数のページの最後のほう で,実行時の -w オプションについて説明しました. 値を一度代入しただけで,その値をぜんぜん参照していない変数があったり, 値を代入セットしてない変数の値を読もうとしてるところがあると, メッセージが表示されます.

また,エディターに文字列補完機能がある場合にはこれを活用すると 変数名の誤りが避けられます.最初の数文字を入れたところで,この文字列 ではじまる変数名をエディターが見つけて入力の手助けをしてくれる機能です. 見つかるはずなの変数名が出てこなかったら最初の数文字がちがっている ことが分かるし,候補から選ぶだけなら打ち間違いの余地もありません (複数候補からの選び間違いはありえるけど).

さらに中級編のワザとしては use strict; する,という手があります. ここでは解説しませんが,変数の自動生成を許さず, こんな変数を使うぞ!と特定の形式で宣言した変数だけ使えるようにする ワザです.やや不便にはなりますが,長いプログラムへのバグの混入の 可能性を大きく減らせます.百行以上のプログラムを書くようになったら, ぜひ勉強してみてください.

少数点以下のデータのあつかいに注意

研究用のプログラムでは,整数以外の数値を使うこともよくあります. そのとき,小数点以下の数値の精度が問題になることがあります. これはコンピュータのなかでの数値の保持方法と関係します.

簡単な例では,0.1 を 10 倍しても正確に 1.0 になるとは限らないのです. コンピュータのなかではすべての数値は二進数で表現されますが, 0.1 は二進法では正確に表現できないからです (0.5 や 0.25 なら正確に表現できる).

for ($i = 0; $i < 10; ++$i) {
    print $i / 10, "\n";
}

print "\n";

for ($i = 0; $i < 1.0; $i += 0.1) {
    print $i, "\n";
}

上のプログラムを私のコンピュータで実行してみたところ,最初の for ループは 10回繰り返されて 0 から 0.9 まで表示されましたが,2番めの for ループは 11回繰り返されて 0 から 1 まで表示されてしまいました.0.1 を 10 回足しあわせた ものが 1.0 より小さかったからです. このように,小数点を含む数値の条件判断には注意が必要です.


データのまちがい

プログラムが処理するデータにも,まちがいはつきものです. 処理させようと思って指定したデータファイルの名前が違ってるとか, データファイルのなかに,入力時のまちがいが含まれているとか, いろんな形のまちがいがあります.

プログラムの中で発見できるようなまちがいは, チェックして指摘するようにプログラムを書いておきましょう. たとえば,前のページの例で植物がつける花の数と,そのうち実になった数の データを処理しました.この場合,実の数が花の数より多くなっていたら, これはまちがいである可能性が高いでしょう(花の数を数えたあとに さらに花が咲いた可能性もありますが). そんなデータが現れたら,これほんと? とメッセージを表示するように プログラムを書いておけば,入力まちがいを発見する手掛かりになります. たとえばこんなふうに.

#  個体番号,花序番号,花の数,実の数に分割
($plantID, $florID, $n_flowers, $n_fruits) = split (/\s+/, $line); 

if ($n_flowers < $n_fruits) {
    print "!!! n_flowers < n_fruits in ", $line, "\n";
}

また,データの個数(上の場合は個体番号,花序番号,花の数,実の数の4つ)が 違ってるのは明らかにまちがいです. これを検出したかったら,たとえばこんなふうに書くことができます.

#  個体番号,花序番号,花の数,実の数に分割
($plantID, $florID, $n_flowers, $n_fruits, $dummy) = split (/\s+/, $line); 

if (!defined ($n_fruits)) {
    print "!!! Too FEW data in ", $line, "\n";
}
if (defined ($dummy)) {
    print "!!! Too MANY data in ", $line, "\n";
}

このなかで,defined という関数を使ってます.これは,変数が未定義 (あるいは実定義値を持っている)かどうかを調べるもので, 未定義なら偽,定義されている(未定義値以外の値を持っている)なら真を返します (※正確には,defined の引数は変数名である必要はなく,値を評価できる式なら なんでもよい).

リスト間の代入の場合,右辺のリストの1番めの要素が左辺のリストの1番めの要素に 代入され,2番めの要素が2番目に代入され… とういう処理が行われることは すでに書きました. では,リストの代入で,右辺と左辺の要素の数が違うときはどうなるか. 右辺の要素のほうが多いときは,うしろのほうの多すぎる要素は無視され,どこにも代入されません.左辺の要素のほうが多いときは,うしろのほうの多すぎる要素には未定義値が代入されます.もともとなにかの値が入っていたとしても,それは未定義値に置き換わります.

というわけで,$line に含まれるデータが4個より少ないと, 左辺のリストの4番めの要素である $n_fruits は未定義値になり, defined ($n_fruits) は偽となります.これに ! をつけて真偽をひっくり返してる から,$n_fuirts が未定義のときに if のうしろの {} の中が実行され,データが 足りないぞ! というメッセージが表示されます.

もし $line に4個より多くのデータが含まれると,左辺のリストの5番めの要素 である $dummy に5番めのデータが代入されます.$line が正しく4つしかデータを 含まないなら, $dummy は未定義になります. そこで, defined ($dummy) が真のとき,データが多すぎるぞ!  というメッセージを表示させています.


データのまちがいさがしのやり方は,扱うデータ次第です. 「自分のデータの場合,正しく入力されてるならこうなるはず」という条件をよく考えて, プログラムでエラーチェックをしましょう.


| Top Page | プログラミング | Perl 目次 | prev | next |