デバッグと不確定性原理

客観的事実など存在しない。
あるのは自分の目を通して見た事実だけである
ハイゼンベルク

プログラムの動作がおかしい。そこで、デバッグのためにログ出力のコードを仕込んで動作させてみる。すると、さっきまで調子の悪かったプログラムが正常に動いている。プログラムをもとに戻すとやっぱり動作がおかしい。

このような経験はないでしょうか。いわゆる、デバッグにおける「不確定性原理」の一例です。

不確定性原理とは何か

ハイゼンベルクの不確定性原理(uncertainty principle)とは量子力学における基本原理ですが、これはデバッグに当てはまります。オリジナルの不確定性原理の概要をWikipediaの説明から引用しておきます。
極微の世界では粒子の運動量と位置を同時に決定することはできないという原理。位置をより正確に観測する為にはより正確に「見る」必要があるが、極微の世界でより正確に見る為には、波長の短い光が必要であり、波長の短い光はエネルギーが大きいので観測対象へ与える影響が大きくなる為、観測対象の運動量へ影響を与えてしまうからである。位置を正確に測ろうとするほど、対象の運動量が正確に測れなくなり、運動量を正確に測ろうとすれば逆に位置があいまいになってしまい、両者を完全に正確に測る事は絶対に出来ない。
冒頭のデバッグの例は、まさにこのような不確定性原理がデバッグに適用されたような形になっています。つまり、ログ出力というプログラミングに対する観測行為が、プログラムの動作に影響を与えてしまったために、もともとの状態が変化してしまっていたのです。バグが再現しなくなったのは、観測行為がプログラムの動作を変えてしまっていたのです。

では、なぜログ出力がプログラムの動作を変えてしまったのでしょうか?ログ出力を追加しただけで、アルゴリズムを変えたわけではありません。このような場合で最も多いのは、もともとのバグがタイミングに依存したものであった場合です。マルチスレッドや割り込み処理を行うようなプログラムの場合、ログ出力の追加による僅かなタイミングのずれが、バグを起こらなくしてしまうということは良くある話です。特に、ログの出力先がファイルやシリアル等の場合は、そのタイミングで実行タスクがI/O待ちでスリープしてしまうことがあるためよく起こります。
その他、コードの追加はメモリマップを変更してしまうため、メモリ破壊や変数の初期化漏れ等のバグの挙動を変えてしまうこともあります。

なお、ログ出力等のコード追加をしなくとも、デバッグのためにコンパイルの最適化レベルを変更したり、デバッガをつないだりするだけでもプログラムの挙動に影響を与えてしまうことがありますし、デバッガでブレークをかけたりする行為もプログラムの挙動に影響を与えてしまいます。

不確定性原理とうまく付き合う

では、この不確定性原理の影響を排除するには、どのようにすれば良いのでしょうか。残念ながら、これは「原理」であるので、完全に回避することは不可能です。しかし、何も悲観的になる必要はありません。見方を変えれば、これはデバッグのための観測についての指針を示してくれているものと考えられます。

オリジナルの不確定性原理の説明を再び引用すると、
位置を正確に測ろうとするほど、対象の運動量が正確に測れなくなり、運動量を正確に測ろうとすれば逆に位置があいまいになってしまい、両者を完全に正確に測る事は絶対に出来ない。
とあります。
デバッグに置き換えれば、正確な内部情報を知ろうとすればするほど、もとの動作に影響を与えてしまうということになるでしょうか。
ここで、見方を変えれば、
  • 動作に影響を与えないようにデバッグするためには、
    極力観測による影響が少なくなるようにすれば良い
と考えられます。

具体的な例をあげると、
  • ロギングを使用する場合、目的のログだけを出力する等、対象を絞れる仕組みを入れる
  • ログ出力のI/O負荷が問題になりそうな場合は、ログを絞ってメモリ上に残す
  • ブレークによる一時停止が影響しそうな場合は、条件ブレークを活用して最小限にブレークがかかるようにする。
    もしくは、ロギングを活用したデバッグを行う
等が考えられます。

不確定性原理によるリスク

冒頭の例でもそうですが、デバッグ用のコードの追加がバグを隠してしまうことがあります。このため、開発の終盤にはじめてデバッグ用のコードを抜いたり、最適化レベルを変更したりすると、とたんにプログラムが動かなくなる可能性があります。このような事を防ぐために、普段からデバッグコードを抜いたり、最適化レベルを変えても正常に動くかどうかをチェックしながら開発を進めることが必要です。

4873115930 【関連リンク】
不確定性原理 (Wikipedia)

【関連書籍】
デバッグルール 9つの原則、54のヒント
不確定性原理―運命への挑戦 (都筑 卓司)

デバッグ指向のススメ

