【入門編】printデバッグを卒業!pdbを使ってバグを見つけるスマートな手法 – デバッグ・コード品質・テストツール生産性向上バイブル

こんにちは!日々の開発、本当にお疲れ様です。

Pythonでコードを書いているとき、バグの原因を探るためにとりあえず `print(variable)` を大量に仕込んで実行し、コンソールにあふれかえる文字の海から正解を探し出す……そんな「printデバッグ」をやっていませんよね?

もちろん、ちょっとした値の確認にはprintも手軽で便利です。しかし、プログラムが複雑になり、ループや条件分岐が重なり、さらには外部ライブラリの内部でエラーが起きたとき、printデバッグは途端に無力になります。「なぜこの変数が `None` になるんだ?」「この関数はどこから呼ばれたんだ?」と迷子になり、コードのあちこちに `print` を追加しては消しての繰り返し……。気づけばコードは汚染され、デバッグだけに何時間も溶かしてしまう。

そんな泥臭いデバッグから、今日でスパッと卒業しましょう。

今回は、Python標準の最強デバッガ `pdb` (そしてそれをさらに使いやすくした `IPdb`)を使った、スマートでモダンなバグ追跡の手法を優しく解説します。これをマスターすれば、あなたの開発スピードとコードへの理解度は劇的に跳ね上がりますよ。

—

1. なぜ `print` デバッグを卒業すべきなのか?

まず、なぜ私たちが `pdb` のようなインタラクティブ・デバッガを使うべきなのか、その本質を共有させてください。

`print` デバッグの最大の欠点は「コードを書き換えないと状態が確認できない」「静的なスナップショットしか見られない」という点にあります。

一方、`pdb`(Python DeBugger)を使うと、プログラムの実行を指定した場所でピタッと「一時停止(ブレークポイント)」させることができます。停止したその瞬間から、あなたはまるで神の視点のように、その場にあるすべての変数の書き換え、関数の手動実行、コールスタック(関数が呼び出された履歴)の遡りが自由自在に行えるようになります。

つまり、「推測してprintを仕込む」のではなく、「実行中のプログラムの内部にダイブして直接対話する」ことができるのです。これがどれほど強力で、精神的なストレスを軽減してくれるか、次の章から一緒に体験していきましょう。

—

2. インストールと「持てる者」のセットアップ

Pythonには標準ライブラリとして `pdb` が最初から入っています。そのため、実は `pip install` なしでも動きます。

しかし、現代のプロフェッショナルは、よりリッチで色鮮やかな補完機能がついた `ipdb`(IPythonベースのpdb)を使います。これを入れるだけで、デバッグ中の絶望感が一気にワクワク感へと変わります。

インストールコマンド

ターミナルを開き、以下のコマンドを実行してください。

IPythonの強力な機能を引き継いだ、色付き・補完付きのデバッガをインストール
pip install ipdb

【重要】モダンなデバッグ作法:`breakpoint()` の導入

Python 3.7以降を使っているなら、古い `import pdb; pdb.set_trace()` を書く必要はもうありません。ビルトイン関数である `breakpoint()` を使います。

Pythonの公式な作法として、この関数を実行するだけで、環境変数 `PYTHONBREAKPOINT` に設定されたお好みのデバッガ(今回は `ipdb`)が自動で起動するようになっています。

—

3. HelloWorld的・実践ハンズオン:バグをその場で捕まえる

百聞は一見にしかず。わざとバグを埋め込んだ小さなスクリプトを用意しました。
以下のコードを `debug_sample.py` という名前で保存してください。

debug_sample.py
def calculate_discount(price, rate):
# 割引率を計算する関数だが、rateに文字列が渡されるバグが潜んでいると仮定
discounted_price = price (1 – rate)
return discounted_price

def process_cart(items):
total = 0
for item in items:
# ここで意図しないデータ型が混入するシナリオ
price = item[“price”]
rate = item[“rate”]

# 処理を一時停止させたい箇所に breakpoint() を挿入
breakpoint()

final_price = calculate_discount(price, rate)
total += final_price

return total

if __name__ == “__main__”:
# モックのカートデータ
cart_items = [
{“price”: 1000, “rate”: 0.1},
{“price”: 2000, “rate”: “0.2”}, # おっと! rateが数値ではなく文字列になっている!
]

result = process_cart(cart_items)
print(f”合計金額: {result}”)

