2014年6月22日日曜日

デバッガの利用


ちょうどC#のサンプル

プログラムで

デバッガを使う部分が

あったので、ご紹介。




画像の左端の赤丸がブレイクをはっている部分です(4箇所)

今は2番目で停止しています。(黄色矢印部分が該当)

また、クイックウォッチという変数の値をチェックする機能もあります。

デバッグも簡単なので、IDEはやはり便利さを感じずにはいられません。

さて前回は、ソースプログラムを変更してデバッグする方法を紹介しました。

これは、自力でバグを見つけ出すための方法ですが、自分で学習するための

プログラムではいいかもしれませんが、実務でのプログラムになると

バグの発見やソースコードの変更が非常に大変になってきます。

そこで、ぜひ覚えておきたいのが、ツールの力を借りるということです。

デバッグを支援するツールのことを「デバッガ」といい、コンパイラと共に

プログラムの開発には欠かせないものです。

ここでは、代表的なデバッガの機能を紹介します。


ブレイクポイントの設定
デバッガを使えば、ソースプログラムの指定した位置でプログラムの動作を

停止させることができます。ブレイクポイントとは、その停止位置のことです。

ブレイクポイントを設定してプログラムを実行すると、その場所にきたときに

プログラムが停止します。

変数の表示と変更
ブレイクポイントでプログラムを一時停止した時に、そのときの変数の値を

参照することができます。要は、print文で出力するのと同じですが、

こちらの場合は、ソースプログラムを変更することなく、好きな変数の値を

表示させることができます。また、多くのデバッガでは変数の値を直接書き換え

その値でプログラムの実行を継続させることができます。

ただし、演算子をオーバーロードしているような場合は、見た目の記述どおりに

デバッガで表示できるとは限らないので注意が必要です。

ステップ実行
ソースプログラムの1行ごとに、プログラムを実行する機能です。

この機能は、プログラムが実際にどのように動作しており、どの時点で止まって

いるかを捉えることができるので、非常に役立ちます。

このとき変数の値を表示させておけば、値の移り変わりを調べることができます。

条件分岐が多いプログラムなどでは特に重宝するでしょう。


このようにデバッガを使うと、ソースプログラムを変更するよりはるかに

簡単で効率的にデバッグできるようになります。

デバッガが使える環境では、積極的にデバッガを利用して

時間と労力を節約したいものです。

もし、IDEの開発環境が整っていれば、たいていコンパイラとデバッガの

両方を備えているので、デバッグしやすいかもしれません。

しかし、Linuxでコマンドラインで開発してるような場合だと、デバッガが

用意されてなかったりとか、デバッグ版のバイナリを作成するのが

手間がかかりすぎるという場合もあって、ログを仕込んで実機検証の

方が早く解決できるということもあるでしょう。

既にある環境で開発する場合が圧倒的に多いので、

それに合わせてデバッグ手法も柔軟に変えるというのがおススメです(^^;)


今日の名言
人生をあるがままに受け入れない者は、悪魔に魂を売り渡す。
                               シャルル・ボードレール

幸福になる秘訣はできるだけ多方面に関心を持つこと。そして自分が関心を
持つ人物や事柄に対しては、なるべく腹立たしい気持ちを示さずに、できるだけ
親しい気持ちで接することだ。
                               バートランド・ラッセル

心は天国をつくり出すことも、地獄をつくり出すこともできる。
                               ジョン・ミルトン

人間は、起こることよりも、起こることに対する見解によってひどく傷ついてしまう。
                               ミシェル・エケム・ド・モンテーニュ

私たちが取り組むべき唯一最大の問題は、正しい考え方を選ぶことにある。
もしこれができたら、私たちの問題にはことごとく解決の道が開けていくであろう。
                               デール・カーネギー

2014年6月10日火曜日

デバッグ手法について



プログラムが意図したとおりに動かない時は、

バグ(誤り)を発見し修正する「デバッグ」という

作業を行わなければなりません。

ちなみに、サンプルプログラムは

全く意味のないものです(^^;)


エラーの種類
言語を問わず、プログラミングをしていてまず最初に突き当たる壁が

コンパイルエラーです。プログラムがコンパイルできない原因は、

文法が間違っている、コンパイル方法が正しくない、などがありますが、

どの部分が間違っているのかはたいていコンパイラが指摘してくれます。

ただし、コンパイラが出力するエラーメッセージは、良くも悪くも

「流れ作業的」なので、ほんの一箇所間違いがあるだけでも、それ以降で

整合性が取れず、エラーメッセージが多数出力されることがあります。

エラーメッセージがたくさん表示されたとしても、間違いと関係ないものも

ありますので、どのメッセージが本質的なものなのかを見極めることも

必要となります。

また、コンパイルでエラーになっていないからといって、それが正しく

動作するかは別の話です。

一番厄介なのは、プログラム実行中の不具合です。

例えば、プログラムが途中で止まる、あるいは異常終了するとか

動作は期待通りであるが、出力結果がおかしいなど、バグの種類も

様々です。

変数 a に5を代入は 「a = 5;」 と書きます。これを間違って「a == 5;」 と

イコールを二つ書いたとしても、a と 5 が等しいかどうかを比較する式

なので、文法的には正しく、コンパイルエラーにはなりません。

しかし、a に 5 は代入されず不定値のままなので、意図した動作に

ならないプログラムの出来上がりです(-_-;)


バグの発見
プログラムのバグを発見するには、まずソースコードをトレースするのが

