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:
| Jenis | Berhenti ketika | Contoh kebutuhan |
|---|---|---|
| Line breakpoint | Eksekusi mencapai baris tertentu | Memeriksa state sebelum satu pernyataan penting |
| Conditional breakpoint | Baris tercapai dan kondisi boolean terpenuhi | Hanya iterasi ke-1000, atau hanya data bernilai negatif |
| Exception breakpoint | Exception tertentu dilempar — tertangkap atau tidak | Semua titik NumberFormatException muncul di satu tempat |
| Field watchpoint | Field dibaca atau ditulis | Menemukan 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:
== 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]=8Mari kita bedah cara kerja program tersebut:
- Bagian pertama (breakpoint baris):
simulasiJedadipanggil di setiap iterasi — lima jeda untuk lima elemen, meniru breakpoint tanpa kondisi. - Bagian kedua (kondisional): blok
if (data[i] > 10)hanya terpenuhi padai=1dani=3, sehingga hanya dua jeda terekam. Di IDE nyata, kondisi sepertidata[i] > 10ditulis pada properti breakpoint, bukan di kode sumber. - Bagian ketiga (hit count): penghitung
sentuhandinaikkan setiap loop; jeda hanya terjadi ketikasentuhan == 3. Fitur hit count pada IDE memakai mekanisme yang sama tanpa mengubah kode. - Metode
simulasiJeda: mencetakidandata[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:
[exception-bp] berhenti di parseAngka baris 23 -> For input string: "abc"
Jumlah nilai valid: 19Poin bedah untuk program di atas:
- Baris
jumlah += parseAngka(teks): hanya masukan"abc"yang gagal; dua nilai lain menambahjumlahmenjadi 19. cariFrameKita(e): memfilter frameInteger.parseIntmilikjava.langdan mengembalikan frame pertama milik berkas kita —parseAngkabaris 23, titik pemanggilan pustaka.- Tanpa simulasi ini, exception yang tertangkap tidak meninggalkan jejak di output sama sekali; inilah sebabnya exception breakpoint begitu berguna untuk exception yang “ditelan” blok
catchyang 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:
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:
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 berkaspublic 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 berkaspublic 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];
}
}