デバッグがつらい人に心がけてほしいこと
この記事はmast Advent Calendar 2020 8日目の記事です。
7日目はsuiくんの「オンラインでのチーム開発」でした。この記事も開発系なのでぜひ読んでみてください。
今回はデバッグの時に心がけておくといいこと、逆にやめたほうがいいことについてお話します。ただし銀の弾丸のようなツールや裏技の紹介ではなく、かつ心がけているといいことが起こるくらい程度のことを書いていますので、その点はご留意ください。
はじめに
私は筑波大学の組み込み技術キャンパスOJT(通称:COJT)のハードウェアコースでFPGAを用いた回路設計を行ったり、アルバイトで自己位置姿勢推定の開発を行ったり、趣味で低レイヤの開発を行っています。
30日OS本を参考に実装したOS
↑カメラから送られてきた信号を画像信号に変換して、それを画面に表示する回路を使った録画機能
この記事をご覧になっている皆さんも趣味や課題でプログラムの実装をすると思うのですが、何よりデバッグが嫌いだという人が多いかと思います。デバッグは時間がかかりますし、何をどのようにすればいいのかが分からない、だからプログラミングは嫌い・苦手だという人もいるかもしれません。
私もデバッグは苦手だったのですが、「デバッグルール 9つの原則、54のルール」という本を読み、そこに書いてあることを実践していくうちに、だんだんとデバッグがすいすい終わるようになりました。
今回はその内容と、曲がりなりにもエンジニアとして活動して得られた知見を例を交えながら書きたいと思いますが、中には別のやり方のほうがうまくいくぞというものもあるかもしれませんので、参考程度に読んでいただければと思います。また、本の内容を自分なりに解釈して書いているので、実際の内容と異なる部分がある場合があります。
デバッグのための行動
デバッグの時に心がけておくといいことは主に5つあります。9つの原則が書かれているものを、強引に5つにまとめました。
理解する
理解するべきこととして、
- 何が起こっているのか
- 何が正しいのか
の2つがあり、これはつまり現在位置と進行方向、着地点を把握しようということになります。
まず何が起こっているのかについて、これに限った話ではありませんが、できる限り詳細であればあるほど良いといわれています。
これは他人に状況を説明する場面を思い浮かべるとよいですが、例えば
「関数Aが動かなくなった」
では自分も、聞かれた相手も状況がつかめませんが、
「入力に0を入れたところ、関数Aがdivision by zeroで動かなくなった」
ぐらいまでわかっていると、どのようになっていてどのようにすればいいのかのおおよそのめどがつきやすくなると思います。何が起こっているのかについては、エラーメッセージや入出力、変数の値(Visual Studioなら「ローカル」で確認できます)とにらめっこしながらあぶりだしてみましょう。
次に何が正しいのかについて、これは正しい結果と正しい使い方の2つがセットになります。
そもそも今いじっている関数はどのような働きをするのか、今使っている機能は本来どのような動きをするのかを把握することで、今後のデバッグの方針が立てられるだけでなく、実は使っている関数機能が意図しているものと違っていた、ということにも気づくこともできます。
このためには設計書や言語の仕様書を読み込む必要がありますが、自分が知っていると思っていることでも必ず徹底的に読み込んでください。この後にも出てきますが、自分が知っていると思っていたことが実は思い違いだったということはよくあることで、それこそがデバッグを長引かせる原因であるということもあります。灯台下暗しなものほど残念なものはありませんので、この点はぜひ注意していただきたいと思います。
観察する
問題を観察するということは、簡単なようでとても難しいことだと個人的に思っています。
その観察ですが、まずは問題を再現することから始めてみましょう。そのためにはどういう状況で起こったのかを詳細に記録する必要があります。
問題の再現ができれば、その時の状況(例えば入出力や実行時間)をもとにデバッグを進めることができます。ごくまれに再現性のないバグ、つまりたまにしか起きないバグという非常に厄介なものが起きることがありますが、その時でも状況を記録したものは捨てずにとっておくと後の手助けになります。
問題の再現ができたら、次は問題の予測をしながら観察を行いましょう。ただし、ここで注意をしてほしいのが、
- あてずっぽうで入力を変えて観察をしない
- 問題の結果を見るのではなく、問題を見る
ということです。
あてずっぽうであれこれパラメータを変えるということは本来の観察の意図と真反対の行為です。ここでの観察はあくまで問題を絞り込むために行うことなので、それを妨げるようなことはしないようにしましょう。
次の問題を見るという部分は、哲学的な言い回しなのですが非常に大事なことです。
たとえば、デスクの電球が急につかなくなったとします。この時、デスクの電球がつかなくなったということを問題として考えてしまいがちですが、これはあくまで問題の結果であって、問題は電球が寿命を迎えただとか、コンセントからプラグが抜けてしまっていたということになります。問題の結果から考えられる原因は複数あることが多いので、取り違えてしまうと原因究明に非常に時間がかかってしまうことがわかると思います。
分割する
バグというのは今見ている対象すべてではなく、その中の一部分が原因であることがほとんどです。つまり、バグであるところとバグでないところをきっちりと分けないと、正常に動いているところまで壊してしまうということがあり得ます。
分割を行う際には、まず問題のある所から始めましょう。問題の上流(例えばプログラムの大元となるmain関数)から始めてしまうと、問題に関係のないところまで手を出してしまう危険性があるため、無理せず問題があるとわかったところから行うのが良いです。
そして今見ているところがバグに関係あるかどうかを調べます。その判断材料として、今まで記録してきたデータや、目立つテストパターンを使った出力結果を使うととても分かりやすくなります。
いわゆるコーナーケースというもので、数値の中に外れ値を入れたものであったり、本来の仕様とは異なる値をいれるというものがありますが、これについてはそのバグに応じていろいろ考えてみましょう。
一度に一つだけ変更する
一度に複数の場所を変更してしまうということはしばしばやってしまうことだと思います。しかし、一度に複数個所を変更してしまうと、具体的にどの部分がバグに直結しているのかがわからなくなってしまいます。
仮にバグが治ったと思っていても、実は別のバグを組み込んでしまっていたり、自分の用意したテストケースでうまくいっていただけで、実はまだバグが治っていなかったりすることもあります。複数箇所を変更してしまうと、そのようなことが起こった時にどこが原因であるのかがわかりにくくなってしまいます。
記録する
記録はデバッグにおいて非常に重要な手がかりになります。ちなみに、ここでの記録というのは実行結果だけでなく、実行時の状況やコードの変更点も指します。
なるべく詳細に、かつどの順番で実行してどのような結果になったのかを記録すると、あとでその状況を再現することもできます。例えば先ほど出てきた、
「入力に0を入れたところ、関数Aがdivision by zeroで動かなくなった」
は実行時の状況、順番、結果が書かれており、あとで再現することもできます。また、division by zeroを起こしたのはAの中の一部分の処理になるはずですので、ここまで書いておけばセーブポイント的なものとして今後のデバッグに使用することもできます。
やめたほうがいいこと
ここまで、デバッグの際に心がけておくといいことを述べてきましたが、逆にデバッグにおいてやめたほうがいいこともあります。
決めつける
信念は嘘よりも危険な真理の敵であるという言葉があります(本からそのまま引っ張ってきました)。理解するの終わりの部分に関連しますが、いかにくだらないことでも自分の憶測は絶対に信じずに、問題は基本的で常識的なところで起こりうるという認識を持ちましょう。正しい情報は常にドキュメントにあります。
壊す
不必要なところまで手を出してしまったり、すべてが嫌になって大部分を消してしまったりすることは避けましょう。正常な部分まで手を出してしまうと、さらにデバッグに必要な時間が増える負のスパイラルに陥ってしまいます。
なんだかやめたほうがいいことの分量が少なくないか?と思われるかもしれませんが、おおよそこれらに集約されると個人的に思っていますし、デバッグに時間がかかってしまったなというときは大体この2つが守れていなかったと思っています。
最後に
ここまで読まれた方は、ごくごく当たり前なことしか書いてないじゃないかと思われるかもしれませんが、実際の書籍にもほとんど同じようなことが書かれており、逆にいえば当たり前だと思うことを守るだけで、少しはデバッグの速度が上がるということになります。
この記事をご覧になっている方は、おそらく筑波大学生が多いかと思われますので少しだけ宣伝をしますと、冒頭で触れた組み込み技術キャンパスOJTは、大学の講義の扱いですがエンジニアリングについて非常に様々なことが学べる良いプログラムです。筑波大学の3年次の人であれば受講することが可能です!
興味のある方は質問も受け付けています。
Discussion