🐛

ソフトウェアのデバッグ入門

2023/01/25に公開

この記事の対象者

  • プログラミングの経験がまだ浅い人
  • 他の人が書いたコードや過去の自分のコードを運用することに慣れていない人
  • バグの調査やデバッグに慣れず苦労している人

デバッグとは

ここで記載するデバッグとは、プログラム上で発生しているバグ(不具合)やソフトウェアシステム上で発生しているバグを発見し、対応することを指します。

デバッグの基本

① 起きている事象を正確に把握する

はっきりと原因が分かっているときは別ですが、一見しても原因がわからないバグでいきなりコード調査しにいくことはやめたほうが良いでしょう。
デバッグでまず重要なのは、現在起きている事象を正確に把握すること です。

正確に把握するための観点には以下のようなものがあります。

環境

そのバグがどんな環境で発生しているかを確認しましょう。
一般的なバグチケットには発生している環境が書かれていることがありますが、実際に自分でも確認してみましょう。また後述しますが、その他の環境で起きないかどうかの確認も重要です。

  • 実行環境のOSバージョン
    • iOS, Andoird, Chromeなどのバージョン
  • 実行している端末
    • PCのOS(Windows, Mac)、スマートフォンのOS(iOS, Android)
  • 実行しているアカウント(ユーザー)
    • どんなユーザーで発生しているのか?

再現率

そのバグがどのくらいの確率で発生するのかを確認しましょう。
バグは100%発生するものだけではありません。
ものによっては、10回に1回程度しか発生しないものもあり、再現率が低ければ低いほど調査に時間がかかります。
また、環境によって再現率が変わるものも存在します。例えば、本番環境では100%発生するが、手元の開発環境では発生しないなどです。

エラーの出力

バグの発生時にサーバーや端末(スマートフォン、ブラウザ)にエラーログが出力されていることがあります。
バグチケットにログが添付されていることもありますが、見落としている可能性も考えられるため、自分でも確認するようにしましょう。

バージョン

そのバグがいつ発生したかを確認します。
バージョン管理されているようなアプリケーションであれば、発生しているバージョンを確認します。

時間・その他

人が多い時間帯のみ発生するなど時間軸によって発生確率が変わるものも存在します。
アプリケーション利用者が時間帯によって大きく変わるようなソフトウェアでは発生時間も確認しておきましょう。

② 発生箇所を絞り込む

バグの内容について十分に把握できたら、バグの発生箇所が何処にあるかを絞りこんで行きます。

発生箇所を絞り込むときの基本は、バグが発生する場所とバグが発生しない場所の境界を見つける ことです。

「① 起きている事象を正確に把握する」 でいくつかの観点について説明しました。これらの観点については、バグが発生する場所とバグが発生しない場所の境界が存在します。
そこを見極めてバグが発生する可能性のある範囲を徐々に狭めていくのです。

余談ですが、これは、プログラミングのアルゴリズムにおける「2分探索」と同じような考えです。
はじめは大きな観点でざっくり分けていき、徐々に原因の範囲を狭めて行くイメージです。

事例を見ていきます。例えば、「発生OS」について、バグチケットにてiOSでのバグが記載されている場合は、Androidでも発生するのかを確認します。このときAndroidではバグが発生していない場合、この2つのOSの間には バグが発生する場所とバグが発生しない場所の境界 を引くことができます。

境界:OS

このバグがiOSでしか発生していないことがわかりました。それでは次はiOSのOSバージョンで見ていきます。そうするとiOS14では発生していますが、iOS13では発生してないことが分かったとします。

境界:OSバージョン

つまり、このバグは「iOSのOSバージョンが14のときに発生する」ということです。

領域

このように事実関係が不明な観点について、バグが発生する/発生しないの境界をはっきりさせ、徐々にバグ発生箇所の範囲を狭めていくのです。

これはプログラミングコードにおいても基本的には同じです。
例えば何か特定の値がおかしくなるバグがある場合、バグの周辺コードが、以下のようなコールシーケンスになっているとします。

コールシーケンス1

例えばこのようなときはざっくり関数のはじめと終わりにログを埋め込むなどして、観測している値を確認していきます。

コールシーケンス2

もしfunctionBの終了地点で目的の値がおかしくなっていることがわかれば、原因はfunctionBの中である可能性が高いことがわかります。

コールシーケンス3

こうしてfunctionB内でも、同じようなことを繰り返していくといつかは原因のあるコードにたどり着きます。

このように2分探索をしながら、バグの発生箇所を特定することで、何度も同じ所を調べて時間を使ってしまったり、うっかり見落としてしまうことを防げます。

③ 発生時期を絞り込む

バグが埋め込まれているコードの位置は特定できたが、いまいち何が原因かがわからない場合は、別の観点として そのバグがいつ埋め込まれたのか?を調べることが有効です。

ここでは、gitによるコードのバージョン管理が行われていることを前提に話をします。

gitには、各コードのスナップショットがコミットログとして残っています。 ここも 「② 発生箇所を絞り込む」と同じように、バグが発生するコミットと発生しないコミットの境界を見つけ原因のコミットを二分探索していきます。

