Program yang berhenti di breakpoint ibarat foto: kita melihat kondisi pada satu momen, tetapi belum melihat urutan kejadian yang menghasilkan kondisi tersebut. Step-through debugging mengubah foto itu menjadi film — kita menggerakkan eksekusi selangkah demi selangkah, masuk ke dalam method yang dicurigai, melewati method yang sudah dipercaya, lalu keluar saat informasi cukup. Tiga tombol sederhana (step over, step in, step out) plus dua bantuan navigasi (run to cursor, evaluate expression) menutup hampir seluruh kebutuhan investigasi harian. Pada Chapter ini kita akan memahami semantik setiap tombol melalui program yang merekam jejak eksekusi secara bertahap.
Tiga Tombol Dasar #
Semantik ketiga tombol berikut perlu dipahami presisi karena perbedaannya menentukan ke mana eksekusi kita pergi:
| Perintah | Perilaku | Berhenti di |
|---|---|---|
| Step Over | Jalankan baris saat ini seluruhnya, termasuk semua pemanggilan method di dalamnya | Baris berikutnya pada method yang sama |
| Step In | Jika baris memanggil method, masuk ke method itu | Baris pertama method yang dipanggil |
| Step Out | Jalankan sisa method saat ini sampai selesai | Baris pemanggil pada frame di atasnya |
Kesalahan umum pemula adalah menekan step in pada baris System.out.println(...) lalu tersesat di dalam pustaka java.io. Sebaliknya, step over pada baris berisi pemanggilan method sendiri membuat kita melewati logika yang justru dicurigai. Aturan praktisnya: over untuk pustaka dan method yang sudah dipercaya, in untuk method milik kita yang mencurigakan.
Simulasi Jejak Eksekusi Bertahap #
Program berikut menghitung gaji dengan dua method berlapis, lalu mencatat setiap langkah sebagaimana panel debugger akan menampilkannya. Indentasi teks menirukan kedalaman frame yang aktif:
public class SimulasiStep {
public static void main(String[] args) {
System.out.println("[main] mulai, memanggil hitungGaji(40, 25000)");
int gaji = hitungGaji(40, 25000);
System.out.println("[main] step over: gaji = " + gaji);
System.out.println("[main] selesai");
}
static int hitungGaji(int jam, int tarif) {
System.out.println(" [step in] masuk hitungGaji: jam=" + jam
+ ", tarif=" + tarif);
int pokok = jam * tarif;
System.out.println(" [step over] pokok = " + pokok);
int lembur = hitungLembur(5, tarif);
System.out.println(" [step over] lembur = " + lembur);
System.out.println(" [step out] hitungGaji mengembalikan "
+ (pokok + lembur));
return pokok + lembur;
}
static int hitungLembur(int jam, int tarif) {
System.out.println(" [step in] masuk hitungLembur: jam=" + jam);
int hasil = jam * tarif * 2;
System.out.println(" [step out] hitungLembur mengembalikan " + hasil);
return hasil;
}
}Output yang Dihasilkan:
[main] mulai, memanggil hitungGaji(40, 25000)
[step in] masuk hitungGaji: jam=40, tarif=25000
[step over] pokok = 1000000
[step in] masuk hitungLembur: jam=5
[step out] hitungLembur mengembalikan 250000
[step over] lembur = 250000
[step out] hitungGaji mengembalikan 1250000
[main] step over: gaji = 1250000
[main] selesaiMari kita baca jejak tersebut sebagai peta navigasi bertahap:
[main] mulai, memanggil hitungGaji(40, 25000): eksekusi berada dimain, tepat sebelum baris pemanggilan. Menekan step in di sini memindahkan kita ke dalamhitungGaji.[step in] masuk hitungGaji: jam=40, tarif=25000: frame aktif kinihitungGaji; parameter sudah terisi dan terlihat di panel variabel.[step over] pokok = 1000000: menekan step over pada barisint pokok = jam * tarif;mengeksekusi perkalian itu seluruhnya lalu berhenti di baris berikutnya pada frame yang sama.[step in] masuk hitungLembur: jam=5: barishitungLembur(5, tarif)berisi pemanggilan method milik kita, sehingga step in menurunkan frame satu tingkat lagi.[step out] hitungLembur mengembalikan 250000: dari dalamhitungLembur, step out mengeksekusi sisa method dan mengembalikan kita ke pemanggilnya — barislemburdihitungGaji.- Dua baris terakhir
main: setelahhitungGajimengembalikan nilai, step out terakhir mendaratkan eksekusi kembali dimaindengangajisudah terisi.
Urutan main → hitungGaji → hitungLembur → hitungGaji → main itulah yang dilihat debugger sebagai pergerakan tumpukan frame (call stack): setiap step in menambah satu frame, setiap step out melepasnya.
Run to Cursor dan Evaluate Expression #
Dua bantuan navigasi membuat langkah kita lebih efisien. Run to cursor menjalankan program sampai baris tempat kursor berada — breakpoint sementara tanpa mengubah apapun — berguna untuk melewati blok inisialisasi yang sudah jelas benar. Evaluate expression mengevaluasi ekspresi apa pun dalam konteks frame saat ini, termasuk memanggil method, tanpa mengubah kode sumber. Program berikut mensimulasikan isi panel Watches pada satu titik jeda:
public class EvaluasiEkspresi {
public static void main(String[] args) {
int total = 125000;
int diskon = 0;
if (total > 100000) {
// Mensimulasikan panel Watches / Evaluate Expression saat jeda:
System.out.println("[watches] total > 100000 -> " + (total > 100000));
System.out.println("[watches] total - diskon -> " + (total - diskon));
System.out.println("[watches] rupiah(total - 5000) -> " + rupiah(total - 5000));
diskon = 12500;
}
System.out.println("Total bayar: " + rupiah(total - diskon));
}
static String rupiah(int nilai) {
return "Rp" + nilai;
}
}Output yang Dihasilkan:
[watches] total > 100000 -> true
[watches] total - diskon -> 125000
[watches] rupiah(total - 5000) -> Rp120000
Total bayar: Rp112500Poin penting dari simulasi tersebut:
total > 100000dantotal - diskon: debugger mengevaluasi ekspresi dengan nilai variabel hidup saat jeda;diskonmasih0karena penugasan12500belum dieksekusi — informasi urutan yang hanya terlihat saat berhenti.rupiah(total - 5000): evaluate expression juga boleh memanggil method. IntelliJ IDEA menyediakannya lewat pintasanAlt+F8; VS Code lewat perintah Debug: Evaluate.- Efek samping perlu kehati-hatian: evaluasi yang memanggil method dapat mengubah state program (misalnya menambah counter). Gunakan evaluasi untuk membaca, hindari memanggil method yang menulis.
Padanan Perintah di jdb #
Semua kemampuan ini juga tersedia di jdb dengan nama perintah yang berbeda:
| Navigasi IDE | Perintah jdb | Efek |
|---|---|---|
| Step Over | next | Satu baris, pemanggilan method dieksekusi penuh |
| Step In | step | Satu baris, masuk ke method yang dipanggil |
| Step Out | step up | Jalankan sampai method saat ini kembali |
| Langkah bytecode | stepi | Satu instruksi bytecode — untuk kasus sangat detail |
| Lanjut penuh | cont | Jalankan sampai breakpoint berikutnya |
Navigasi bertahap menuntaskan perangkat debugging kita: berhenti di titik penting, bergerak selangkah, dan mengintip nilai hidup. Namun semua itu terjadi di mesin developer — saat program berjalan di server produksi tanpa debugger terpasang, satu-satunya jendela ke perilaku program adalah log. Chapter berikutnya membangun fondasi logging yang benar, mulai dari alasan mengapa System.out.println tidak memadai.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
EvaluasiEkspresi.java
Unduh berkaspublic class EvaluasiEkspresi {
public static void main(String[] args) {
int total = 125000;
int diskon = 0;
if (total > 100000) {
// Mensimulasikan panel Watches / Evaluate Expression saat jeda:
System.out.println("[watches] total > 100000 -> " + (total > 100000));
System.out.println("[watches] total - diskon -> " + (total - diskon));
System.out.println("[watches] rupiah(total - 5000) -> " + rupiah(total - 5000));
diskon = 12500;
}
System.out.println("Total bayar: " + rupiah(total - diskon));
}
static String rupiah(int nilai) {
return "Rp" + nilai;
}
}
SimulasiStep.java
Unduh berkaspublic class SimulasiStep {
public static void main(String[] args) {
System.out.println("[main] mulai, memanggil hitungGaji(40, 25000)");
int gaji = hitungGaji(40, 25000);
System.out.println("[main] step over: gaji = " + gaji);
System.out.println("[main] selesai");
}
static int hitungGaji(int jam, int tarif) {
System.out.println(" [step in] masuk hitungGaji: jam=" + jam
+ ", tarif=" + tarif);
int pokok = jam * tarif;
System.out.println(" [step over] pokok = " + pokok);
int lembur = hitungLembur(5, tarif);
System.out.println(" [step over] lembur = " + lembur);
System.out.println(" [step out] hitungGaji mengembalikan "
+ (pokok + lembur));
return pokok + lembur;
}
static int hitungLembur(int jam, int tarif) {
System.out.println(" [step in] masuk hitungLembur: jam=" + jam);
int hasil = jam * tarif * 2;
System.out.println(" [step out] hitungLembur mengembalikan " + hasil);
return hasil;
}
}