Skip to content

デバッグで行き詰まった時、状況を打開する5つの手段

やあshi3zだ。
ここんとこ絶望的に忙しくてwise9の更新がちょっと滞ってしまった。

ところでみんな、デバッグしてるかな?
まあデバッグっていやだよね。
知らない人のために説明すると、デバッグというのは、プログラムに潜む誤り(バグ)を修正すること。これがけっこう厄介なんだ。

身近なところで若いプログラマーがデバッグに行き詰まって「Oh!No!」と悲鳴を上げていたので久しぶりにデバッグを手伝ってみたんだけど、意外とみんなデバッグの方法を知らないんだなと思った。

デバッグに行き詰まった時にどうすればいいか、意外とそういう時の方法って知られてないよね?

そこでデバッグに行き詰まった時に状況を打開するにはどうすればいいか、簡単に手順をまとめてみたぞ。

1.基本に立ち戻る

 デバッグに行き詰まったら、まず落ち着いて基本に立ち戻ってみよう。
 そもそもそのコードはなにをどのようにしたいのか、そしてどのようにおかしくなっているのかを正確に把握する必要がある。

 こういうときは、意外とprintfデバッグと呼ばれる最も愚直な方法が有効だ。要はプログラムの要所要所でログを吐き出し、「この処理を通った」ことを確認する手段だ。

 特に最近はスクリプト言語が多用されるようになったので、コンシューマゲーム機や組み込み向けソフトみたいにブレークポイントやステップ実行、ウォッチ式は使えないこともある。

 printfデバッグは、要するに「ここまで来た」ということを確認したり、「この時点の数値」を表示したりしてプログラムの動作を把握するやり方だ。

 ステップ実行とウォッチ式よりは手間がかかるが、余計なものが表示されないので却って便利な場合もある。

 JavaScriptならconsole.logでデバッグログを吐くことが出来る。

2.バグ状況の再現方法を発見する

 あるバグが発生するとき、どんな手順で発生するのか、その再現方法を見つけ出すのはデバッグにとって最も重要な手がかりとなる。

 しかも、同じバグを見つける時の再現方法が複数通りあるのか、それとも一通りしかないのかを見つけ出す必要がある。

 また、似た手順でも他のバグが出てくる可能性もある。
 複数のバグをひとつのバグと勘違いする可能性だ。

 そこで、まずバグをとるということを一旦はひとつの手順に集中する。
 集中すべき手順は、できるだけ短く再現できるもので、しかもなるべく多くの手順で再現されるようなバグがいい。

 こうしたバグをとると、連鎖的に複数のバグがとれることがある。

 このバグの切り分けが重要だ。
 テスト担当者やプログラマー以外のスタッフが手伝えるのは主にこの「バグの発見と切り分け」という領域である。

 ただし、プログラマーでなかったとしても、プログラムの基本的な構造がどんなものなのかある程度想像しながらテストするのが望ましい。

 状況を切り分けると、ソースのどの部分を読むべきかの手がかりになる。

3.データか、アルゴリズムか切り分ける

大規模なプログラムには大きく分けて三つのバグがある。
アルゴリズムのバグと、データのバグ、リソースのバグだ。

アルゴリズムのバグの場合、アルゴリズムだけを取り出して、より小さなプログラムを書き、アルゴリズムだけのデバッグを行うのが最も効率がいい。

アルゴリズムに切り分けが可能な場合、後述するペアプログラミングにも向いている。

データのバグとは、いつのまにか異常なデータが入っていて、それによって誤作動するという複合的な原因によるバグである。これも広義にはアルゴリズムのバグと言えなくもないが、データにアクセスする方法が複数用意されていて、アルゴリズム単体の切り分けが難しい場合は、データのバグと呼ぶことにする。

異常なデータがとにかく入ってくる場合、当然だがその異常なデータにアクセスしている部分の全てを疑う必要がある。

とりわけ怪しいのは書き込みをしている部分だ。
書き込みをしてる値のアルゴリズムが間違っているという可能性が高い。

