Debugging dengan print() mengharuskan kita mengubah kode, menjalankan ulang program dari awal, lalu mengulangi seluruh siklus setiap kali ada nilai baru yang ingin diperiksa. Debugger mengubah cara kerja itu sepenuhnya: program dijalankan sekali, dijeda di titik yang kita pilih, lalu kita bisa memeriksa variabel dan mengeksekusi kode baris demi baris secara interaktif. Python sudah membawa debugger sendiri bernama pdb di standard library, jadi tidak ada yang perlu diinstal. Pada Chapter ini kita akan menguasai pdb mulai dari breakpoint(), perintah dasarnya, debugging setelah program crash, sampai breakpoint bersyarat.
Memulai dengan breakpoint() #
Sejak Python 3.7, cara resmi menjeda program adalah memanggil fungsi bawaan breakpoint(). Simpan kode berikut sebagai titik_jeda.py, lalu jalankan dengan python3 titik_jeda.py:
def hitung_diskon(total, member):
diskon = total * 0.1 if member else 0
return total - diskon
total = 500000
member = True
breakpoint()
print("Total bayar:", hitung_diskon(total, member))Saat dieksekusi, program berhenti tepat sebelum pernyataan yang mengikuti breakpoint() — interpreter Python menyerahkan kendali ke sesi pdb di terminal:
> titik_jeda.py(9)<module>()
-> print("Total bayar:", hitung_diskon(total, member))
(Pdb) p total
500000
(Pdb) p member
True
(Pdb) c
Total bayar: 450000.0Mari kita baca transkrip di atas:
- Baris
> titik_jeda.py(9)<module>(): header yang menyatakan file, nomor baris berikutnya yang akan dieksekusi, dan konteksnya<module>karena masih di tingkat atas modul. Di komputer Anda path lengkapnya ikut ditampilkan. - Baris
-> print(...): kode pada baris 9 yang belum tereksekusi. p totaldanp member: perintahp(print) mengevaluasi ekspresi apa pun pada frame saat ini dan mencetak hasilnya.c(continue): menjalankan sisa program sampai selesai, yang mencetakTotal bayar: 450000.0.
Perintah Dasar pdb #
Kendali penuh diperoleh dengan menjalankan program lewat python3 -m pdb nama_file.py. Simpan berikut sebagai diskon.py, lalu jalankan dengan perintah tersebut:
def hitung_diskon(total, member):
if member:
diskon = total * 0.1
else:
diskon = 0
return total - diskon
def main():
total = 500000
member = True
print("Total bayar:", hitung_diskon(total, member))
main()Perintah yang paling sering dipakai di sesi pdb hanya segelintir:
| Perintah | Nama lengkap | Fungsi |
|---|---|---|
l | list | menampilkan kode di sekitar baris saat ini |
b N | break | memasang breakpoint di baris N |
c | continue | berjalan sampai breakpoint berikutnya atau program selesai |
n | next | mengeksekusi satu baris; pemanggilan function dilewati utuh |
s | step | mengeksekusi satu baris; masuk ke dalam function yang dipanggil |
p eksp | mencetak nilai ekspresi pada frame saat ini | |
q | quit | keluar dari debugger |
Berikut transkrip sesi nyata pada diskon.py — amati bagaimana breakpoint dipasang di baris 12, lalu eksekusi dimasukkan ke dalam hitung_diskon():
> diskon.py(1)<module>()
-> def hitung_diskon(total, member):
(Pdb) l
1 -> def hitung_diskon(total, member):
2 if member:
3 diskon = total * 0.1
4 else:
5 diskon = 0
6 return total - diskon
7
8
9 def main():
10 total = 500000
11 member = True
(Pdb) b 12
Breakpoint 1 at diskon.py:12
(Pdb) c
> diskon.py(12)main()
-> print("Total bayar:", hitung_diskon(total, member))
(Pdb) p total
500000
(Pdb) p member
True
(Pdb) s
--Call--
> diskon.py(1)hitung_diskon()
-> def hitung_diskon(total, member):
(Pdb) p total, member
(500000, True)
(Pdb) n
> diskon.py(2)hitung_diskon()
-> if member:
(Pdb) n
> diskon.py(3)hitung_diskon()
-> diskon = total * 0.1
(Pdb) n
> diskon.py(6)hitung_diskon()
-> return total - diskon
(Pdb) p diskon
50000.0
(Pdb) c
Total bayar: 450000.0
The program finished and will be restarted
> diskon.py(1)<module>()
-> def hitung_diskon(total, member):
(Pdb) qMari kita bedah momen-momen penting pada transkrip:
b 12laluc: breakpoint dipasang di baris 12 (pemanggilanhitung_diskon()), lalucmelempar eksekusi sampai baris tersebut di dalammain().sversusn: karena kursor berada di barisprint(... hitung_diskon(...)), perintahsmasuk ke dalam functionhitung_diskon()— ditandai header--Call--. Seandainya kita memakain, seluruh panggilan function akan dieksekusi utuh dan kursor melompat ke baris berikutnya.p total, member:pmenerima ekspresi Python apa pun, termasuk tuple dua nilai.p diskonsetelah baris 3 tereksekusi: nilai antara50000.0berhasil diperiksa tanpa satu pun perubahan kode.
Perbedaan n dan s adalah inti pdb: gunakan n saat yakin sebuah function benar, gunakan s saat curiga isi function itulah sumber masalahnya.
Debugging Post-mortem #
Skenario lain yang sering terjadi: program sudah crash, traceback sudah tercetak, dan kita ingin memeriksa kondisi variabel tepat di titik kegagalan. Inilah mode post-mortem. Simpan berikut sebagai pesanan.py:
def ambil_harga(produk):
return DAFTAR_HARGA[produk]
DAFTAR_HARGA = {"kopi": 25000, "teh": 18000}
print(ambil_harga("susu"))Jalankan dengan python3 -m pdb -c continue pesanan.py — opsi -c continue menyuruh pdb menjalankan program sampai crash, lalu berhenti pada frame yang gagal:
Traceback (most recent call last):
File "pesanan.py", line 7, in <module>
print(ambil_harga("susu"))
^^^^^^^^^^^^^^^^^^^
File "pesanan.py", line 2, in ambil_harga
return DAFTAR_HARGA[produk]
~~~~~~~~~~~~^^^^^^^^
KeyError: 'susu'
Uncaught exception. Entering post mortem debugging
Running 'cont' or 'step' will restart the program
> pesanan.py(2)ambil_harga()
-> return DAFTAR_HARGA[produk]
(Pdb) p produk
'susu'
(Pdb) p DAFTAR_HARGA
{'kopi': 25000, 'teh': 18000}Tanpa mengubah satu baris kode pun, kita bisa memastikan bahwa produk bernilai 'susu' sementara dictionary harga memang tidak memiliki kunci tersebut. Ketik q untuk keluar dari sesi.
Breakpoint Bersyarat #
Loop yang berjalan ribuan kali membuat breakpoint biasa tidak praktis karena eksekusi berhenti di setiap iterasi. pdb mengizinkan kondisi ditambahkan langsung pada breakpoint dengan pola b N, kondisi. Simpan berikut sebagai antrian.py:
def proses_pesanan(pesanan_id, prioritas):
print("memproses", pesanan_id, "prioritas:", prioritas)
daftar = [(4128, "reguler"), (4129, "cepat")]
for pesanan_id, prioritas in daftar:
proses_pesanan(pesanan_id, prioritas)Berikut transkrip sesi dengan python3 -m pdb antrian.py:
> antrian.py(1)<module>()
-> def proses_pesanan(pesanan_id, prioritas):
(Pdb) b 2, prioritas == "cepat"
Breakpoint 1 at antrian.py:2
(Pdb) c
memproses 4128 prioritas: reguler
> antrian.py(2)proses_pesanan()
-> print("memproses", pesanan_id, "prioritas:", prioritas)
(Pdb) p pesanan_id
4129
(Pdb) c
memproses 4129 prioritas: cepat
The program finished and will be restarted
> antrian.py(1)<module>()
-> def proses_pesanan(pesanan_id, prioritas):
(Pdb) qIterasi pertama (4128, reguler) berjalan tanpa henti karena kondisi prioritas == "cepat" bernilai False; eksekusi baru berhenti ketika pesanan 4129 memenuhi kondisi. Pola ini adalah cara membidik iterasi spesifik pada data berukuran besar.
Debugger di Editor #
Editor modern menyematkan antarmuka debugger yang jauh lebih nyaman secara visual, dan di balik layar konsepnya sama persis dengan pdb. Di VS Code, cukup klik titik merah di kiri nomor baris untuk memasang breakpoint, lalu tekan F5 untuk menjalankan program dalam mode debugging. Panel kiri menampilkan nama variabel beserta nilainya secara real-time, panel Watch memantau ekspresi pilihan kita, tombol Step Over/Into/Out masing-masing setara dengan n, s, dan keluar function, dan setiap exception yang tidak tertangani otomatis menghentikan eksekusi layaknya mode post-mortem. Editor lain seperti PyCharm menyediakan pengalaman serupa.
Dengan pdb dan breakpoint bersyarat di tangan, kita sudah bisa membedah program yang macet di titik mana pun. Bab berikutnya menggeser fokus dari memperbaiki bug ke disiplin yang lebih besar: merekam jejak kesehatan program secara sistematis lewat module logging.