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:

PerintahPerilakuBerhenti di
Step OverJalankan baris saat ini seluruhnya, termasuk semua pemanggilan method di dalamnyaBaris berikutnya pada method yang sama
Step InJika baris memanggil method, masuk ke method ituBaris pertama method yang dipanggil
Step OutJalankan sisa method saat ini sampai selesaiBaris 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:

text
[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] selesai

Mari kita baca jejak tersebut sebagai peta navigasi bertahap:

  1. [main] mulai, memanggil hitungGaji(40, 25000): eksekusi berada di main, tepat sebelum baris pemanggilan. Menekan step in di sini memindahkan kita ke dalam hitungGaji.
  2. [step in] masuk hitungGaji: jam=40, tarif=25000: frame aktif kini hitungGaji; parameter sudah terisi dan terlihat di panel variabel.
  3. [step over] pokok = 1000000: menekan step over pada baris int pokok = jam * tarif; mengeksekusi perkalian itu seluruhnya lalu berhenti di baris berikutnya pada frame yang sama.
  4. [step in] masuk hitungLembur: jam=5: baris hitungLembur(5, tarif) berisi pemanggilan method milik kita, sehingga step in menurunkan frame satu tingkat lagi.
  5. [step out] hitungLembur mengembalikan 250000: dari dalam hitungLembur, step out mengeksekusi sisa method dan mengembalikan kita ke pemanggilnya — baris lembur di hitungGaji.
  6. Dua baris terakhir main: setelah hitungGaji mengembalikan nilai, step out terakhir mendaratkan eksekusi kembali di main dengan gaji sudah 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:

text
[watches] total > 100000        -> true
[watches] total - diskon        -> 125000
[watches] rupiah(total - 5000)  -> Rp120000
Total bayar: Rp112500

Poin penting dari simulasi tersebut:

  1. total > 100000 dan total - diskon: debugger mengevaluasi ekspresi dengan nilai variabel hidup saat jeda; diskon masih 0 karena penugasan 12500 belum dieksekusi — informasi urutan yang hanya terlihat saat berhenti.
  2. rupiah(total - 5000): evaluate expression juga boleh memanggil method. IntelliJ IDEA menyediakannya lewat pintasan Alt+F8; VS Code lewat perintah Debug: Evaluate.
  3. 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 IDEPerintah jdbEfek
Step OvernextSatu baris, pemanggilan method dieksekusi penuh
Step InstepSatu baris, masuk ke method yang dipanggil
Step Outstep upJalankan sampai method saat ini kembali
Langkah bytecodestepiSatu instruksi bytecode — untuk kasus sangat detail
Lanjut penuhcontJalankan 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 berkas
java
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;
    }
}

SimulasiStep.java

Unduh berkas
java
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;
    }
}