アルゴリズムを図に書き出して怪しいところに当たりをつけるといい。

このバグは厄介で、たいていはものすごく些細なミスだ。
ダイナミックなミスというのは設計ミスしか存在しない。そしてしばしば、設計ミスを疑う必要がある場合がある。設計ミスは経験の少ないプログラマーがよく犯す間違いだ。

もうひとつ、リソースのバグの代表例はメモリリークだ。
複雑なプログラムからメモリリークを完全に排除するのはとても難しい。どこで漏れてるかわからないからだ。自転車のパンク修理とは違う。

メモリリークを排除するには、メモリリークしないようにリソースの管理を慎重に行うしかない。特に最近のガベージコレクション全盛時代は、メモリリークに関する知識が不十分なプログラマーも少なくなく、発見が難しくなっている。

昔はmallocとfreeを追い掛ければたいていのメモリリークは発見できたが、今はfreeなんて呼ばない(そんな命令もない)。余計なオブジェクトを配列やオブジェクトが参照していないか慎重に追い掛ける必要がある。これはしばしばMicrosoftやAppleといった偉大な企業さえ頭を抱えるバグだ。腰を据えて探さなければならない。

4.ペアプログラミングとコードレビュー

プログラミング。この世界の全てを支配する神は、ロジックだ。
ロジックに貴賎はない。ロジックの神のもと、人は皆平等なのだ。

それは創造主たるプログラマーも例外ではない。あなたが書いたコードはあなたの子供のようなもので、あなたは子供の欠点を見つけることができない場合があることを知るべきだ。

そういうことを指摘してくれるのは、第三者、つまり他のプログラマーだ。岡目八目、と言う言葉もある。一度全くそのコードに関わりのない第三者に相談すると、思わぬ問題解決の糸口が見つかることはとても多い。

行き詰まったらあなたより経験豊富で賢くてイケてるしヤバい年長のプログラマーを探そう。自分よりプログラミング歴が長ければ年長でなくてもいい。


その人にまずコードを見せる。コードレビューというやつだ。
コードレビューはただコードを見せるだけではなく、コードをモジュールごとに説明し、このプログラムの全体がどういう構造で、なにをしようとしているのかを説明する。

プロジェクターを使ってコードレビュアーを複数連れてくるのもいい。
コードレビューの目的は、説明する側が説明するという行動を通してコードの動きを明確化することと、レビューする側がコードの構造を聞いて論理的に怪しい部分や経験上、複雑なバグが入り込む原因になる部分などを切り分けることにある。

コードレビューのあと、実際のコードを見てもらうと、スムーズにコードの動きを把握してもらうことができる。

どうしても無理ということになったら、他の経験豊富なプログラマーとペアプログラミングでデバッグをつき合ってもらうしかない。

経験豊富なプログラマーはあらゆる経験則を知っている。ロジックの神のもと平等な世界であっても、経験値は圧倒的な意味を持つ。

僕はずっと以前、Microsoftの仕事をしていた頃、顧問プログラマーをしていたことがある。そんな肩書きではなかったが、やっていた仕事はまさにそんな感じだった。

ある会社が「このコードが思うように動かない。パフォーマンスが出ない」とソースコードをまるごと送ってくる。

僕はそのコードをまずコンパイルして実行し、なるほど向こうの主張するようなことが起きてることを把握する。それからコードを一時間ほどじっと読む。それが終わると、僕はキーをタイプする。たいていの場合はスラッシュ二回打つだけだ。それから実行して、パフォーマンスが100倍になってることを確認して先方に送り返す、というわけだ。

またあるときは、OS内部のあるモジュールがメモリリークを起こしていることを突き止め、レドモンドに電話をかける。一眠りして、起きると修正されたOSが送られて来ている。

この差は、単に経験値だ。パフォーマンスを出そうとするとき障害になるのは何か、メモリリークが起きるのはどんな時か、そういう時の経験が全部頭の中に入っている。あとは自分の頭の中にあるパターンと今目の前にあるコードのパターンを照合して、あてはめるだけだ。経験がなければ1ヶ月かかることが、1時間で解決するような例はいくらでもある。

