Breakpoint adalah kesepakatan dengan debugger: “berhentilah tepat di sini, sisanya biar saya yang periksa”. Tanpa breakpoint, debugger hanya penonton; dengan breakpoint yang tepat, debugger menjadi alat ukur presisi yang menghentikan program pada satu baris, satu kondisi, bahkan satu penulisan field. Memilih jenis hentian yang tepat sering menentukan lama investigasi: berhenti di setiap iterasi loop membuat kita menekan continue ratusan kali, sedangkan berhenti hanya saat kondisi mencurigakan langsung mengantarkan kita ke peristiwa yang dicari. Pada Chapter ini kita akan membedakan empat jenis hentian, lalu menjalankan program yang merekam jejak jeda sebagaimana sebuah debugger melakukannya.

Ragam Breakpoint #

Empat jenis hentian berikut tersedia di IntelliJ IDEA, VS Code, Eclipse, dan juga jdb:

JenisBerhenti ketikaContoh kebutuhan
Line breakpointEksekusi mencapai baris tertentuMemeriksa state sebelum satu pernyataan penting
Conditional breakpointBaris tercapai dan kondisi boolean terpenuhiHanya iterasi ke-1000, atau hanya data bernilai negatif
Exception breakpointException tertentu dilempar — tertangkap atau tidakSemua titik NumberFormatException muncul di satu tempat
Field watchpointField dibaca atau ditulisMenemukan siapa yang mengubah saldo diam-diam

Nilai strategis exception breakpoint sering luput: hentian terjadi pada saat exception dibuat, sebelum stack terurai, sehingga kita melihat seluruh konteks pemanggilan. Bandingkan dengan membaca stack trace setelah kejadian, yang hanya menunjukkan jejak tersisa.

Simulasi Hentian pada Kondisi #

Program berikut merekam jeda dalam tiga mode: breakpoint baris biasa, breakpoint kondisional, dan hit count (berhenti pada sentuhan ke-N). Metode simulasiJeda meniru panel Variables debugger yang menampilkan nilai saat eksekusi berhenti:

public class JejakBreakpoint {
    public static void main(String[] args) {
        int[] data = {5, 12, 8, 21, 9};

        System.out.println("== Breakpoint baris: berhenti di setiap iterasi ==");
        for (int i = 0; i < data.length; i++) {
            simulasiJeda("baris", i, data[i]);
        }

        System.out.println();
        System.out.println("== Breakpoint kondisional: hanya saat data[i] > 10 ==");
        for (int i = 0; i < data.length; i++) {
            if (data[i] > 10) {
                simulasiJeda("kondisional", i, data[i]);
            }
        }

        System.out.println();
        System.out.println("== Hit count 3: berhenti pada sentuhan ke-3 ==");
        int sentuhan = 0;
        for (int i = 0; i < data.length; i++) {
            sentuhan++;
            if (sentuhan == 3) {
                simulasiJeda("hit-count-3", i, data[i]);
            }
        }
    }

    // Meniru panel Variables pada debugger ketika eksekusi berhenti
    static void simulasiJeda(String jenis, int i, int nilai) {
        System.out.println("[" + jenis + "] i=" + i
                + ", data[i]=" + nilai);
    }
}

Output yang Dihasilkan:

text
== Breakpoint baris: berhenti di setiap iterasi ==
[baris] i=0, data[i]=5
[baris] i=1, data[i]=12
[baris] i=2, data[i]=8
[baris] i=3, data[i]=21
[baris] i=4, data[i]=9

== Breakpoint kondisional: hanya saat data[i] > 10 ==
[kondisional] i=1, data[i]=12
[kondisional] i=3, data[i]=21

== Hit count 3: berhenti pada sentuhan ke-3 ==
[hit-count-3] i=2, data[i]=8

