2009-03-22
■ わたしがprintf()デバッグをしない理由

プログラマという職業について、もう25年くらいになるのであるが、その間にコンピュータのコストパフォーマンスは、それこそムーアの法則に従って、10万倍〜100万倍くらい向上した。にもかかわらづ、デバッグの方法というものの劇的な変化はほとんどみられない。
プログラミング入門書では、デバッグについて、ほとんど議論されていないし、仮にふれられていても、おざなりな方法というか、かなり邪険にあつかわれていたりする。プログラマの多くの時間がデバッグについやされていたとしてもだ。
たまたま手元にあった、C実践プログラミング(ISBN4-900900-64-8)という10年くらい前に買った参考書では、450ページのうちデバッガの利用については、4行ほど記述がある。たった4行である。診断用のprintf()を挿入するということは3ページにわたって記述されているのにだ。
流石に21世紀になってprintf()デバッグもなかろうと思うのだが、綺麗なIDEで、ブレークポイントを設定し、華麗にデバッグをしている姿というのが、どれくらい一般的かはわからないが、printf()デバッグはいかがなものかと思う。
新卒で入社したところでは、先輩がデバッグのイロハを教えてくれたので、printf()デバッグの悪弊にそまることはなかったのだが、なんで、printf()デバッグがよろしくないのか考えてみた。
確かにprintf()はお手軽である。変数の値を表示するのにこれほどお手軽な事はない。Cの教科書の最初のサンプルは'Hello World'である。それが、そもそもの間違いのような気もしないでもないが。
仕事で作ってきたものは、わたしの場合、ソフトウェア製品(コンパイラ、データベース管理システム、OSなど)なので、ちょっとデバッグするのにprint文を埋めてみようということには、ならなかったという事情があった。
ビルドに当時のマシンで数10分もかかるとなると、お気楽にそのような事はできない。デバッガを使うのが基本中の基本である。昨今のスクリプト言語全盛の場合、そもそもビルドなんていうことは初めから必要ないので、printして様子を見て、試行錯誤して、理解を深めるというプロセスも、なんとなくわからないでもないが、旧来型のビルド、テスト、デバッグというプロセスではビルドのコストがいかんせん高すぎる。
ハードウェアが高速化したんだから、ビルドなんていうのはmake一発だろう、機械力を使えよという話になるのであるが、ハードウェアが高速化した分、ソフトウェアも複雑化し、結果として肥大化してビルド時間もそれに比例してかかるようになって、やはり巨大ソフトウェアのビルド時間は、ほぼ一定という皮肉な結果になっている。
Linuxなんかもモジュールやドライバーをテンコ盛にすると、平気で、何10分もビルドにかかってしまう。
ということで、使いすてのプログラムや小規模なプログラムならまだしも、ちょっとした規模のものや、商用ソフトウェアの場合、printデバッグというのはお勧めできない。というかありえない。
それでも、kernelのように便利なデバッガというのが簡単に手にはいらない場合、どうしても要所要所にprintkなるものを埋め込んでデバッグすることもあるが、アプリケーションの場合は、便利なデバッガはあるので、それを使わない手はない。というか使うだろう、フツーは。
学生のころは、デバッグの方法というかスタイルも実に場当たり的で、適当にprint文を埋め込んで状態を確認し、テストをし、試行錯誤しているうちに、どれがデバッグ版で、どれが正式版かわけがわからなくなって、デバッグしているんだかバグをせっせと作り込んでいるのだか、何をやっているか、わからないということが良くあった。
デバッグの方法がデバッガを使うことが基本になると、どこでブレークポイントを設定するのか、どの変数の値を確認するのか、というスタイルになってきて、もうちっとシステマティックになってくるような気がする。すくなくとも、わけのわからないデバッグ文を埋め込んだバージョンを管理するという、まったく非生産的な、いたずらに問題を複雑化することはしていない分だけでも、デバッグに専念できる。
デバッグ対象のプログラムを正しく理解するためには、そのプログラムは変更してはいけないのである。動作を虚心坦懐に理解するために、1バイトたりともいじってはいけないのである。そして、そのためにデバッガを使えということになる。
アプリケーションプログラムの場合は、それでいけるのであるが、幸いなことにカーネルの分野においても昨今ではkprobe/jprobe/systemtapなどのプローブ用ツールが進歩したおかげで、printk()にたよることなく、ある程度まであたりをつけることが可能になってきた。また、oprofileなどのプロファイリングツールによってソースコードを変更することなくプロファイリングデータの計測が手軽に出来るようになってきている。
デバッグやテストをするときには、ソースコードを変更してはいけない。デバッグをするためにprint文を埋めこむなどというのは以てのほかである。(1)ビルドのコストがかかる。(2)デバッグ版と正式版という複数のものを管理するコストがかかる。問題をいたづらに複雑化している。(3)デバッガなどの機械力を使うのが正しい姿である。
まあ、こーゆーことを書くと、デバッガがない環境があるとか、タイミング的な問題はデバッガではおえないとか言う人が出てくるのであるが、そーゆー環境の場合でも、ソースコードを変更しないでデバッグする方法を考えるなり、構築するのが正しいアプローチだと思う。
- 各言語のデバッガ
- src’s note - 気になる技術メモ
- Text::Easyhacking - わたしがprintf()デバッグをする理由
- Yet Another But Open
- 未来のいつか/hyoshiokの日記 - デバッグ方法論
- デバッグより重要なもの
- yasuhoの隠れ家 - 名探偵プログラマ
- watawata日記 - バグとどうつきあっていくか
- デバッグは大変ね(棒読み)
- 糸且之入Eヨ言己 - デバッグとデバッガ
- iakioの日記 - printf()を書くかわりに.gdbinitを書く
- 未来のいつか/hyoshiokの日記 - Debug Hacks
- TrickDiary - `Д´)ノ バグベアード抜きで printf デバッグを語...
- 新日々此何有哉 - printfデバッグ
- Yutaka Hirata blog - printfデバッグ
- Yet Another But Open - メモメモ
- tsucchiの日記 - オイラが printf()デバッグをする理由、またはオイ...
- おろかな日々 - 「どうやって」デバッグするか
- YAMDAS現更新履歴 - 『Debug Hacks』こそオライリー・ジャパンから...
- interleaveの日記 - unittestはprintf()デバッグに含まれる?

>デバッグの方法というものの劇的な変化はほとんどみられない
機械力は10万倍〜100万倍くらい向上しても、人間力はほとんど変わらない(というか、次第に衰えてきている)のでしょう。今時マシン語を理解できる人は少ないですし。
>すくなくとも、わけのわからないデバッグ文を埋め込んだバージョンを管理する
それは管理の仕方が拙いと思います。まともなデバッグ文であれば、 #define や CONFIG_DEBUG_ や /proc/ や設定ファイル等で ON/OFF できるようになっていると思います。デバッグ文はプログラマのためだけにあるのではないと思います。ユーザ(システム管理者)だって詳細なログを得たい場合があるでしょう。
>ちょっとした規模のものや、商用ソフトウェアの場合、printデバッグというのはお勧めできない。というかありえない。
というか、デバッグレベルを動的に変更できないのはありえないと思います。容量が限られていて1バイトでも節約したい場合以外はデバッグ文を残したまま使う気がします。
>kprobe/jprobe/systemtap/oprofile
使い方に関する情報が全く足りません。(^^;
「ツールの情報がない→ツールの使い方がわからない→printk()とBUG_ON()でいいや→ツールの情報がない」のループです。
デバッグする必要はもう無い。人間がソースコードを改変すること自体が最大のバグの温床だね。
ふつうはDEBUG_LEVELとかLOG_LEVELなどの外部変数でどのレベルでどんなログを出すのかあらかじめ設計しておくのではないでしょうか?
人間は、そんなに賢くならないので、積極的に機械力を利用したデバッグ方法論を編み出したいものです。いまだにアルゴリズミックデバッギング(デバッグの半自動化)が実用化されていないというのが、不思議というか謎というか。
アドホックなprintデバッグを戒めているわけで、ユーザーレベルのログの有用性を否定はしていないです。ソフトウェア製品の機能として、ログをとったり、ダンプをとったりするというのは、当然必要な時がある。大規模ソフトウェアでは、自己診断機能は実装されていたりしますし。
各種ツールのマニュアルはそこそこ充実していますよ。(kprobe/jprobe/systemtap/oprofileなど) 仮に情報が足りないからと言って利用しないというのは、プロフェッショナルとしてはいかがなものかなと。
.さん、コメントありがとうございます。dtrace的なトレースツールは今後の発展に期待します。
YANさん、コメントありがとうございます。ログの出力はその通りですね。
ソフトウェアの実装における、デバッグ容易性という概念の議論が必要かなと思いました。ちょっと、考えてみます。皆さんどうもありがとうございました。
唯一、「C言語 入門書の次に読む本(ISBN-10: 4774117978 )」で見かけたなぁと開いてみたら29ページほどデバッグの話が載ってました。
amazonnで同じ作者の本を探したら「C言語 デバッグ完全解説(ISBN-10: 4774133620 )」なんて本までありました。
私はCは良く知らないVBプログラマなのですが、VBの本でもあまりデバッグに関する本は見かけないですね。
このへん素直に疑問なんですが、いまどきのまともなバージョン管理システムのbranch機能は使わない前提なんですか?
だとするとまず最初に言うべきは「VCS使え。手作業でソースコード管理するな」ですよね?
まさか「VCSは信頼できない。頭の中がごちゃごちゃするから嫌いだし壊れるのが怖い」なんて人はまともなプログラマには居ないでしょうし。(linusだってVCSにこだわって使ってる)
コメントありがとうございます。別バイナリですから、一方で動いて他方で動かないというのは、ありえますね。
>yTanaka さん、
コメントありがとうございます。デバッグの入門書、ベストプラクティスが共有されていないのは問題かなと思います。
>otsuneさん、
コメントありがとうございます。バージョン管理システムだけではなく、デイリービルド、リグレッションテストくらいは最低限必要かと思います。
その前提の上で複数のブランチを持てば、テストの工数は倍になるし、バージョン管理システムを使ったところで、そのコストは減らないですね。複数のバイナリを管理するコストは、それによって得られるメリットより高いと考えます。printf()デバッグで出きることはデバッガーで出来ますので、メリットはほとんどないです。
それ以外はIDEとかのデバッグ機能を使用します。
当日記はときどき読ませていただいております。
デバッグに関する書籍が無いことは同様になんとかならんものかと考えています。というより、初心者が文法事項などをマスターして実際にガリガリ書くようになってから、開発現場で実際に出る問題を解決するための読む書籍が無いと思っています(「デバッグ完全解説」は、そのような思いから書きました)。
デバッガの利用に関しては、デバッガに関する書籍が少ないため、初心者を脱皮したくらいのひとがデバッガを使おうと思う「気づき」の機会が少ないことがひとつの原因かなとも思っています。最近オライリーからデバッガの本が出ていましたが、リファレンス的な説明も多く、もっと実地的というか、実際のバグをデバッガで追いかけていく過程を説明するような本があればいいなあとも思います。