デバッグ
ソフトウェア開発工程デバッグ(debug)とは、コンピュータプログラムや電気機器中のバグ・欠陥を特定して取り除き、動作を仕様通りのものとするための作業である。“de-"は「〜から離れて/分離して/除去して」といった否定の意味を持つ接頭辞であり、「虫」を意味する英語名詞“bug"と結びついた複合語が“debug"である。
サブシステムが密結合であると、1箇所の変更が別の箇所でのバグを作り出すので、バグの修正がより困難となる。
「デバッグ」という語を初めて使った人物については論争がある(バグを参照)。1976年に Glenford J. Myers の Software Reliability: Principles and Practices で"debugging"が「既知のエラーの原因を突き止め、そのエラーを修正すること」と定義されて使われたのが初めてだとする者がいる一方、1940年代にグレース・ホッパーによるとする者もいる。
その逸話は以下のようなものである。ホッパーがある初期のコンピュータに携わっていたとき、蛾がリレーの中に入って動作不良を起こすのを見た。ここから、プログラムからエラーを取り除く作業を指してデバッグという語が使われ始めたと信じている者もいる。ホッパーによればバグという語はそれ以前にも使われており、実際にその場面に遭遇したのがおもしろかったという。
一般的に言って、デバッグは面倒で退屈な作業である。実際の作業ではプログラマのデバッグに関するスキルがおそらく最も重要な要素となるが、ソフトウェアのデバッグの難易度は使用するプログラミング言語やデバッガなどのツールによって大きく左右される。デバッガを使うとプログラムの実行について観測、停止、再開、速度を落としての実行、メモリ中の値の変更が行え、さらには時間を巻き戻すことさえ可能な場合がある。また、デバッグ作業を行う人のことを指してデバッガと呼ぶこともある。
一般的に高級言語―例えばJava―でのデバッグはより簡単である。なぜなら例外処理などの機能が使え、異常なふるまいの原因となっている箇所を特定するのがより簡単となるからである。低級言語、例えばC言語やアセンブリ言語では気づかないうちにメモリ破壊(不正なアドレスへのアクセスやメモリアロケーションミスなど)を引き起こすことがあり、問題がどこから生まれているのか突き止めるのが困難なことが多い。そのような場面では高度なデバッグツールが必要とされる。
状況によっては、特にソース・ツリーが巨大でウォークスルーが困難な場合など、言語に特化した汎用ソフトウェアツールが非常に役立つことがある。それらは静的コード解析ツールの一種であり、限られたいくつかの既知の問題をソースコード中から探す。問題にはよくあるものも、稀なものも含まれる。このようなツールに検出される問題はコンパイラやインタプリタにはほとんど検出されないものである。つまり、これらのツールは文法チェッカーではなく意味チェッカーである。300以上の問題を検出できると宣伝するツールもある。様々な言語において商用やフリーのものが存在する。検出される問題として典型的なものに、変数に値を代入する前に起きるデリファレンスがある。また、言語仕様にない場合でも強い型検査を行うことができる。したがって、実際のエラーよりも潜在的なエラーに強い。このため、これらのツールは誤検出の悪評がある。このようなツールの初期の例としてlintがある。
電気機器(コンピュータ・ハードウェアなど)やローレベルソフトウェア(BIOS、デバイスドライバなど)、ファームウェアのデバッグでは、オシロスコープやロジックアナライザ、インサーキット・エミュレータ (ICE) が単独または組み合わせでよく使われる。ICEではソフトウェアデバッガが行うことのできる典型的な作業の多くをローレベルソフトウェアやファームウェアに対して行うことができる。
デバッグ作業は対象によってさまざまであるが、一般的なデバッグの原則を見つけることができる。本節ではソフトウェアのデバッグについて扱うが、ハードウェアについても適用できることは多い。
デバッグの基本的なステップは以下である。
修正し、テストする
バグの存在は予知できることも結果的に判明することもある。
経験を積んだプログラマはどこでエラーが起きやすいかをよく知っている。それはプログラムの部分ごとの複雑性やデータ破壊の危険性などから判断できる。例えば、ユーザが入力したデータは疑って扱うべきである。注意深く、データの形式および内容が正しいものであると検証すべきである。通信によって得たデータならば、メッセージ(データ)の全体を受信したか確かめねばならない。複雑なデータをパースまたは加工する際には、値の予期しない組み合わせを含んでいて正しく扱えないことがある。エラーの兆候らしき箇所にチェックを挿むことで、データがいつ破壊された、または正しく処理されなかったかを検出することができる。
もしエラーがプログラムを異常終了させるほど深刻なものだったなら、バグの存在は明らかである。プログラムがそこまで深刻ではない問題を検出した場合、エラーとなるかログメッセージが表示されるかすると、バグの存在を認識できる。しかしエラーが軽微で間違った結果を出すだけであったら、バグの存在を検出するのははるかに難しくなる。これはプログラムの結果を検証するのが困難であるか不可能である場合に特に成り立つ。
このステップの目的はバグのしるしを特定することである。問題のしるし、どのような条件の下で問題が起き、(可能なら)どのような回避策があったか、を観測することは後のステップで問題点をデバッグする大きな助けとなる。
このステップはシステムのどの部分が問題を引き起こしているかを特定するものである。これは度々デバッグ作業で最も困難な(ゆえにやりがいのある)ステップとなる。都合の悪いことに問題の発生箇所は兆候の出現箇所と常に同じであるわけではない。例えば、入力レコードが破壊されていたら、プログラムが別のレコードを処理したり間違った情報に基づいて動作するまでエラーは起きないことがあり、その場合レコードが読み込まれてから長い時間が経ってしまっている。
このステップは反復的なテストを必要とすることが多い。プログラマは最初に入力が正しいことを検証し、次に正しく読み込まれたか、正しく処理されたかなどを検証していく。モジュール化されたシステムではモジュール間のインタフェースを通してやり取りされるデータの妥当性を検査することで、このステップをわずかに楽にすることができる。入力が正しく、しかし出力がそうでなければ、エラーの発生源はそのモジュールの中にある。入力と出力を反復的にテストすることでデバッガはエラーが起きている箇所を数行のコードまで特定することができる。
経験の厚いプログラマは、以前の似た状況からの類推でどこに問題があるか仮説を立てることができる。そして疑わしい箇所の入力と出力をテストする。このようなデバッグは科学的手法の一種である。経験の浅いデバッガはプログラムをステップ実行して、プログラムの振る舞いが期待のものと異なる箇所を探そうとする。異常な振る舞いを探すためにどの変数に着目するか決めなければならないので、これもまた科学的手法の一種である。別のアプローチは二分探索の類を使うことである。処理またはデータのフローの中央付近でテストすることで、エラーがプログラムのそれより前で起きているか後で起きているかを確定することができる。データに何も問題が検出されなければ、エラーはそれより後で起きていることになる。二分探索を使わない場合の探索時間が最大tだとすれば、二分探索を使った場合の探索時間は最大log2 tである。
バグの位置を見つけたら、次のステップはバグの実際の理由を突き止めることである。これにはプログラムの他の部分を調べることが必要な場合がある。例えば、データのフィールドが間違っていたためにプログラムが停止したとする。この次のステップはフィールドが間違っていた理由を特定することであり、それがバグの真の発生源である。プログラムが不正なデータに対処できないこともバグとみなせると主張する者もいるが。
システムをよく理解していることはバグの原因をうまく特定するのに不可欠である。熟練したデバッガなら問題がどこに由来するのかを特定することができるが、システムに詳しい者だけがそのエラーの真の原因を的確に突き止めることができる。原因が、例えば入力データなど、システムの外部に在ることもある。また、正しいデータを間違った方法で扱っているなど、ロジック上のエラーもありうる。他の可能性としてはデータの与えられ方(値の組み合わせ、数など)が予期しないものであったり、不正な参照など様々なものがある。
バグの原因を突き止めたら、コード中の類似した箇所を調べて間違いが繰り返されていないかを確認するのが良い考えである。重複コードが多ければこれはより面倒な作業となる。エラーがはっきりとしたタイポであったら可能性は低いが、プログラマが設計や仕様を誤解したものであったのなら同じか類似した間違いが他で犯されている可能性がある。
問題の源を特定したら、次はどのようにその問題を修正するかを決定する作業である。問題が非常に単純なものである場合を除いて、システムの深い理解が必要不可欠となる。なぜなら、修正によってシステムの現状の振る舞いを変更してしまい、予期しない結果を生むことになるかもしれないからである。その上、既存のバグの修正は新しいバグを生み出したり、修正したバグに隠れていたバグを顕在化させることがよくある。このような問題はコード中でそれまでテストされていなかった部分を実行する際によく発生する。
場合によっては、修正は単純で明確である。これはオリジナルの設計を間違って実装しているようなロジック上のエラーに特に当てはまる。反対に、問題がシステムの大部分に渡るような重大な設計上の不備を浮き彫りにした場合には、修正は悪ければ不可能となり、ソフトウェアの一からの書き直しが必要となる。
状況によって、恒久的な修正の前に"quick fix"の実装が応急処置的に望まれることがある。これは修正の性質や製品のスケジュール(またはさらに切迫した問題)以外にも、問題の緊急度、現れやすさ、頻度、副作用などを考慮して決定されることが多い。
修正が適用された後にシステムをテストしてその修正が以前の問題に正しく対処しているか確認するのは重要である。テストを行うべき理由は2つある:
の確認のためである。
規模の大きなシステムではリグレッションテストを行うのがよい考えである。重大な変更やバグ修正の後で、このテストはシステムが依然仕様通りに動作することを検証するためにいつでも繰り返し実行される。新しい機能が追加されると、追加のテストがテストスイートに収録される。