予めおもんぱかれば、簡単であるが、後になっておもんぱかれば、複雑になる。
ゲーテ -『格言と反省
デバッグ指向プログラミング(debug-oriented programming)とは何か。一言で言うとすると、「デバッグしやすいプログラムを書きましょう」ということです。テスト指向(test-oriented)と近いところもありますが、よりデバッグの効率に重点を置いた考え方です。詳しい内容は今回だけでは書ききれませんが、とりあえず今回は、なぜデバッグ指向なのか、デバッグ指向とは具体的にどんなものか、ということについて書いてみたいと思います。

バグの発生は必然

バグの発生は予定外の出来事ではありません。よほど小規模のプログラムでない限り、ソースコードが一発で不具合なく動くことはほとんどありません。つまり、デバッグ作業というものは、ソフトウェア開発において重要なステップなのです。実際、大抵のソフトウェア開発では、コーディングそのものよりもデバッグに費やす時間の方が多いことが多いのではないでしょうか。つまり、このデバッグをいかに効率よく進められるかということが、ソフトウェア開発の効率を左右するといっても過言ではありません。

デバッグ効率をいかにあげるか

では、デバッグの効率はどのようにすれば改善できるのでしょうか?項目としてあげるとすると、
(1)バグのパターンを知り、解析・切り分け方法を習得する
(2)デバッガ・各種ツールを使いこなす
(3)デバッグ用のコードを埋め込んでおく
(4)デバッグしやすいコードを書く
といったところでしょうか。

(1)についてですが、設計手法と同様、デバッグ手法にもパターンや系統的手法が存在します。よくあるバグのパターンを知っていると、デバッグ効率もあがります。
(2)についてですが、最近のデバッガはかなり強力です。メモリリークやオーバーランの検出に有用なツールのあります。デバッガやツールを使いこなせるかどうかでデバッグ効率は大きく変わってきます。

(3)(4)が、デバッグ指向プログラミングの勘所です。デバッグ用のコードといっても単にprintfを埋め込めというわけではありません。勿論ログを残すというのは有効なデバッグ方法ですが、それだけではなく、assertの埋め込み、デバッガ向けコード、デバッグ関数の埋め込み等があります。開発対象がライブラリであれば、ライブラリ自身だけでなく呼び出し側でのデバッグも考慮します。

細かい点は追々追加していきますので、今回はデバッグ指向の一例として、契約プログラミングについて説明します。

契約プログラミング

契約プログラミング(DBC:Design By Contract)という言葉をご存知でしょうか。プログラムのコード上に、満たすべき条件を埋め込んでおき、違反があれば検出するというものです。最も基本的なものにassertがあります。ご存知のように、条件をチェックして、不一致であればログ出力後、abortするものです。
#include <stdio.h>
#include <assert>

#define ENTRY_MAX 5
int g_entry[ENTRY_MAX]

int
nth_entry(int n)
{
  assert(n < ENTRY_MAX && n >= 0)
  return g_entry[n];
}

上記のコードでは、nが5以上で呼び出されると、abortしてプログラムが停止してしまいます。違反が検出された時点でプログラムが停止するため、デバッガで動作させていればすぐにバックトレースをとることもできます。

では、assertがなかった場合、どうなるでしょうか?運がよければ、不正メモリアクセスでsegmentation faultか何かで同じく停止し、assert使用時と同様に発生地点を発見できるかもしれません。しかし、大抵の場合、不定値を返したあと、しばらく実行が続いたのちにバグが表面化するため、一体どこでバグが入ったのかを調べるのが大変になります。バグは発生した瞬間に捕まえないと非常にやっかいなので、assertで異常を見つけた瞬間に停止させるのは非常に有効な手段です。

まれにつぎのようなコードをみかけます。
#include <stdio.h>
#include <assert>

#define ENTRY_MAX 5
int g_entry[ENTRY_MAX]

int
nth_entry(int n)
{
  if (n >= ENTRY_MAX || n < 0)
    return -1; /* out of range */
  return g_entry[n];
}

配列の範囲外アクセスの場合、エラー(-1)を返すということになっています。外部提供関数であれば、これもありかもしれませんが、これが内部関数であれば、エラーを返す意味がありません。内部ロジックがおかしいのですから、エラーなぞ返さずに異常検出でabortした方が望ましいのです。誰もエラーチェックしないような関数でエラーを返す意味はありません。先のassertを入れたコードのようにするべきでしょう。

assertは非常に有用です。必ず満たしていなければならない条件がある場合は、こまめにassertをいれるべきです。関数の引数だけでなく、複雑なアルゴリズムの計算経過の確認などでも使えます。assertはコメントと違い、直接コードとリンクするのでコードとの不一致を起こすこともありません。

但し、assertを入れるとそのチェックが入る分、コードサイズや処理時間に影響を及ぼします。ですので、数が多い場合は、段階ごとにassertをOFFにできるようしておいた方が良いでしょう。
#define DEBUG_LEVEL 0

#if DEBUG_LEVEL > 0
#define ASSERT_1(x) assert(x)
#else
#define ASSERT_1(x)
#endif

#if DEBUG_LEVEL > 1
#define ASSERT_2(x) assert(x)
#else
#define ASSERT_2(x)
#endif

