Bug yang hanya muncul di produksi itu menyebalkan. Kamu sudah menaruh print() di lima titik, me-restart server berkali-kali, tapi nilai variabelnya tetap tidak masuk akal. Kalau itu rutinitasmu, ada alat yang jauh lebih cepat: pdb, debugger bawaan Python. Dengan pdb kamu bisa menghentikan program tepat di baris tertentu, memeriksa nilai variabel saat itu juga, lalu menjalankan kode baris demi baris — tanpa menebak-nebak lewat print().
Kenapa print() Tidak Cukup
Debugging pakai print() punya tiga masalah yang sulit dihindari. Pertama, kamu harus menebak variabel mana yang bermasalah sebelum menulis perintah cetak — padahal justru itu yang sedang kamu cari. Kedua, setiap tebakan baru berarti menjalankan ulang program dari awal. Ketiga, begitu bug ketemu, kamu harus membersihkan semua print() yang tadi disebar. Debugger menyelesaikan ketiganya sekaligus: berhenti di mana saja, melihat semua variabel dalam scope, dan tidak meninggalkan kode sampah.
Memulai dengan breakpoint()
Sejak Python 3.7 kamu cukup menulis breakpoint() di baris yang ingin diperiksa. Python akan berhenti di situ dan membuka prompt interaktif (Pdb). Tidak perlu import pdb manual. Mari pakai contoh nyata. Perhatikan fungsi berikut — ia seharusnya hanya menjumlahkan pesanan berstatus lunas, tapi hasilnya salah.
def proses_pesanan(daftar_pesanan):
total = 0
for pesanan in daftar_pesanan:
breakpoint() # berhenti di sini tiap iterasi
if pesanan["status"] == "lunas":
total += pesanan["total"]
elif pesanan["status"] == "pending":
total += pesanan["total"] # bug: pending ikut dihitung
return total
pesanan = [
{"id": 1, "total": 100_000, "status": "lunas"},
{"id": 2, "total": 50_000, "status": "pending"},
{"id": 3, "total": 75_000, "status": "lunas"},
]
print(proses_pesanan(pesanan))
Simpan sebagai debug_contoh.py lalu jalankan dengan python debug_contoh.py. Program berhenti di breakpoint() dan prompt (Pdb) muncul. Sekarang kamu punya kendali penuh atas alur eksekusi.
Kalau kamu pakai VS Code atau PyCharm, kamu tidak perlu mengetik perintah pdb manual. Klik titik merah di kiri nomor baris untuk memasang breakpoint, lalu jalankan lewat menu Run > Start Debugging. Tetap pahami pdb di terminal, karena di server produksi yang hanya bisa diakses via SSH kamu tidak punya GUI.
Perintah pdb yang Wajib Kamu Hafal
n jalankan baris berikutnya, tanpa masuk ke dalam fungsi
s masuk ke dalam fungsi yang dipanggil (step into)
c lanjutkan sampai breakpoint berikutnya atau program selesai
p x cetak nilai variabel x
pp x cetak dengan pretty-print, nyaman untuk dict/list besar
l tampilkan kode di sekitar baris sekarang
w tampilkan call stack, di mana posisi kamu sekarang
u/d naik/turun satu tingkat di call stack
b 12 pasang breakpoint di baris 12
q keluar dari debugger
Dua yang paling sering saya pakai adalah n dan p. Perbedaan n dan s penting: kalau baris berikutnya memanggil fungsi, n mengeksekusinya sekaligus lalu berhenti di baris setelahnya, sedangkan s melompat masuk ke dalam fungsi itu. Untuk bug contoh tadi kita cukup memakai n.
Menelusuri Bug Contoh
Kembali ke prompt (Pdb). Ketik n lalu Enter untuk mengeksekusi baris if. Ketik p pesanan dan kamu akan melihat isi dict pesanan pertama. Lanjutkan dengan n beberapa kali sampai iterasi kedua — saat pesanan["status"] bernilai 'pending'.
Di titik ini, ketik n sekali lagi. Program masuk ke cabang elif dan menambahkan pesanan["total"] ke total — padahal pesanan pending seharusnya diabaikan. Ketik p total untuk melihat nilainya bertambah tanpa alasan. Bug ketemu tanpa satu pun print(). Ketik q untuk keluar, lalu perbaiki logikanya: hapus baris penambahan di cabang elif, dan jangan lupa buang baris breakpoint() sebelum commit.
Breakpoint Bersyarat
Menaruh breakpoint() di dalam loop berarti program berhenti di setiap iterasi. Kalau list kamu berisi 10.000 item, itu buang-buang waktu. Pasang kondisi supaya debugger hanya berhenti saat situasi yang kamu incar terjadi.
for pesanan in daftar_pesanan:
if pesanan["status"] == "pending":
breakpoint() # hanya berhenti untuk pesanan pending
total += pesanan["total"]
Cara lain tanpa menyentuh kode: jalankan python -m pdb debug_contoh.py, lalu di prompt ketik b 7, pesanan["status"] == "pending". Ini memasang breakpoint di baris 7 yang hanya aktif saat kondisinya bernilai True.
Post-Mortem: Debugging Setelah Crash
Bug yang melempar exception paling cepat ditangani dengan post-mortem debugging. Program dibiarkan crash, lalu kamu masuk ke state persis sebelum exception dilempar dan memeriksa semua variabel saat itu. Berikut contoh yang melempar IndexError.
def hitung_rasio(data):
hasil = []
for i in range(len(data)):
hasil.append(data[i] / data[i + 1]) # IndexError di elemen terakhir
return hasil
hitung_rasio([4, 8, 16])
Jalankan dengan python -m pdb debug_crash.py, ketik c untuk continue. Program crash, dan pdb otomatis membuka prompt di baris yang melempar IndexError. Ketik p i — kamu akan melihat i == 2, lalu data[i + 1] mencoba mengakses indeks 3 yang tidak ada. Akar masalahnya langsung terlihat.
Kalau program sudah terlanjur crash tanpa pdb, masih ada jalan keluar: panggil pdb.pm() di interpreter yang sama untuk masuk ke state crash terakhir. Untuk mempelajari cara menangkap exception itu sendiri sebelum ia membunuh program, baca panduan exception handling di Python.
import pdb
def hitung_rasio(data):
hasil = []
for i in range(len(data)):
hasil.append(data[i] / data[i + 1])
return hasil
hitung_rasio([4, 8, 16]) # IndexError
# setelah traceback muncul, ketik di prompt yang sama:
pdb.pm() # buka debugger di state crash terakhir
Post-mortem debugging hanya bekerja di sesi interpreter yang sama dengan tempat program crash. Kalau kamu menjalankan script lewat python -m pdb, cukup ketik c dan pdb berhenti otomatis saat exception terjadi — tidak perlu pdb.pm().
Debugging yang Lebih Cepat dengan icecream
Ada kalanya print() masih berguna, misalnya untuk memantau alur eksekusi cepat tanpa prompt interaktif. Masalah klasiknya: kamu lupa variabel mana yang sedang dicetak, apalagi kalau outputnya cuma deretan angka. Library icecream memperbaikinya dengan mencetak nama ekspresi beserta nilainya.
pip install icecream
from icecream import ic
def proses_pesanan(daftar_pesanan):
total = 0
for pesanan in daftar_pesanan:
ic(pesanan["id"], pesanan["status"], pesanan["total"])
total += pesanan["total"]
return total
proses_pesanan([
{"id": 1, "total": 100_000, "status": "lunas"},
{"id": 2, "total": 50_000, "status": "pending"},
])
Outputnya menyebut nama variabel secara eksplisit, jadi kamu tidak perlu menebak angka milik siapa:
ic| pesanan["id"]: 1, pesanan["status"]: 'lunas', pesanan["total"]: 100000
ic| pesanan["id"]: 2, pesanan["status"]: 'pending', pesanan["total"]: 50000
Pilih Alat Sesuai Masalahnya
Aturan praktis saya: bug yang bisa direproduksi di lokal, selesaikan dengan breakpoint() dan telusuri pakai n/p. Bug yang hanya muncul di produksi, tangani lewat logging terstruktur atau icecream — menanam debugger interaktif di server produksi itu berisiko. Exception yang melempar, langsung pakai post-mortem debugging.
Latihan tercepat: ambil satu fungsi di proyekmu yang sedang bermasalah, tanam breakpoint(), telusuri dengan n sampai kamu menemukan baris yang menyimpang. Kalau yang gagal justru test suite, pdb juga bisa dipanggil dari dalam test — pelajari caranya di panduan pytest.