未来のいつか/hyoshiokの日記 このページをアンテナに追加 RSSフィード Twitter

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)デバッガなどの機械力を使うのが正しい姿である。

まあ、こーゆーことを書くと、デバッガがない環境があるとか、タイミング的な問題はデバッガではおえないとか言う人が出てくるのであるが、そーゆー環境の場合でも、ソースコードを変更しないでデバッグする方法を考えるなり、構築するのが正しいアプローチだと思う。

PANDAPANDA 2009/03/22 19:26 Debug Hacks ?

>デバッグの方法というものの劇的な変化はほとんどみられない
機械力は10万倍〜100万倍くらい向上しても、人間力はほとんど変わらない(というか、次第に衰えてきている)のでしょう。今時マシン語を理解できる人は少ないですし。

>すくなくとも、わけのわからないデバッグ文を埋め込んだバージョンを管理する
それは管理の仕方が拙いと思います。まともなデバッグ文であれば、 #define や CONFIG_DEBUG_ や /proc/ や設定ファイル等で ON/OFF できるようになっていると思います。デバッグ文はプログラマのためだけにあるのではないと思います。ユーザ(システム管理者)だって詳細なログを得たい場合があるでしょう。

>ちょっとした規模のものや、商用ソフトウェアの場合、printデバッグというのはお勧めできない。というかありえない。
というか、デバッグレベルを動的に変更できないのはありえないと思います。容量が限られていて1バイトでも節約したい場合以外はデバッグ文を残したまま使う気がします。

>kprobe/jprobe/systemtap/oprofile
使い方に関する情報が全く足りません。(^^;
「ツールの情報がない→ツールの使い方がわからない→printk()とBUG_ON()でいいや→ツールの情報がない」のループです。

.. 2009/03/22 20:21 モニタリングするだけなら dtrace に頼ればいいと思うよ。
デバッグする必要はもう無い。人間がソースコードを改変すること自体が最大のバグの温床だね。

YANYAN 2009/03/22 22:40 PANDAさんも書いていますが
ふつうはDEBUG_LEVELとかLOG_LEVELなどの外部変数でどのレベルでどんなログを出すのかあらかじめ設計しておくのではないでしょうか?

hyoshiokhyoshiok 2009/03/23 07:39 PANDAさん、コメントありがとうございます。
人間は、そんなに賢くならないので、積極的に機械力を利用したデバッグ方法論を編み出したいものです。いまだにアルゴリズミックデバッギング(デバッグの半自動化)が実用化されていないというのが、不思議というか謎というか。
アドホックなprintデバッグを戒めているわけで、ユーザーレベルのログの有用性を否定はしていないです。ソフトウェア製品の機能として、ログをとったり、ダンプをとったりするというのは、当然必要な時がある。大規模ソフトウェアでは、自己診断機能は実装されていたりしますし。
各種ツールのマニュアルはそこそこ充実していますよ。(kprobe/jprobe/systemtap/oprofileなど) 仮に情報が足りないからと言って利用しないというのは、プロフェッショナルとしてはいかがなものかなと。
.さん、コメントありがとうございます。dtrace的なトレースツールは今後の発展に期待します。
YANさん、コメントありがとうございます。ログの出力はその通りですね。
ソフトウェアの実装における、デバッグ容易性という概念の議論が必要かなと思いました。ちょっと、考えてみます。皆さんどうもありがとうございました。

taraijpntaraijpn 2009/03/23 17:00 「デバッグ用のprintf()を入れると正しく動くのに、抜くとなぜか動かなくなる」という話を聞いたことがあります。必要なのは最終行の出力結果だけなので、何が起きてるのか分からないからとりあえず入れっぱなしで計算させてみた、なんて対処をしていたみたいですが、今にして思えばそれもまたひとつの弊害?だったのかも知れません。

yTanakayTanaka 2009/03/23 21:44 確かにデバッグに関する本はあまり見かけませんね。
唯一、「C言語 入門書の次に読む本(ISBN-10: 4774117978 )」で見かけたなぁと開いてみたら29ページほどデバッグの話が載ってました。
amazonnで同じ作者の本を探したら「C言語 デバッグ完全解説(ISBN-10: 4774133620 )」なんて本までありました。
私はCは良く知らないVBプログラマなのですが、VBの本でもあまりデバッグに関する本は見かけないですね。

otsuneotsune 2009/03/24 17:12 >わけのわからないデバッグ文を埋め込んだバージョンを管理するという、まったく非生産的な、いたずらに問題を複雑化することはしていない分だけでも、デバッグに専念できる。

このへん素直に疑問なんですが、いまどきのまともなバージョン管理システムのbranch機能は使わない前提なんですか?
だとするとまず最初に言うべきは「VCS使え。手作業でソースコード管理するな」ですよね?
まさか「VCSは信頼できない。頭の中がごちゃごちゃするから嫌いだし壊れるのが怖い」なんて人はまともなプログラマには居ないでしょうし。(linusだってVCSにこだわって使ってる)

hyoshiokhyoshiok 2009/03/25 07:22 >taraijpnさん、
コメントありがとうございます。別バイナリですから、一方で動いて他方で動かないというのは、ありえますね。
>yTanaka さん、
コメントありがとうございます。デバッグの入門書、ベストプラクティスが共有されていないのは問題かなと思います。
>otsuneさん、
コメントありがとうございます。バージョン管理システムだけではなく、デイリービルド、リグレッションテストくらいは最低限必要かと思います。
その前提の上で複数のブランチを持てば、テストの工数は倍になるし、バージョン管理システムを使ったところで、そのコストは減らないですね。複数のバイナリを管理するコストは、それによって得られるメリットより高いと考えます。printf()デバッグで出きることはデバッガーで出来ますので、メリットはほとんどないです。

kensir0ukensir0u 2009/03/26 09:14 私はさくっと実行できて結果がわかるもの(とりあえず動作可能なもの)はprintf()で実行します、3つまでが限界だけども。IDEとかのデバッグでチェックするよりは速いですからね。結果がエディタに残るのもいいですね。
それ以外はIDEとかのデバッグ機能を使用します。