Mari kita bedah cara kerja program tersebut:

  1. Bagian pertama (breakpoint baris): simulasiJeda dipanggil di setiap iterasi — lima jeda untuk lima elemen, meniru breakpoint tanpa kondisi.
  2. Bagian kedua (kondisional): blok if (data[i] > 10) hanya terpenuhi pada i=1 dan i=3, sehingga hanya dua jeda terekam. Di IDE nyata, kondisi seperti data[i] > 10 ditulis pada properti breakpoint, bukan di kode sumber.
  3. Bagian ketiga (hit count): penghitung sentuhan dinaikkan setiap loop; jeda hanya terjadi ketika sentuhan == 3. Fitur hit count pada IDE memakai mekanisme yang sama tanpa mengubah kode.
  4. Metode simulasiJeda: mencetak i dan data[i] — variabel yang akan tampil di panel debugger pada saat berhenti.

Pola yang layak diingat: mulai investigasi dari breakpoint kondisional yang paling spesifik. Jika pelanggarannya langka (satu dari sejuta baris), breakpoint polos akan membuat sesi debug berlangsung sangat lama.

Breakpoint untuk Exception #

Pertanyaan klasik saat data masuk tidak valid: dari mana exception itu berasal? Exception breakpoint menjawabnya tanpa perlu menebak letak try-catch. Program berikut mensimulasikan hentian semacam itu dengan membaca frame pertama milik kode kita sendiri:

public class JejakException {
    public static void main(String[] args) {
        String[] masukan = {"12", "abc", "7"};
        int jumlah = 0;

        for (String teks : masukan) {
            try {
                jumlah += parseAngka(teks);
            } catch (NumberFormatException e) {
                // Mensimulasikan exception breakpoint: jeda di titik lempar
                StackTraceElement lokasi = cariFrameKita(e);
                System.out.println("[exception-bp] berhenti di "
                        + lokasi.getMethodName()
                        + " baris " + lokasi.getLineNumber()
                        + " -> " + e.getMessage());
            }
        }

        System.out.println("Jumlah nilai valid: " + jumlah);
    }

    static int parseAngka(String teks) {
        return Integer.parseInt(teks);
    }

    // Ambil frame pertama milik kode kita sendiri, bukan pustaka java.lang
    static StackTraceElement cariFrameKita(NumberFormatException e) {
        for (StackTraceElement frame : e.getStackTrace()) {
            if (!frame.getClassName().startsWith("java.")) {
                return frame;
            }
        }
        return e.getStackTrace()[0];
    }
}

Output yang Dihasilkan:

text
[exception-bp] berhenti di parseAngka baris 23 -> For input string: "abc"
Jumlah nilai valid: 19

Poin bedah untuk program di atas:

  1. Baris jumlah += parseAngka(teks): hanya masukan "abc" yang gagal; dua nilai lain menambah jumlah menjadi 19.
  2. cariFrameKita(e): memfilter frame Integer.parseInt milik java.lang dan mengembalikan frame pertama milik berkas kita — parseAngka baris 23, titik pemanggilan pustaka.
  3. Tanpa simulasi ini, exception yang tertangkap tidak meninggalkan jejak di output sama sekali; inilah sebabnya exception breakpoint begitu berguna untuk exception yang “ditelan” blok catch yang terlalu lapar.

Sesi jdb nyata berikut menunjukkan versi sebenarnya. Perintah catch all java.lang.NumberFormatException memasang exception breakpoint; saat exception terjadi, jdb berhenti di titik lempar dan perintah where menampilkan tumpukan pemanggil:

text
Exception occurred: java.lang.NumberFormatException (to be caught at:
CobaParse.main(), line=8 bci=50)"thread=main", java.lang.Integer.parseInt(), line=565 bci=284

main[1]   [1] java.lang.Integer.parseInt (Integer.java:565)
  [2] java.lang.Integer.parseInt (Integer.java:662)
  [3] CobaParse.parseAngka (CobaParse.java:15)
  [4] CobaParse.main (CobaParse.java:7)