コミットログ

どのコミットでバグが埋め込まれたのかが把握できると、コード差分を見ることができるため、どういった経緯でバグになったのかが明確になり原因の解析に繋がります。
また、何を目的にした修正だったのかを理解することで、どのようにバグを修正したら良いのかという観点も明確になってきます。

④ 修正方法をリストアップする

ここまででようやく原因となるコードが特定できました。
ここから「さあコードを書き直そう」とするのはまだ少し早いです。まずは修正方法を考えて書き出しましょう。

過去のコミット履歴を見ることで何を目的にした修正でバグが埋め込まれたのかはある程度分かったと思います。そのことを念頭に『その修正を壊さないように』注意して解決方法を考えましょう。

複雑な修正なのであれば、いくつかの修正案が思い浮かぶとベストです。
選択肢には必ず「メリット/デメリット」が存在しますので、各修正案に対してメリット/デメリットをリストアップすることでより良い修正の比較検討ができるからです。

自分ひとりでは判断つかない場合は、これらの案を元に先輩や有識者に相談してみましょう。

⑤ 影響範囲を調査する

修正案が決まりました。
ここでようやくコードを修正したいところですが、最後にもう一つだけ作業をします。決定した修正案に対して『影響範囲』を調べましょう。

修正案に対して、影響範囲を軽視したり見誤ると、「ある共通関数のバグを修正したことにより、その関数を使っている別の箇所に新たなバグを埋め込んでしまった。」といった悲劇を産むことになります。

こうした悲劇を産まないために、自分が修正するコードの内容が他のコードに悪い影響を与えていないかを調査して問題ないことを確認しましょう。

別の観点として、同じコードが分散して存在しており、修正箇所が1箇所で済まないといったケースも存在します。今回発見したバグと同じ原因のバグが他コードに存在していないか?も改めて調べておくと良いでしょう。

⑥ バグを修正する

ここまで来たら、ついにコードを修正できます。検討した修正案でバグを修正しましょう。
この辺りの具体的な内容は割愛しますが、できればソフトウェアテストなどで同じ原因のバグは発生しないように予防を行えるのが理想です。

まとめ

デバッグは、実際にコードを修正する作業よりも、起きている現象/原因を正確に調査・分析する作業の方が重要です。一見難解な事象に見えても、原因が正確に把握できていれば、修正自体はそこまで難しくないといったことも多いのです。
ここで記載した手順を参考に、デバッグ作業を効率的に進めてもらえれば幸いです。

ポイント

  • バグの事象(発生条件や場所など)をできる限り正確に把握する
  • バグの発生箇所を2分探索を使って調査する
  • 複雑な改修については修正案は複数用意する
  • バグ修正による影響範囲を調べる
  • (同じバグが起きないように予防する)

[補足1] 必ずしも丁寧な調査や分析が必要なわけではない

全てのバグについて、発生範囲やコードの分析が必要かというとそうではありません。
一口にバグと言っても、「表示されている文字列が仕様と違っている」といった原因が明確であり軽微なバグも多く存在します。確実に発生している箇所が分かっている場合や、深いコード理解があり原因が特定できている場合は、ここで記載したような手順を1から実行する必要はありません。

ここで書かれている手順は、あくまでバグチケットについて知見がなく、詳しい内容が調べなくてはわからないような状況において、必要な観点を参考にすると良いと思います。

[補足2] 再現率が極めて低いバグの場合

ここで記載した手順では、なかなか対処しにくい例外として、再現率が100%ではない(再現率が低い)バグがあります。

このようなバグは、バグの再現自体が難しく、限られた時間で何度も再現調査をすることは難しいからです。
中には再現率が低く、エラーログも確認できず、特定のユーザーでしか発生しない。といったA級難易度のバグも存在します。このようなバグに当たった時、経験が少ない方はまずは周りに相談してみるのが良いかもしれません。

長く運用されているソフトウェアには古くからある知見や対策があることが多く、チームの有識者はそのようなノウハウを知っています。メンバーの助けを借りつつ難しいバグへの経験を徐々に積んでいきましょう。

[余談] デバッグが早いエンジニア

エンジニアの中には、調査や分析をしているとは、とても思えないほどの速さでバグの原因を特定できる人がいます。このような人たちは、調査や分析もせずなぜそんなに速くデバッグができるのでしょうか?

個人の見解ですが、デバッグ能力にはバグに対しての「勘所」のようなものがあると思っています。
「なんとなく怪しそうだと思ってコードを見てみたら間違っていることを発見した」みたいなことです。
これらは超能力や才能のような話ではありません。勘とは過去から蓄積した膨大な「経験」に裏打ちされたものなのです。

そのような優秀な人たちもきっと過去では、ここで記載されたような泥臭い調査や分析をしていたと思います(思いたいです)。もしくは今も頭の中で調査やパターンの洗い出しを高速で行っていると私は考えています。

今はデバッグが苦手な人もいるかもしれませんが、地道な経験の蓄積がいずれこのような高速デバッグを可能にしていくれるかもしれません。

Discussion

ログインするとコメントできます