デバッグ
デバッグ(英: debug)とは、コンピュータプログラムや電気機器中のバグ・欠陥を特定して取り除き、動作を仕様通りのものとするための作業である。“de-”は「〜から離れて/分離して/除去して」といった否定の意味を持つ接頭辞であり[1]、「虫」を意味する英語名詞“bug”と結びついた複合語が“debug”である。
サブシステムが密結合であると、1箇所の変更が別の箇所でのバグを作り出すので、バグの修正がより困難となる。
語源
[編集]「デバッグ」という語を初めて使った人物については論争がある(バグを参照)。1976年に Glenford J. Myers の Software Reliability: Principles and Practices で"debugging"が「既知のエラーの原因を突き止め、そのエラーを修正すること」と定義されて使われたのが初めてだとする者がいる一方、1940年代にグレース・ホッパーによるとする者もいる。
その逸話は以下のようなものである。ホッパーがある初期のコンピュータに携わっていたとき、蛾がリレーの中に入って動作不良を起こすのを見た。ここから、プログラムからエラーを取り除く作業を指してデバッグという語が使われ始めたと信じている者もいる。ホッパーによればバグという語はそれ以前にも使われており、実際にその場面に遭遇したのがおもしろかったという[2]。
ツール
[編集]一般的に言って、デバッグは面倒で退屈な作業である。実際の作業ではプログラマのデバッグに関するスキルがおそらく最も重要な要素となるが、ソフトウェアのデバッグの難易度は使用するプログラミング言語やデバッガなどのツールによって大きく左右される。デバッガを使うとプログラムの実行について観測、停止、再開、速度を落としての実行、メモリ中の値の変更が行え、さらには時間を巻き戻すことさえ可能な場合がある。また、デバッグ作業を行う人のことを指してデバッガと呼ぶこともある。
一般的に高水準言語―例えばJava―でのデバッグはより簡単である。なぜなら例外処理などの機能が使え、異常なふるまいの原因となっている箇所を特定するのがより簡単となるからである。低水準言語、例えばC言語やアセンブリ言語では気づかないうちにメモリ破壊(不正なアドレスへのアクセスやメモリアロケーションミス[要説明]など)を引き起こすことがあり、問題がどこから生まれているのか突き止めるのが困難なことが多い。そのような場面では高度なデバッグツールが必要とされる。
状況によっては、特にソース・ツリーが巨大でウォークスルー(段階的な検証)が困難な場合など、言語に特化した汎用ソフトウェアツールが非常に役立つことがある。それらは静的コード解析ツールの一種であり、限られたいくつかの既知の問題をソースコード中から探す。問題にはよくあるものも、稀なものも含まれる。このようなツールに検出される問題は、プログラミング言語の仕様で定められた構文上は適格(well-formed)であるものも含まれ、その場合コンパイラやインタプリタの実装によっては警告が出ず、ほとんど検出されないものもある[注釈 1]。つまり、これらのツールはシンタックス(構文/文法)チェッカーではなくセマンティクス(意味)チェッカーである。300以上の問題を検出できると宣伝するツールもある[要出典]。様々な言語において商用やフリーのものが存在する。また、動的型付け言語など、静的型付けが言語仕様にない場合でも強い型検査を行うことができる[注釈 2]。したがって、実際のエラーよりも潜在的なエラーに強い。一方、これらのツールには誤検出(偽検出)や過剰検出の問題点もある。このようなツールの初期の例としてlintがある。ただしスレッドにまつわるバグなど、動作タイミングが非決定論的で実行時でなければ顕現しないような問題は、静的コード解析では通例発見できないため、動的コード解析(動的プログラム解析)を利用する必要がある。
電気機器(コンピュータ・ハードウェアなど)やローレベルソフトウェア(BIOS、デバイスドライバなど)、ファームウェアのデバッグでは、オシロスコープやロジックアナライザ、インサーキット・エミュレータ (ICE) が単独または組み合わせでよく使われる。ICEではソフトウェアデバッガが行うことのできる典型的な作業の多くをローレベルソフトウェアやファームウェアに対して行うことができる。
基本的な手順
[編集]この節には独自研究が含まれているおそれがあります。 |
デバッグ作業は対象によってさまざまであるが、一般的なデバッグの原則を見つけることができる。本節ではソフトウェアのデバッグについて扱うが、ハードウェアについても適用できることは多い。
デバッグの基本的なステップは以下である。
- バグの存在を認識する
- バグの発生源を分離する
- バグの原因を特定する
- バグの修正方法を決定する
- 修正し、テストする
バグの存在を認識する
[編集]バグの存在は予知できることも結果的に判明することもある。
経験を積んだプログラマはどこでエラーが起きやすいかをよく知っている。それはプログラムの部分ごとの複雑性やデータ破壊の危険性などから判断できる。例えば、ユーザが入力したデータは疑って扱うべきである。注意深く、データの形式および内容が正しいものであると検証すべきである。通信によって得たデータならば、メッセージ(データ)の全体を受信したか確かめねばならない。複雑なデータをパースまたは加工する際には、値の予期しない組み合わせを含んでいて正しく扱えないことがある。エラーの兆候らしき箇所にチェックを挿むことで、データがいつ破壊された、または正しく処理されなかったかを検出することができる。
もしエラーがプログラムを異常終了させるほど深刻なものだったなら、バグの存在は明らかである。プログラムがそこまで深刻ではない問題を検出した場合、エラーとなるかログメッセージが表示されるかすると、バグの存在を認識できる。しかしエラーが軽微で間違った結果を出すだけであったら、バグの存在を検出するのははるかに難しくなる。これはプログラムの結果を検証するのが困難であるか不可能である場合に特に成り立つ。
このステップの目的はバグのしるしを特定することである。問題のしるし、どのような条件の下で問題が起き、(可能なら)どのような回避策があったか、を観測することは後のステップで問題点をデバッグする大きな助けとなる。
バグの発生源を分離する
[編集]このステップはシステムのどの部分が問題を引き起こしているかを特定するものである。これは度々デバッグ作業で最も困難な(ゆえにやりがいのある)ステップとなる。都合の悪いことに問題の発生箇所は兆候の出現箇所と常に同じであるわけではない。例えば、入力レコードが破壊されていたら、プログラムが別のレコードを処理したり間違った情報に基づいて動作するまでエラーは起きないことがあり、その場合レコードが読み込まれてから長い時間が経ってしまっている。
このステップは反復的なテストを必要とすることが多い。プログラマは最初に入力が正しいことを検証し、次に正しく読み込まれたか、正しく処理されたかなどを検証していく。モジュール化されたシステムではモジュール間のインタフェースを通してやり取りされるデータの妥当性を検査することで、このステップをわずかに楽にすることができる。入力が正しく、しかし出力がそうでなければ、エラーの発生源はそのモジュールの中にある。入力と出力を反復的にテストすることでデバッガはエラーが起きている箇所を数行のコードまで特定することができる。
経験の厚いプログラマは、以前の似た状況からの類推でどこに問題があるか仮説を立てることができる。そして疑わしい箇所の入力と出力をテストする。このようなデバッグは科学的手法の一種である。経験の浅いデバッガはプログラムをステップ実行して、プログラムの振る舞いが期待のものと異なる箇所を探そうとする。異常な振る舞いを探すためにどの変数に着目するか決めなければならないので、これもまた科学的手法の一種である。別のアプローチは二分探索の類を使うことである。処理またはデータのフローの中央付近でテストすることで、エラーがプログラムのそれより前で起きているか後で起きているかを確定することができる。データに何も問題が検出されなければ、エラーはそれより後で起きていることになる。二分探索を使わない場合の探索時間が最大tだとすれば、二分探索を使った場合の探索時間は最大log2 tである。
バグの原因を特定する
[編集]バグの位置を見つけたら、次のステップはバグの実際の理由を突き止めることである。これにはプログラムの他の部分を調べることが必要な場合がある。例えば、データのフィールドが間違っていたためにプログラムが停止したとする。この次のステップはフィールドが間違っていた理由を特定することであり、それがバグの真の発生源である。プログラムが不正なデータに対処できないこともバグとみなせると主張する者もいるが。
システムをよく理解していることはバグの原因をうまく特定するのに不可欠である。熟練したデバッガなら問題がどこに由来するのかを特定することができるが、システムに詳しい者だけがそのエラーの真の原因を的確に突き止めることができる。原因が、例えば入力データなど、システムの外部に在ることもある。また、正しいデータを間違った方法で扱っているなど、ロジック上のエラーもありうる。他の可能性としてはデータの与えられ方(値の組み合わせ、数など)が予期しないものであったり、不正な参照など様々なものがある。
バグの原因を突き止めたら、コード中の類似した箇所を調べて間違いが繰り返されていないかを確認するのが良い考えである。重複コードが多ければこれはより面倒な作業となる。エラーがはっきりとしたタイポであったら可能性は低いが、プログラマが設計や仕様を誤解したものであったのなら同じか類似した間違いが他で犯されている可能性がある。
バグの修正方法を決定する
[編集]問題の源を特定したら、次はどのようにその問題を修正するかを決定する作業である。問題が非常に単純なものである場合を除いて、システムの深い理解が必要不可欠となる。なぜなら、修正によってシステムの現状の振る舞いを変更してしまい、予期しない結果を生むことになるかもしれないからである。その上、既存のバグの修正は新しいバグを生み出したり、修正したバグに隠れていたバグを顕在化させることがよくある。このような問題はコード中でそれまでテストされていなかった部分を実行する際によく発生する。
場合によっては、修正は単純で明確である。これはオリジナルの設計を間違って実装しているようなロジック上のエラーに特に当てはまる。反対に、問題がシステムの大部分に渡るような重大な設計上の不備を浮き彫りにした場合には、修正は悪ければ不可能となり、ソフトウェアの一からの書き直しが必要となる。
状況によって、恒久的な修正の前に"quick fix"の実装が応急処置的に望まれることがある。これは修正の性質や製品のスケジュール(またはさらに切迫した問題)以外にも、問題の緊急度、現れやすさ、頻度、副作用などを考慮して決定されることが多い。
修正し、テストする
[編集]修正が適用された後にシステムをテストしてその修正が以前の問題に正しく対処しているか確認するのは重要である。テストを行うべき理由は2つある:
- その修正が問題に正しく対処しているか
- その修正が望ましくない副作用を引き起こしていないか
の確認のためである。
規模の大きなシステムではリグレッションテストを行うのがよい考えである。重大な変更やバグ修正の後で、このテストはシステムが依然仕様通りに動作することを検証するためにいつでも繰り返し実行される。新しい機能が追加されると、追加のテストがテストスイートに収録される。
脚注
[編集]注釈
[編集]- ^ 静的コード解析ツールによって検出される問題として典型的なものに、C言語やC++において未初期化の変数に値を代入する前に起きるデリファレンスがある。未初期化変数に読み取りアクセスした場合、C/C++の構文上は適格であるものの、未定義動作となる。このようなコードは一般的に潜在的な問題を引き起こすバグとなる可能性が非常に高いため、警告を出すコンパイラも多い[3][4]が、未初期化の配列や構造体といった集成体や、クラスの未初期化メンバー変数に読み取りアクセスしても警告を出さないコンパイラもある。このような検出漏れを静的コード解析ツールで補うことができる。一方、JavaやC#といった後発の高水準言語では、未初期化のフィールドはゼロ相当の既定値で初期化され、また未初期化のローカル変数にアクセスすることは不適格(ill-formed)であり、コンパイラがエラーを表示して停止するため、そもそもバグを作り込みにくい言語仕様になっている。
- ^ C/C++の可変長引数は型消去によって実現されているため、例えばprintfやscanfの書式指定文字列の内容と対応する実引数の型や個数のミスマッチがあった場合でもコンパイラは誤りを検出できず、プログラムの実行時に未定義動作を引き起こす。このような書式指定ミスをコンパイル時に検出できるようにするために、独自の属性や注釈の機能をサポートする処理系もある[5][6]。
出典
[編集]- ^ de-の意味・使い方|英辞郎 on the WEB
- ^ “bug”. The Jargon File (2003年12月29日). 2013年3月21日閲覧。
- ^ Compiler Warning (level 1 and level 4) C4700 | Microsoft Learn
- ^ Diagnostic flags in Clang — Clang git documentation
- ^ Annotating function parameters and return values | Microsoft Learn
- ^ Attributes in Clang — Clang git documentation
参考文献
[編集]- Agans, David J.. Debugging: The Nine Indispensable Rules for Finding Even the Most Elusive Software and Hardware Problems. AMACOM. ISBN 0-8144-7168-4
- Telles, Matthew A.; Yuan Hsieh, Matt Telles. The Science of Debugging. The Coriolis Group. ISBN 1-57610-917-8
- Metzger, Robert. Debugging by Thinking : A Multidisciplinary Approach. Digital Press. ISBN 1-55558-307-5
- Robbins, John. Debugging Applications. Microsoft Press. ISBN 0-7356-0886-5
- Ford, Ann R.; Toby J. Teorey. Practical Debugging in C++. Prentice Hall. ISBN 0-13-065394-2
- Blunden, Bill. Software Exorcism: A Handbook for Debugging and Optimizing Legacy Code. APress. ISBN 1-59059-234-4
- Brooks, Frederick Phillips. The Mythical Man-Month: Essays on Software Engineering. Pearson Addison Wesley. ISBN 0-201-00650-2
- Myers, Glenford J. Software Reliability: Principles and Practices. John Wiley & Sons inc. ISBN 0-471-62765-8
- Myers, Glenford J. The Art of Software Testing. John Wiley & Sons inc. ISBN 0-471-04328-1
- Zeller, Andreas. Why Programs Fail: A Guide to Systematic Debugging. Morgan Kaufmann. ISBN 1-55860-866-4
- Andreas Zeller:「デバッグの理論と実践 ―なぜプログラムはうまく動かないのか」、オライリージャパン、ISBN 978-4873115931(2012年12月22日)。
関連項目
[編集]外部リンク
[編集]以下英語
- Debugging Backwards in Time - Omniscient Debugging
- Citations from CiteSeer
- Learn the essentials of debugging デバッグの方法論に関する記事
- Debugging Software Crashes in C
- Debugging Software Crashes in C++
以下日本語
- 職人的デバッグと科学的デバッグ 千葉滋、bit vol.29、no.1、pp. 24-25、共立出版、January, 1997.
- ソースコードを読むための技術
- 使いながら覚えるGDB