バグとは……
不具合、欠陥、障害。いくつかの呼び名があるが、プログラマにとっては「バグ」という名前が一番馴染み深い。そして「バグ」とは、プログラムに関わる誰かにとって何かがうまくいかない状態のことを言う。「誰か」(利害関係者)はユーザであることが多いが、他にもプログラマ自身のこともあるし、さらにはユーザでもプログラマでもない他の関係者(例えば発注者)の場合もある。「うまくいかない」というのは、言葉を変えれば「期待した通りにならない」ということだ。この「期待した通り」のことを一般に仕様と呼ぶ。
正しいとされる「期待」(仕様)が誰にとってのものであれ、実際のプログラムの振る舞いとの差異が指摘されてしまえば、それはバグになる。バグに対してプログラマが許される振舞いは次の 4 つ。
- バグを修正する。
- 「これは仕様(あなたの期待通り)のものです」と関係者を説得する。
- 他のプログラマに回す。
- 対処を先送りにする。
プログラマがどの対応を選ぶかは、彼または彼女の置かれた状況によって異なる。けれどたいていのプログラマは、それがどんなバグであれ、基本的には修正したいと考える。修正以外の道を選ぶとすれば、プログラマ自身の心情よりも周辺事情(迫る期日、労力の不足、...)がそれを許さないからであることが多い。
だから諸事情がそれを許せば、プログラマは 1. を選ぶ。そして、1. を選んだ瞬間から、デバッグが始まる。
デバッグの基本
デバッグには 2 つの側面がある。それは (a) バグを見つけること、(b) それを修正すること、の 2 つだ(→ 「Code Craft」p.206)。そしてデバッグの真髄は (a) にある。バグを見つけることさえできれば、修正は単純であることが多いからだ(そうでない場合、完全に修正するには設計変更をともなうことになり、それはもはや修正と呼ぶの域を超える)。
また、デバッグ作業を段階に分解すると以下のようになる(→ 「Code Craft」p.209-212 を参照)。
- 障害を認識する
- 障害を再現する
- 欠陥の所在を突き止める
- 問題を理解する
- テストを作成する
- 欠陥を修正する
- 修正できたことを立証する。
前半の 3 つが先の (a) に、残りが (b) に相当する。
同じことが「Code Complete」(下巻 p.96) では次のように書かれている。
- エラーを安定させる(確実に再現させる)。
- エラーの原因を特定する。
- 欠陥を修正する。
- 修正をテストする。
- 同様のエラーを探す。
多少の表現の違いはあるが、どちらも同じことを主張している。つまり、デバッグの段階は、(1) 再現可能な環境を構築すること、(2) その中でバグの原因となるコードを特定すること、(3) 修正を行うこと、最後に (4) 検証を行うこと、となる。
今回は、この中でも (2) の原因を特定するための方法について考えてみる。
ウェブアプリのデバッグ
バグの原因となっているコードの場所を突き止める場合、一番確実な方法は print 文などによって、おかしなことが起きる場所をしぼりこむことだ。
(「プログラミング作法」p.175)
プログラムが何をしているのかわからないときには、もっと多くの情報を表示させる文を追加するのが一番手軽で効率の良い手段となり得る。
ウェブアプリのようにコンソールを持たないプログラムの場合、プログラムに print 文を埋め込む方法は使えない(あるいは使うべきではない)。その代わりの手段がログだ。
ログを取る
GAE アプリでは、Python の標準ライブラリにあるロギングモジュールを使い、ログを記録することができる。
記録されたログは、ローカルの開発環境で実行している場合は GAE Launcher からアプリごとのログを表示させれば見ることができる。GAE の本番環境では管理コンソールからアクセスできるとのこと。
ログはプログラムの動作を知る上で何よりも重宝する。一方で、大量のログはアプリのパフォーマンスに影響を及ぼす可能性があるし、ストレージ等のリソースも消費する(GAE の場合はどうなんだろう?)。また、多すぎれば見る方にも負担になる。デバッグ用に一時的に埋め込んだログ出力は、デバッグ(と修正の検証)が終わったら、本番環境に配備する前に取り除く方が良い。
いちばん大事なこと
実は、デバッグにおいて何よりも忘れてはならない手法がある。それは記録を取ること。
これは「プログラミング作法」の p.178 でも「手がかりのない困難なバグ」に対処する方法の 1 つとして紹介されている。「Code Complete」下巻 p.102 でも、バグを見つけるためのヒントとして「メモ帳を用意して、試してみることをリストアップする」ことが挙げられている。
デバッグの作業中に、仮説を立てているのであれ、分割統治法でバグの範囲をしぼりこんでいるのであれ、あるいは同じ間違いを探しているときであれ、自分の記憶を信頼しすぎてはいけない。記憶は平気で嘘をつく。そもそもバグの大半は、ちょっとした見落しや、記憶違いから入り込むのだ。バグを潰す作業で同じことをやっていては、同じ間違いを見逃したり、別の間違いを潜り込ませることになりかねない。
あらかじめ作業リストを作って消していく方式でも良いし、思いついたことやったことを簡単にメモするだけでも良い。不確実な記憶に頼らず、確実な記録を残すこと。
参考文献
第5章がデバッグについての章になっている。取り上げられているバグの例は、(他の 2 冊とくらべて)具体的で読んでいておもしろい。ただし、載せられているコードの断片はいずれも C のものばかりなので、Ruby や Python でのデバッグにそのまま活かせるものではない。
この「コード・コンプリート」は「プログラミング作法」でデバッグに関する参考文献として挙げられている。第23章(下巻に収められている)がデバッグについての章になっている。ここに挙げた 3 冊のうちで「バグを見つける」ことに関する具体的な手順については、これがもっとも詳しい。
第9章「謝りの検出」がデバッグについての章だ。3 冊のうちでは、バグおよびその原因の分類について詳しい。
関連リンク
- アプリケーションのイベントのログ (GAE 公式ドキュメントより)
- 14.5 logging -- Python 用ロギング機能 (Python ライブラリリファレンス; PyJUG)
- library logger (Ruby リファレンスマニュアル Ruby 1.9.2 版 より)
0 件のコメント:
コメントを投稿