この経験を思い出したのは、昨日まさにスラッシュ二回打ってバグを回避したからだ。バグを回避しただけでなく全体のパフォーマンスも向上した。不要な関数呼び出しを見つけるのは最も効果的なデバッグだ。

若いプログラマーは、行き詰まったら年長者にアドバイスを求めるべきだし、プログラマーが行き詰まったところを見たプロジェクトマネージャは、すぐに経験豊富なプログラマーを連れて来てコードレビューとペアプログラミングをさせるべきだ。

理屈では解っているのになかなかコードレビューとペアプログラミングをしようとするプロマネは少ない。これは作業を担当しているプログラマーにとっても、顧客にとっても、もちろんプロマネにとっても不幸なことだ。

5.寝よう

ペアプログラミングまでやってもなかなか成果が出ない。
そんなときはもう寝るしかない。足りないのはおそらく休養である。
そして寝ながらデバッグするのだ。

それを「ゲームクリエイター列伝」では「ポップアップ」と呼んでいる。
寝ると何がいいのか?

人間の脳は、睡眠をとったときに初めて海馬から大脳新皮質にデータをセーブする。
海馬はいわばコンピュータのヒープメモリーで、大脳新皮質はハードディスクやSSDと言える。

この際に夢を見ると言われている。

これは僕の想像で、全く医学的な根拠はないけれども、おそらく海馬から大脳新皮質にデータが以降する最中にその記憶は記号化という圧縮を受けるのではないかと思う。その方が遥かに沢山の情報を大脳新皮質に記憶できるから。記号化というのは、「言葉」や「論理」として整理した上で大脳新皮質に送り込むということ。もちろん多少の感覚的要素、香りや質感といったものもセーブされるはずだが、一度大脳新皮質にセーブされた質感をハッキリと思い出すことは困難。それはそういう情報を削ぎ落して大脳新皮質にセーブするからではないだろうか。

「さっきまでの感触」はすぐに思い出せるけど、「昨日の感触」を思い出すのは難しいし、「10年前の感触」を思い出すのはもっと難しい。けれども「10年前の同級生の名前」は記号化されているので思い出せる。でもその同級生の顔は記号化されていないので思い出しにくい、まあそんなイメージかな。

セーブする際に記号化されるのは、いわばzipで圧縮するのと同じとも言える。
そして記号化された記憶が便利なのは、記号化されたことによってより記号の処理、つまり論理の処理に都合が良くなるのだ。

徹底的にコードの構造を把握し、コードを追いかけ、コードレビューとペアプログラミングでコードの怪しい部分もなんとなく掴めて来た時、起きているよりもむしろ寝ているときの方が脳の情報が整理されて突然、雷が落ちるようにバグの原因個所が掴めることはよくある。

「掴めた!」と思ったら起きてしまう。
そしてとにかく枕元の携帯から自分にメールするとか、メモを残すといい。

起きれそうだったら起きてしまって、そのままデバッグに戻るのがいい。
万が一勘違いだったら、まあ落胆はするだろうけど、そのままデバッグを続けよう。どちらにせよ落雷が落ちたと言うのは、コードの構造に関してなにか重要なヒントを掴んだということなので、以前とは違う視点でコードを見れるはず。そこで思わぬ発見をすることはよくあること。

起きてしばらくデバッグを続けても行き詰まったら、もう寝てしまうしかない。
睡眠が足りないのだ。

以上、本当に行き詰まったときのための5つの手段を書いてみた。
特にペアプログラミングは有効性が再三指摘されているにも関わらず、導入している会社は日本ではまだまだ少ないのでプロマネの方はペアプログラミング用に経験豊富なプログラマーと常日頃から良好な関係を構築しておくのがオススメ。

では快適なデバッグを

このエントリーをはてなブックマークに追加
はてなブックマーク - デバッグで行き詰まった時、状況を打開する5つの手段
Post to Google Buzz
Share on GREE

No related posts.

Facebook comments:

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*