基本です。そのロジックが正しいのかを再度確認する必要があります。

しかし、何度も見直したけどおかしいところはない、なぜ期待した結果と

異なるのか、と意外と誤りに気付かないケースもあります。

すぐに誰かに聞くのも一つの手ですが、まずは自分で解決するための

方法を押さえておきたいところです。


・ 処理を分割する
C++では、式や文の記述が非常に柔軟なので、かなり凝ったプログラムも

作ることができます。ただ、そういったものは他人には分かりにくく保守性

が悪い、いわゆる自己満足にしかならないプログラムとなる場合が多いです。

バグの温床にもなりますので、処理や意味の単位で分割することを

考えた方がよいでしょう。短く書こうとして、あまり一行にまとめすぎると

どこでエラーが起きているかも分かりにくくなります。

演算子の優先順位も間違いやすいところです。式の意味が分かりにくいとか

優先順位がはっきりしない、などの場合は、カッコをつけたり、一度変数に

代入すると読みやすくなるものです。

ポインタや配列の演算、インクリメント演算子などを多用すると

複雑になりますので、特に注意してください。

「困難は分割せよ」です(^^)


・ print文を挿入する
コンパイルしたプログラムを実行しただけでは、バグがあることは分かっても

原因の特定は難しいでしょう。このような時は、トレーサとして cout あるいは

printf を挿入して手がかりを掴めるようにします。例えば、プログラム中で

処理を分岐しているところに、cout << "実行1" << endl; 、cout << "実行2" << endl;

などと仕込んでおけば、そこに到達した時にメッセージが出力されますので

どの条件でこちらの処理が行われたのかがわかるというわけです。

また、そこで変数の値を表示するようにしておくのも非常に有効です。

ログは本当に重要なのです(^^)


・ 関数ごとに実行する
C++の処理の単位は関数です。関数に様々な引数を与えて、戻り値を調べれば

その関数が正常に動作しているかが分かります。関数は様々な状況で

呼び出されますが、デバッグしたい部分を切り出して main関数からすぐに

実行するようなテストプログラムを作成すれば効率的です。

ここでも「困難は分割せよ」ですね(^^)


・ throw文を記述する
ある条件の時に意図的に例外を発生させることで、エラー箇所とエラー内容を

特定できるかもしれません。 例外処理であるtry, catch文でエラー内容を

取得します。


・ assertマクロを記述する
assertマクロを使用すると、特定の条件が当てはまらない時に、メッセージを

出力してプログラムを強制終了することができます。値をあらかじめ評価して

おくことで、バグの早期発見につながります。 例えば、「assert(a == 5);」と

書いておくと、a の値が 5 でなくなった時に 「assertion failed」というメッセージを

出力して強制終了します。

assertマクロを使う時は、<assert.h> のインクルードが必要です。

上記の画像のプログラムを実行すると以下の結果になります。

# ./test
 start
as: as.cpp:18: int main(): Assertion `a == 5' failed.
中止


・ 処理の流れを限定する
バグが潜んでいる箇所を特定するには、条件分岐が邪魔になることが

あります。条件分岐は状況に応じて動作が変わるのでバグの位置が

分かりにくくなってしまいます。このような場合は、条件分岐の条件を

書き換えたり、どちらか片方だけを行うようにするのも一つの方法です。

この方法は、論理的にありえないエラーケースのテストなどにも有効です。


・ データ構造を推測する
時にはこれまで紹介したような方法だけでなく、推理力が

必要になることがあります。複雑なアルゴリズムやデータ構造の

プログラムでは、バグの箇所とは全く関係ない部分で異常な動作を

示すこともあります。そのような場合、いくらコードを見返しても

「ありえない」という結論にしか辿り着けません(-_-;)

ここはひとつ、メモリがどのように使われているのかを考えてみましょう。

また、データ構造を紙に書き出してみるというのもいいでしょう。

配列の範囲外にアクセスしたり、ポインタが間違ったところを参照して

いるため、突然異常終了するというのは非常によくあることです(^^;)

ありがちだけど、発見するのが難しい厄介な問題です...


さて、ここまでは自力で解決する方法を見てきました。

次回は、ツールに頼るというデバッガ利用についてです。


今日の名言
人間は幸福を求めてこそ意味ある存在である。そしてこの幸福は、人間自身の
中にある。つまり自分が生存するために、毎日必要なものを満足させるところに
あるのだ。
                                   レフト・トルストイ

多くの人々は、どこかほかの土地へ行きさえすれば、何か他の仕事につきさえ
すれば、それですぐ幸福になれると考えているが、ちょっと考えものだ。だから
自分の今手がけていることから、出来るだけ多くの幸福を得ることだ。そして
幸福になる努力を、またの日まで延ばさないことだ。
                                   デール・カーネギー

人間は、自分で努力して得た結果の分だけ幸福になる。ただしそのためには、
何が幸福な生活に必要であるか知ることだ。すなわち簡素な好み、ある程度の
勇気、ある程度までの自己否定、仕事に対する愛情、そして何よりも、清らかな
良心である。今や私は、幸福は漠然とした夢ではないと確信している。経験と
思考を正しく用いることにより、人間は自分自身から多くのものを引き出すことが
できる。決断と忍耐により、人間は自分の健康を取り戻すことすらできる。
だから人生をそのあるがままに生きよう。そして感謝を忘れないようにしよう。
                                   ジョルジュ・サンド