こうすれば、DEBUG_LEVELマクロで、一部のassertだけを有効にできます。

今回は契約プログラミング,assertについて説明しました。その他のデバッグ指向プログラミングに関するTIPSも本サイトでおいおい紹介していきたいと思います。ちなみに、「デバッグ指向」という単語は私の造語ですので、Googleで調べていただいてもhitしません。あしからずご了承ください (^^;)

※ D言語ではより強化された契約プログラミングの機能が言語レベルで実装されているそうです

4873114063 【関連記事】
バグのパターン

【関連リンク】
Linuxのデバッグ手法をマスターする
プログラミング言語D - 契約プログラミング

【関連書籍】
.NET&Windowsプログラマのためのデバッグテクニック徹底解説
Javaデバッグ明快技法
D言語パーフェクトガイド―一歩先行くC/C++/C#/Java開発者に贈るD

趣味と職業の違い

「職業としてのプログラミング」となんだかすごいタイトルをつけてしまいましたが、私自信まだまだプログラマあるいはソフトウェア技術者としての経験はまだまだです。
なので、そんなにえらそうなことを言える立場でもないですが、今からプログラマを目指そう、あるいは、自分の技術をステップアップしていこうという方にとって、なにかしら役に立つことが書けたらなと思います。

最初に、私が考える、「趣味としてのプログラミング」と「職業としてのプログラミング」の違いについて書きたいと思います。

■趣味としてのプログラミング
私も昔は趣味でプログラミングをしていた時期があります。主に作っていたのは、簡単なゲームなんかです。別にゲームをしたいってわけではなくて、プログラムを書くということが楽しくてやっていました。インターネットもそれほど普及する前で、作ったものをオープンソースで公開するとかいうこともなかったんですが、自分なりに綺麗なコードを書くこと目指してせこせこがんばってました。

このような趣味のプログラミングの特徴を書くとすれば、次のような感じでしょうか。
  • 最も重要な目的は、自分自信が満足すること
  • 自分のやりたいようにできる
  • 作ったものに対する責任はなし
  • 締め切りはなし!
例外もあるでしょうが、だいたいこんな感じではないでしょうか。
趣味なんだから、とにかく自分が楽しいことが重要ですよね。

■職業としてのプログラミング
職業としてのプログラミングは、当たり前ですが就職してからがメインです。就職前も、WebページのCGI等はちょっとだけやってました。ちなみに現在の仕事はいわゆるSEではなく、組み込み機器のソフト屋さんです。

職業としてのプログラミングになると、趣味のころには無かったような、いろんな制約が発生します。具体的には次のようになります。
  • お客さんの満足が第一義
  • 複数人での開発になることが多い
  • 開発を行う上でのルールがある
    (コーディング規約、言語、プロセス管理)
  • 作ったものに対する責任があり、品質確保が必須
  • コスト(時間、お金)に制限がある
職業としてプログラミングをする場合は、このような制約が必ず発生します。
個々について、もう少し詳しく説明しておきます。

○お客さんの満足が第一義
なんといっても、この点が最も違う点でしょう。仕事でやっている以上、お客さんが最も重要なのは言うまでもありません。
そんなの当たり前と思っていても、本人が良かれと思っていることが、案外技術的な自己満足で終わってしまっているというのもよくある話。
与えられた要求仕様を確実にこなすことは勿論ですが、潜在的な要求を見出していくのも、職業プログラマには必要な技術が必要になってきます。

また、時には嫌な(面白くない)仕事だってやらないといけません。

○複数人での開発
大規模なプロジェクトになると、大抵複数人での開発になります。複数人での開発では、進捗管理、リソース配分等が必要になります。勿論的確な意思疎通を行うコミュニケーション能力も必要です。

○開発上のルール
開発のルールといってもいろいろありますが、使える言語が指定されているというところから、コーディングルール、ドキュメント作成など、趣味の時とは違って、自由にならないことがたくさんあります。

○仕事に対する責任
仕事で作成したソフトウェアの多くは、なんらかの対価と引き換えにお客さんの手にわたります。早い話、お金を頂くわけですから、その成果物に対しては、作成者としての責任が発生します。
重大なバグ等は、お客さんへのご迷惑をおかけするだけでなく、製品の回収、あるいはアップデート費用等で会社にも被害を与えます。高品質ソフトウェア開発は非常に重要なテーマです。

○コスト
企業は利益をあげないといけません。社会への貢献も何もかも、つぶれてしまっては何もできないのです。開発作業では、決められたお金、時間の中で開発を進めますが、それぞれの開発者がコスト意識をもって開発にあたらなければ、良いものはできません。プログラミングそのものの技術だけでなく、プロジェクト管理等の技術も必要とされています。

とりあえず、思いつくままだらだらと書いてしまいましたが、おいおい掘り下げた内容を書いていきたいと思います。

本のおすすめ

4873115655

4274065979

4822236862

4274068579

4822255131

B00SIM19YS


プロフィール

  • Author:proger
  • 組み込み関係で仕事してます

ブログ内検索