Baris (to be caught at: ...) memberi tahu bahwa exception akan ditangkap di main baris 8 — kita melihat masa depan stack sebelum unwinding terjadi. Di IDE, hal yang sama muncul sebagai daftar breakpoint exception di panel Breakpoints dengan pilihan caught, uncaught, atau keduanya.

Watchpoint pada Field #

Watchpoint menjawab pertanyaan investigatif lain: “siapa yang mengubah nilai field ini?” Alih-alih berhenti pada satu baris, watchpoint berhenti setiap kali field tertentu dibaca (access) atau ditulis (modification). Perhatikan class berikut:

java
class Rekening {
    private long saldo;

    void setor(long jumlah) {
        this.saldo += jumlah;
    }

    void tarik(long jumlah) {
        this.saldo -= jumlah;
    }
}

Jika saldo akhir tidak masuk akal, watchpoint pada field saldo akan menghentikan program pada setiap eksekusi this.saldo += jumlah dan this.saldo -= jumlah, lengkap dengan baris pemanggil dan nilai sebelum-sesudah. Di IntelliJ IDEA, klik kanan deklarasi field lalu pilih Add Field Watchpoint; di jdb, perintahnya watch Rekening.saldo untuk modifikasi dan watch access Rekening.saldo untuk pembacaan.

Kini kita mampu menghentikan program di titik mana pun yang penting. Tetapi berhenti hanyalah setengah pekerjaan — setelah program terhenti, kita perlu bergerak maju selangkah demi selangkah untuk melihat urutan kejadian yang sesungguhnya. Chapter berikutnya membahas step over, step in, dan step out sebagai navigasi dasar eksekusi bertahap.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

JejakBreakpoint.java

Unduh berkas
java
public class JejakBreakpoint {
    public static void main(String[] args) {
        int[] data = {5, 12, 8, 21, 9};

        System.out.println("== Breakpoint baris: berhenti di setiap iterasi ==");
        for (int i = 0; i < data.length; i++) {
            simulasiJeda("baris", i, data[i]);
        }

        System.out.println();
        System.out.println("== Breakpoint kondisional: hanya saat data[i] > 10 ==");
        for (int i = 0; i < data.length; i++) {
            if (data[i] > 10) {
                simulasiJeda("kondisional", i, data[i]);
            }
        }

        System.out.println();
        System.out.println("== Hit count 3: berhenti pada sentuhan ke-3 ==");
        int sentuhan = 0;
        for (int i = 0; i < data.length; i++) {
            sentuhan++;
            if (sentuhan == 3) {
                simulasiJeda("hit-count-3", i, data[i]);
            }
        }
    }

    // Meniru panel Variables pada debugger ketika eksekusi berhenti
    static void simulasiJeda(String jenis, int i, int nilai) {
        System.out.println("[" + jenis + "] i=" + i
                + ", data[i]=" + nilai);
    }
}

JejakException.java

Unduh berkas
java
public class JejakException {
    public static void main(String[] args) {
        String[] masukan = {"12", "abc", "7"};
        int jumlah = 0;

        for (String teks : masukan) {
            try {
                jumlah += parseAngka(teks);
            } catch (NumberFormatException e) {
                // Mensimulasikan exception breakpoint: jeda di titik lempar
                StackTraceElement lokasi = cariFrameKita(e);
                System.out.println("[exception-bp] berhenti di "
                        + lokasi.getMethodName()
                        + " baris " + lokasi.getLineNumber()
                        + " -> " + e.getMessage());
            }
        }

        System.out.println("Jumlah nilai valid: " + jumlah);
    }

    static int parseAngka(String teks) {
        return Integer.parseInt(teks);
    }

    // Ambil frame pertama milik kode kita sendiri, bukan pustaka java.lang
    static StackTraceElement cariFrameKita(NumberFormatException e) {
        for (StackTraceElement frame : e.getStackTrace()) {
            if (!frame.getClassName().startsWith("java.")) {
                return frame;
            }
        }
        return e.getStackTrace()[0];
    }
}