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:

python
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:

text
> 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.0

Mari kita baca transkrip di atas:

  1. 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.
  2. Baris -> print(...): kode pada baris 9 yang belum tereksekusi.
  3. p total dan p member: perintah p (print) mengevaluasi ekspresi apa pun pada frame saat ini dan mencetak hasilnya.
  4. c (continue): menjalankan sisa program sampai selesai, yang mencetak Total 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:

python
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:

PerintahNama lengkapFungsi
llistmenampilkan kode di sekitar baris saat ini
b Nbreakmemasang breakpoint di baris N
ccontinueberjalan sampai breakpoint berikutnya atau program selesai
nnextmengeksekusi satu baris; pemanggilan function dilewati utuh
sstepmengeksekusi satu baris; masuk ke dalam function yang dipanggil
p ekspprintmencetak nilai ekspresi pada frame saat ini
qquitkeluar dari debugger

Berikut transkrip sesi nyata pada diskon.py — amati bagaimana breakpoint dipasang di baris 12, lalu eksekusi dimasukkan ke dalam hitung_diskon():

text
> 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) q

Mari kita bedah momen-momen penting pada transkrip:

  1. b 12 lalu c: breakpoint dipasang di baris 12 (pemanggilan hitung_diskon()), lalu c melempar eksekusi sampai baris tersebut di dalam main().
  2. s versus n: karena kursor berada di baris print(... hitung_diskon(...)), perintah s masuk ke dalam function hitung_diskon() — ditandai header --Call--. Seandainya kita memakai n, seluruh panggilan function akan dieksekusi utuh dan kursor melompat ke baris berikutnya.
  3. p total, member: p menerima ekspresi Python apa pun, termasuk tuple dua nilai.
  4. p diskon setelah baris 3 tereksekusi: nilai antara 50000.0 berhasil 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:

python
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:

text
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:

python
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:

text
> 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) q

Iterasi 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.