Ketika hasil program tidak sesuai harapan, refleks pertama banyak programmer adalah menambah System.out.println di sana-sini, menjalankan ulang, lalu membaca deretan teks. Cara itu memang berhasil untuk kasus sederhana, tetapi cepat menjadi tidak praktis: kita harus mengubah kode, menunggu kompilasi ulang, dan menambal kode sementara yang berisiko lupa dibuang. Debugger mengubah pendekatan tersebut menjadi pause-inspect-modify: menghentikan eksekusi di titik mana pun, memeriksa nilai variabel secara langsung, bahkan mengubah nilai saat program berhenti, tanpa menyentuh satu baris kode sumber pun. Pada Chapter ini kita akan membandingkan kedua pendekatan, lalu mencoba jdb — debugger CLI yang sudah terpasang bersama JDK — serta melihat konfigurasi debug di IntelliJ IDEA dan VS Code.
Keterbatasan Debugging dengan println #
Mari kita mulai dari cara lama. Program berikut mencari nilai terbesar dalam array dan “mendebug” pencariannya dengan mencetak status setiap iterasi:
public class DebugPrintln {
public static void main(String[] args) {
int[] data = {8, 3, 15, 6, 21};
int terbesar = data[0];
for (int i = 1; i < data.length; i++) {
// "Debugging" dengan println: mencetak semua, semua waktu
System.out.println("[debug] i=" + i
+ " data[i]=" + data[i]
+ " terbesar=" + terbesar);
if (data[i] > terbesar) {
terbesar = data[i];
}
}
System.out.println("Nilai terbesar: " + terbesar);
}
}Output yang Dihasilkan:
[debug] i=1 data[i]=3 terbesar=8
[debug] i=2 data[i]=15 terbesar=8
[debug] i=3 data[i]=6 terbesar=15
[debug] i=4 data[i]=21 terbesar=15
Nilai terbesar: 21Perhatikan tiga kelemahan yang muncul dari output di atas. Pertama, kita hanya melihat variabel yang sengaja dicetak; jika ternyata data[i] perlu diperiksa isinya lebih dalam, kode harus diubah lagi. Kedua, semua iterasi tercetak meski hanya iterasi keempat yang mencurigakan — tidak ada cara berhenti tepat di kondisi yang menarik. Ketiga, jejak [debug] tersebut harus dibersihkan manual setelah masalah selesai, dan satu baris yang lupa terbuang akan menjadi derau di log produksi.
Tiga Kemampuan Inti Debugger #
Debugger berbasis JPDA (Java Platform Debugger Architecture) bekerja lewat jalur komunikasi khusus antara JVM dan debugger: JVM menjalankan program dengan agent yang melaporkan peristiwa — breakpoint tercapai, langkah selesai, exception dilempar — sementara debugger mengirim perintah berhenti, membaca variabel, atau memodifikasi nilai. Arsitektur inilah yang dipakai bersama oleh jdb, IntelliJ IDEA, Eclipse, dan VS Code.
Tiga kemampuan yang membedakan debugger dari println:
- Pause: menghentikan semua thread tepat di baris atau kondisi tertentu, misalnya hanya saat counter bernilai 3.
- Inspect: melihat seluruh variabel yang hidup di scope saat itu — field, lokal, parameter — lengkap dengan isinya, tanpa satu pun pernyataan cetak.
- Modify: mengubah nilai variabel saat program berhenti, lalu melanjutkan eksekusi untuk menguji hipotesis “jika nilainya X, apa yang terjadi?”.
jdb Debugger Bawaan JDK #
Sebelum membuka IDE, mari kita buktikan bahwa kemampuan ini melekat pada JDK itu sendiri melalui jdb. Program kecil berikut menjadi kelinci percobaan kita:
public class LoopHitung {
public static void main(String[] args) {
int total = 0;
for (int i = 1; i <= 5; i++) {
total += i;
}
System.out.println("Total: " + total);
}
}Output yang Dihasilkan:
Total: 15Sesi jdb nyata berikut diperoleh dengan mengompilasi berkas tersebut (javac -g LoopHitung.java, opsi -g agar nama variabel lokal terbaca), lalu menjalankan jdb -classpath . LoopHitung dan mengetik perintah pada prompt:
Initializing jdb ...
> Deferring breakpoint LoopHitung:5.
It will be set after the class is loaded.
> run LoopHitung
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
>
VM Started: Set deferred breakpoint LoopHitung:5
Breakpoint hit: "thread=main", LoopHitung.main(), line=5 bci=9
5 total += i;
main[1] i = 1
main[1] total = 0
main[1] >
Breakpoint hit: "thread=main", LoopHitung.main(), line=5 bci=9
5 total += i;
main[1] i = 2
main[1] total = 1
main[1] >
Breakpoint hit: "thread=main", LoopHitung.main(), line=5 bci=9
5 total += i;
main[1] Mari kita bedah sesi tersebut langkah demi langkah:
stop at LoopHitung:5: memasang breakpoint pada baris 5 — pernyataantotal += i;di dalam loop.run LoopHitung: memulai JVM target. Karena class belum termuat, breakpoint bersifat deferred dan dipasang setelah classLoopHitungtermuat.Breakpoint hit: "thread=main", ... line=5: eksekusi berhenti tepat sebelum baris 5 dieksekusi; teks di bawahnya menampilkan ulang baris sumber.print ilaluprint total: pada promptmain[1], jdb menjawabi = 1dantotal = 0— kondisi iterasi pertama.cont: melanjutkan eksekusi sampai breakpoint yang sama tersentuh lagi pada iterasi kedua;printkini menunjukkani = 2dantotal = 1, bukti bahwa variabel berubah antar jeda tanpa satu baris kode dicetak dari dalam program.
Konfigurasi Debug di IntelliJ IDEA #
IntelliJ IDEA menyimpan konfigurasi run/debug sebagai berkas XML di dalam direktori .idea/runConfigurations, sehingga konfigurasi dapat dibagikan bersama repositori. Bentuk umumnya seperti berikut:
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="LoopHitung" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="LoopHitung" />
<module name="kursus-java" />
<option name="PASS_PARENT_ENVS" value="true" />
<method v="2" />
</configuration>
</component>Menjalankan konfigurasi tersebut dengan tombol Debug (ikon kutu) membuat IDEA memasang agent JPDA, membuka panel Debugger, dan siap menerima breakpoint yang kita klik di gutter editor. Panel tersebut menampilkan pohon frame tumpukan di kiri dan daftar variabel di kanan — versi bergambar dari perintah where dan locals pada jdb.
Konfigurasi Debug di VS Code #
VS Code memakai berkas launch.json di direktori .vscode untuk keperluan yang sama. Konfigurasi minimal untuk aplikasi Java tampak seperti ini:
{
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "Debug LoopHitung",
"request": "launch",
"mainClass": "LoopHitung"
}
]
}Menekan F5 dengan konfigurasi tersebut menjalankan class utama dalam mode debug; titik merah di gutter berfungsi sebagai breakpoint, dan panel Run and Debug menampilkan variabel, call stack, serta konsol debug. Kedua IDE menawarkan tombol yang sama: Step Over, Step Into, dan Step Out — alat telusur yang akan kita bahas tuntas pada Chapter berikutnya.
Dari sini kita paham bahwa debugger bukan fitur eksotis, melainkan kemampuan standar yang tersedia dari JDK hingga IDE. Yang belum dibahas adalah cara menempatkan hentian secara presisi: pada baris tertentu, hanya saat kondisi terpenuhi, atau saat sebuah field berubah. Chapter berikutnya membahas breakpoints dan watchpoints yang mengatur hal tersebut.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
DebugPrintln.java
Unduh berkaspublic class DebugPrintln {
public static void main(String[] args) {
int[] data = {8, 3, 15, 6, 21};
int terbesar = data[0];
for (int i = 1; i < data.length; i++) {
// "Debugging" dengan println: mencetak semua, semua waktu
System.out.println("[debug] i=" + i
+ " data[i]=" + data[i]
+ " terbesar=" + terbesar);
if (data[i] > terbesar) {
terbesar = data[i];
}
}
System.out.println("Nilai terbesar: " + terbesar);
}
}
LoopHitung.java
Unduh berkaspublic class LoopHitung {
public static void main(String[] args) {
int total = 0;
for (int i = 1; i <= 5; i++) {
total += i;
}
System.out.println("Total: " + total);
}
}