バグを減らす デバッグの工夫 ~ プログラミング実習で生き残るために ~ 2013/02/12 金森由博
よくあるプログラミングの風景 課題めんどくさい とりあえず適当に書くか エラーチェック めんどくさい あとまわし ちゃんと動くかわかんないけど全部書いちゃお ふー やっと全部書けた コンパイルしよ!? エラーメッセージの意味がわからん!! はぁ やっとコンパイルが通った 実行しよ えっ!? なんでセグメンテーション違反!? 何時間かけても動かない もうやだ
よくあるプログラミングの風景で 一番時間がかかるのはデバッグ
なんでそんなに時間がかかった?
ポインタ 構造体にまだ慣れてない 復習 & 練習するしかない
よくあるプログラミングの風景 課題めんどくさい とりあえず適当に書くか エラーチェックしていない 方針をちゃんと決めていない エラーチェック めんどくさい あとまわし ちゃんと動くかわかんないけど全部書いちゃお ふー やっと全部書けた コンパイルしよ!? エラーメッセージの意味がわからん!! はぁ やっとコンパイルが通った 実行しよ えっ!? なんでセグメンテーション違反!? 問題の原因を絞れていない 部分ごとにテストしていない 何時間かけても動かない もうやだ
バグを減らす デバッグの工夫 プログラミングの前に 紙に方針を書いて練る 方針によってはバグが出にくくすることも可能 バグを入れない工夫をする エラーチェック & エラーメッセージを丁寧に 全部書いてから ではなく 少し書いたらテスト バグが出たら 原因の箇所を素早く特定する コメントアウトなどでバグの箇所を絞る
エラーチェックとエラーメッセージ エラーが起こるかもしれないところはちゃんとチェックする エラーメッセージは一目見てわかるように 将来デバッグをしているときの自分は きっと頭が疲れている 将来の自分に優しくする 単に a とか error とか不親切なものではなくどんなエラーが起きたか詳しく書く
例 : コマンドライン引数 チェックなし 最悪 int main(int argc, char *argv[]) { int n = atoi(argv[1]); : コマンドライン引数が間違っているといきなりクラッシュ 実行した人は困惑
例 : コマンドライン引数 エラーメッセージあり さっきよりはまし int main(int argc, char *argv[]) { int n; if (argc!= 2) { printf( おばあちゃん頭だいじょうぶ?(^ω^) n ); return -1; } : が どんなエラーなのかわからない しかもイラッとする
例 : コマンドライン引数 丁寧なエラーメッセージ int main(int argc, char *argv[]) { int n; if (argc!= 2) { printf( エラー : 整数の引数が 1 つ必要です n ); return -1; } : 何が間違っていたのか一目でわかる
例 : ファイルのオープン エラーチェックだけ エラーメッセージなし int LoadImage(const char *filename, ) { FILE *fp; if ( (fp = fopen(filename, rb )) == NULL ) return 0; : エラーメッセージがないので プログラムの実行中に何が起きたかわからない
例 : ファイルのオープン エラーメッセージあり int LoadImage(const char *filename, ) { FILE *fp; if ( (fp = fopen(filename, rb )) == NULL ) { printf( Cannot open file n ); return 0; } : しかしまだ改善の余地あり
例 : ファイルのオープン どの関数で どのファイルを 開けなかったか int LoadImage(const char *filename, ) { FILE *fp; if ( (fp = fopen(filename, rb )) == NULL ) { printf( %s: cannot open %s n, FUNCTION, filename); return 0; } バグの原因を特定しやすい :
少し書いたらテスト 全部書いてからテストしようとすると ミスが重なって原因の特定が難しくなる 特に ヘッダファイルのインクルード忘れ や 括弧 { ( の閉じ忘れ などは謎のエラーが出やすい プログラムを部品 ( モジュール = 関数 ) に分け部品を作るごとに正しく動くかテストする キーワード : 単体テスト テスト駆動開発 IT 企業での開発でも実際に行われている
少し書いたらテスト いきなり全部書かない int main(int argc, char *argv[]) { /* スペースの都合でチェック略 */ Matrix A; Vector b; LoadLinearSystem(argv[1], &A, &b); printf( Matrix A: ); FprintMatrix(&A); printf( Vector b: ); FprintVector(&b); SolveGaussJordan(&A, &b); printf( Vector x: ); FprintVector(&b); } return 0;
少し書いたらテスト 例えば int main(int argc, char *argv[]) { /* スペースの都合でチェック略 */ Matrix A; Vector b; LoadLinearSystem(argv[1], &A, &b); /* これだけテスト */ return 0; } int LoadLinearSystem( ) { /* 何か処理を行う */ /* 期待する結果になったかテスト printf などで値を確認 */ return 1; }
バグの箇所を絞る それでもバグが出たら バグの箇所を絞る int main(int argc, char *argv[]) { /* スペースの都合でチェック略 */ Matrix A; Vector b; LoadLinearSystem(argv[1], &A, &b); #if 0 /* 便利.1 にすればコンパイルされる. ネスト可 */ printf( Matrix A: ); FprintMatrix(&A); printf( Vector b: ); FprintVector(&b); SolveGaussJordan(&A, &b); : #endif ここだけ実行してテスト
バグの箇所を絞る それでもバグが出たら バグの箇所を絞る int main(int argc, char *argv[]) { /* スペースの都合でチェック略 */ Matrix A; Vector b; LoadLinearSystem(argv[1], &A, &b); printf( Matrix A: ); FprintMatrix(&A); #if 0 printf( Vector b: ); FprintVector(&b); 問題なければ次へ SolveGaussJordan(&A, &b); : #endif
バグの箇所を絞る for 文の中で変数の変化を調べる for (ri = 0; ri < A->rows; ri++) for (ci = 0; ci < A->cols; ci++) { /* 何か計算する */ printf( A(%d,%d) = %f n, ri, ci, A->elems[ci+ri*A->cols]); } できるだけ詳しく わかりやすく 引数に期待される値が入っているか調べる void FprintMatrix(Matrix *mat) { printf( FprintMatrix: rows = %d, cols = %d n, mat->rows, mat->cols); : } できるだけ詳しく わかりやすく
それでも詰まったら 得意な友達 TA さん 教員に質問 相談する プログラミングが得意な人は何が違う? バグの場所を特定する能力 に長けている 知識 経験の差 一人で延々と悩まないこと 他の人に頭を下げて教えを乞うことも大事