このコードを実行してみましょう。

python debug_sample.py

実行結果とデバッガの起動

1回目のループ(正常系:`1000`円、`0.1`割引)はそのまま通過し、2回目のループ(異常系:`2000`円、`”0.2″`文字列)の `breakpoint()` に到達した瞬間、ターミナル画面が次のように切り替わります。

> /path/to/debug_sample.py(15)process_cart()
-> final_price = calculate_discount(price, rate)
(Pdb)

おおっ!プログラムがここで完全にフリーズし、私たちの入力を待っています。これがブレークポイントの力です。

—

4. 現場で絶対に使う基本コマンド4選

デバッガのプロンプト `(Pdb)` が表示されたら、以下の4つの呪文(コマンド)だけ覚えておけば、大半のバグは瞬殺できます。

1. `p` (print) または 変数名そのまま

  • 変数の中身を確認する
  • 例: `p price` または単に `price` と打つと `2000` が返る

2. `n` (next)

  • 次の行へ進む(関数の中には入らず、現在のスコープの次へ)

3. `s` (step)

  • 関数の中へ飛び込む(ステップイン)。`calculate_discount` の中身を見たい時に使う

4. `c` (continue)

  • デバッグを終了し、次のブレークポイント(またはプログラム終了)まで一気に走らせる

実際に試してみましょう。現在のコンソールで以下のように入力してみてください。

(Pdb) price
2000
(Pdb) rate
‘0.2’
(Pdb) type(rate)

なんと! `rate` が `` であることが一目瞭然でわかりました。「なるほど、文字列と浮動小数点数を掛け算しようとしたからエラー(あるいは予期せぬ挙動)になるんだな」と、ここで完全に原因が特定できます。

—

5. コールスタックで「どこから呼ばれたか」を追跡する

変数の値だけでなく、「この関数がどこから、どんな経緯で呼ばれたのか」を知ることもデバッグでは極めて重要です。

`ipdb` のプロンプトで `bt` (backtrace または where) と入力してみてください。

(Pdb) bt
/path/to/debug_sample.py(27)()
-> result = process_cart(cart_items)
/path/to/debug_sample.py(15)process_cart()
-> final_price = calculate_discount(price, rate)
> /path/to/debug_sample.py(5)calculate_discount()
-> discounted_price = price (1 – rate)

上から順に、「メインのモジュールから `process_cart` が呼ばれ、その中の何行目から `calculate_discount` が呼び出されたか」という全履歴(コールスタック)が表示されます。

もしフレームを移動したい場合は、以下のコマンドが使えます。

  • `u` (up): 呼び出し元のフレームへ移動する
  • `d` (down): 呼び出し先のフレームへ戻る

これにより、「誰がこの間違った引数を渡したのか?」を上流へ遡って調査することが可能です。

—

6. 先輩エンジニアからの実践アドバイス

最後に、実務で `pdb` / `ipdb` を使い倒すためのちょっとしたコツをお伝えします。

  • 本番環境への残し込みに注意

`breakpoint()` は非常に便利ですが、うっかりコードに書き残したままGitにコミットし、本番サーバー(CI/CDや本番コンテナ)で実行されてしまうと、デバッガが立ち上がったままプロセスがユーザーからの入力を待ち続け、アプリケーションが完全にフリーズ(ハングアップ)します。コミット前には必ず `git diff` で `breakpoint()` が残っていないか確認する習慣をつけましょう。

  • 条件付きブレークポイントの活用

「100回目のループでだけバグる」という場合、毎回 `n` を100回押すのは苦行です。IPdbでは、条件を満たしたときだけ止める高度な設定も可能です(興味がある人は `b 15, item[‘price’] == 2000` などの記法を調べてみてください)。

—

おわりに

いかがでしたでしょうか?
print文を削除しては実行し、また追加して……という不毛なデバッグの日々は、今日で終わりにしましょう。

`pdb` / `ipdb` を使ってコードの内部へダイブし、変数の息づかいや関数の動きを直接感じ取れるようになると、バグを探す時間が「苦痛」から「謎解きの快感」へと変わっていきます。

これをマスターすれば、あなたの毎日のコーディング、そしてエラー対応のスピードは劇的に楽になりますよ。ぜひ、今日の開発から取り入れてみてくださいね!

タイトルとURLをコピーしました