Debugger adalah alat untuk menghentikan program di tengah jalan, membaca keadaan variabelnya, lalu melangkah baris demi baris untuk melihat baris mana yang membuat perilaku mulai menyimpang. Topik ini mempraktikkan kemampuan inti debugger di IDE, yaitu breakpoint, melangkah dan memantau variabel, breakpoint bersyarat, dan evaluate expression, semuanya diperagakan pada bug yang bisa dijalankan sendiri. Tujuan akhirnya sederhana, yaitu mengganti kebiasaan menebak penyebab bug dengan kebiasaan mengamati program pada titik kejadian.
Bug yang paling jelas jejaknya di debugger#
Program berikut menghitung total dan rata-rata nilai ujian, tetapi hasilnya jelas-jelas salah, karena rata-rata dari nilai 72, 85, 90, dan 78 semestinya 81.25. Menjalankannya lebih dahulu memastikan keluhannya nyata.
public class LaporanNilai {
public static void main(String[] args) {
int[] nilai = {72, 85, 90, 78};
int total = 0;
for (int i = 1; i < nilai.length; i++) {
total += nilai[i];
}
double rataRata = (double) total / nilai.length;
System.out.println("total nilai : " + total);
System.out.printf("rata-rata : %.1f%n", rataRata);
}
}total nilai : 253
rata-rata : 63.3Dari keluaran saja, penyebabnya masih tersembunyi, karena total yang terlalu kecil bisa berarti banyak hal. Menebak berantai pun mungkin saja, tetapi debugger menawarkan jalan yang lebih langsung, yaitu menghentikan program di dalam perulangan lalu membaca i, total, dan nilai[i] pada setiap putaran. Keadaan yang teramati langsung menunjukkan bahwa putaran pertama sudah memakai nilai[1], sehingga nilai[0] tidak pernah dihitung.
Cara kerja semacam ini justru sumber keunggulan debugger dibanding menebak. Tebakan berdiri di luar program dan mudah keliru, sedangkan debugger menampilkan fakta pada titik kejadian, kapan pun programnya dihentikan. Untuk itu sesi debug yang baik bergerak dari dugaan kecil menuju pengamatan, bukan dari pengamatan acak menuju kesimpulan yang diburu-buru.
Breakpoint dan mengendalikan eksekusi#
Breakpoint adalah penanda pada satu baris kode yang membuat program berjalan normal sampai baris itu, lalu berhenti sementara sebelum barisnya dieksekusi. Di IDE seperti IntelliJ IDEA dan VS Code, breakpoint dipasang dengan mengeklik tepian kiri editor pada baris yang dituju, dan program dijalankan dengan mode debug alih-alih mode run biasa. Saat program berhenti, baris yang akan dieksekusi berikutnya ditandai, dan seluruh keadaan program dapat dibaca.
Skema di atas memperlihatkan keadaan khas saat program LaporanNilai dijeda pada baris total += nilai[i] di putaran pertama. Panel variabel sudah membocorkan bug, yaitu i bernilai 1 padahal putaran pertama semestinya bernilai 0. Untuk bergerak dari jeda tersebut, debugger menyediakan tiga langkah utama yang perlu dipahami bedanya.
- Step over menjalankan baris saat ini secara utuh lalu berhenti lagi di baris berikutnya, tanpa masuk ke dalam metode yang dipanggil.
- Step into masuk ke dalam metode yang dipanggil pada baris saat ini, berguna saat bagian yang dicurigai justru berada di dalamnya.
- Resume atau lanjut menjalankan program sampai breakpoint berikutnya atau sampai tuntas, sehingga putaran berikutnya dapat diperiksa dengan cepat.
Mengombinasikan ketiganya pada contoh di atas menuntun langsung ke penyebabnya. Setiap resume membawa i naik satu per satu, total terisi dari nilai[1] sampai nilai[3], dan tidak ada satu pun putaran yang menyentuh elemen pertama. Perbaikannya pun satu karakter saja, yaitu mengganti awal perulangan dari i = 1 menjadi i = 0.
Breakpoint bersyarat dan watch#
Breakpoint bersyarat adalah breakpoint yang hanya menghentikan program bila ekspresi tertentu bernilai benar, sehingga program tidak berhenti di setiap putaran. Kemampuan ini menyelamatkan saat bug hanya muncul untuk satu kasus di antara ribuan putaran. Program berikut menyajikan kasus semacam itu, yaitu rekap waktu pelayanan per loket dengan satu loket yang belum melayani siapa pun.
public class RekapLoket {
public static void main(String[] args) {
String[] nama = {"Loket A", "Loket B", "Loket C", "Loket D"};
int[] pelanggan = {24, 31, 0, 18};
int[] totalMenit = {48, 93, 0, 36};
for (int i = 0; i < nama.length; i++) {
double rataRata = (double) totalMenit[i] / pelanggan[i];
System.out.printf("%s : %.1f menit per pelanggan%n", nama[i], rataRata);
}
}
}Loket A : 2.0 menit per pelanggan
Loket B : 3.0 menit per pelanggan
Loket C : NaN menit per pelanggan
Loket D : 2.0 menit per pelangganKeluaran NaN pada Loket C tampak nyaris tanpa jejak, karena pembagian nol oleh nol pada double tidak melempar exception dan hanya menghasilkan nilai khusus itu. Dengan breakpoint biasa, program berhenti empat kali untuk satu kejadian yang menarik. Dengan klik kanan pada breakpoint lalu mengisi syarat pelanggan[i] == 0, program hanya berhenti tepat di putaran Loket C, dan penyebabnya terlihat seketika.
Sementara itu, panel watch menyimpan ekspresi yang dihitung ulang di setiap jeda. Untuk program di atas, ekspresi seperti totalMenit[i] * 1.0 / pelanggan[i] dapat diletakkan di watch, sehingga perhitungan yang dicurigai diamati langsung tanpa menambah satu baris println pun pada kode. Kemampuan sejenis juga tersedia dalam bentuk yang lebih terpasang, yaitu watchpoint yang menghentikan program saat sebuah field berubah nilainya. Untuk pertanyaan “field ini diubah oleh siapa”, watchpoint lebih tepat daripada menempatkan breakpoint di setiap metode yang mungkin mengubahnya. Tidak semua IDE menamainya sama, tetapi IntelliJ IDEA dan VS Code sama-sama menyediakannya untuk field Java.
Evaluate expression#
Evaluate expression adalah fitur untuk menjalankan ekspresi Java apa pun pada saat program sedang berhenti, memakai keadaan variabel yang nyata di titik jeda tersebut. Di IntelliJ IDEA fiturnya diakses dengan Evaluate Expression, sedangkan di VS Code bernama Debug Console, dan keduanya bekerja dengan prinsip yang sama. Ekspresi yang diuji dapat membaca variabel, memanggil metode, bahkan mengubah isi objek.
Fitur ini paling berguna untuk mencoba dugaan tanpa mengubah kode. Ketika ada kecurigaan bahwa rataRata harusnya dihitung dengan pembagi nilai.length - 1, ekspresi (double) total / (nilai.length - 1) cukup dievaluasi di titik jeda untuk melihat hasilnya, tanpa menyunting kode dan menjalankan ulang dari awal. Dugaan yang terbukti keliru pun dibuang tanpa meninggalkan sisa suntingan apa pun.
Satu kehati-hatian perlu diingat, yaitu evaluate expression yang mengubah keadaan program membuat program selanjutnya berjalan dalam keadaan yang sudah dimodifikasi. Untuk pelacakan bug, biasakan memakai ekspresi yang hanya membaca, dan simpan perubahan untuk kode produksi yang benar-benar diperbaiki.
Kapan debugger lebih tepat daripada println#
Menambah System.out.println cepat untuk satu nilai, tetapi cara itu menuntut suntingan, kompilasi ulang, dan keberuntungan menempatkan baris cetak di tempat yang tepat. Debugger menggantikan siklus tersebut dengan jeda yang dapat dipindah-pindah, variabel yang selalu terbaca, dan langkah yang bisa diulang tanpa menyentuh kode. Untuk bug yang penyebabnya belum terpetakan, debugger hampir selalu lebih cepat.
Pilihan alat tetap mengikuti situasinya. Nilai yang cukup dicetak sekali untuk laporan program lebih alami lewat println. Program yang sudah berjalan di server biasanya tidak bisa dipasangi debugger, dan di situ alat diagnostik JVM seperti thread dump yang lebih tepat. Debugger adalah alat untuk lingkungan pengembangan, dan keahlian menggunakannya meliputi juga keberanian menutupnya kembali saat alat lain lebih cocok.
Kesalahan umum#
- Meninggalkan breakpoint lama yang masih aktif. Program berikutnya berhenti di tempat tak terduga dan kecepatan mode debug terasa menyebalkan tanpa alasan. Bersihkan breakpoint sebelum berpindah pekerjaan.
- Melangkah tanpa hipotesis. Menekan step over berulang-ulang tanpa memperkirakan hasil yang diharapkan sama saja dengan membaca ulang kode. Tetapkan dugaan kecil sebelum setiap langkah, lalu uji dugaan itu dengan keadaan variabel.
- Mengubah kode saat jeda tanpa menjalankan ulang. Suntingan di tengah sesi debug sering tidak langsung berlaku atau berlaku sebagian, sehingga pengamatan menjadi keliru. Jalankan ulang sesi debug setelah kode berubah.
- Memperbaiki gejala tanpa menelusuri penyebab. Mengganti pembagi agar NaN hilang menutup jejak bug, bukan memperbaikinya. Telusuri dulu nilai yang mengalir hingga titik kejadian.
- Menjalankan mode debug pada konfigurasi yang berbeda dari mode run biasa. Program yang dijalankan dengan argumen, profil, atau data berbeda bisa menampilkan perilaku yang tidak pernah terjadi di jalur normal.
Ringkasan#
- Breakpoint menghentikan program pada baris tertentu, lalu step over, step into, dan resume mengendalikan gerak eksekusinya.
- Panel variabel dan watch memperlihatkan keadaan nyata pada titik jeda, sehingga penyebab bug seperti indeks yang tergeser terbaca langsung.
- Breakpoint bersyarat menghentikan program hanya saat ekspresi tertentu bernilai benar, cocok untuk bug yang muncul pada satu kasus di antara banyak putaran.
- Evaluate expression menguji dugaan pada keadaan program yang berhenti tanpa menyunting kode, dan debugger paling menguntungkan untuk bug yang penyebabnya belum terpetakan.
Referensi#
- Debugging Code — IntelliJ IDEA Help — dokumentasi resmi breakpoint, langkah eksekusi, dan evaluate expression di IntelliJ IDEA.
- Debugging Java — Visual Studio Code — dokumentasi resmi sesi debug Java di VS Code beserta panel variabel dan breakpoint bersyaratnya.