Mekanisme try, catch, dan throw tersedia di Java, tetapi pemakaiannya yang menentukan apakah kegagalan mudah dilacak atau justru tersamar. Nilai sebuah exception ditentukan oleh kebiasaan di sekelilingnya, yaitu apakah kegagalan dilaporkan apa adanya, apakah lemparannya sesuai peruntukan, dan apakah jejak penyebabnya tersimpan. Topik ini merangkum kebiasaan tersebut dengan contoh yang bisa dijalankan, dimulai dari kesalahan yang paling sering muncul di kode nyata, yakni exception yang ditelan diam-diam.
Jangan menelan exception#
Exception yang ditelan adalah exception yang ditangkap lalu tidak diapakan pun, dan inilah kebiasaan yang paling merugikan. Blok catch kosong membuat program tampak sehat padahal datanya sudah rusak, karena kegagalan tidak meninggalkan jejak apa pun. Program berikut menghitung rata-rata nilai ujian dari teks, dengan dua versi penanganan yang berbeda.
public class DemoTertelan {
public static void main(String[] args) {
String[] nilaiTeks = {"80", "75", "abc", "90"};
System.out.println("Rata-rata versi tertelan : " + rataRataTertelan(nilaiTeks));
try {
System.out.println("Rata-rata versi jujur : " + rataRataJujur(nilaiTeks));
} catch (IllegalArgumentException e) {
System.out.println("Versi jujur berhenti : " + e.getMessage());
}
}
static double rataRataTertelan(String[] nilai) {
double total = 0;
for (String teks : nilai) {
try {
total += Integer.parseInt(teks);
} catch (NumberFormatException e) {
// exception ditelan, seolah tidak terjadi apa-apa
}
}
return total / nilai.length;
}
static double rataRataJujur(String[] nilai) {
double total = 0;
for (String teks : nilai) {
try {
total += Integer.parseInt(teks);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("nilai \"" + teks + "\" bukan angka", e);
}
}
return total / nilai.length;
}
}Rata-rata versi tertelan : 61.25
Versi jujur berhenti : nilai "abc" bukan angkaMenjalankannya dari terminal cukup dengan satu perintah berikut.
# berada di folder tempat DemoTertelan.java disimpan
java DemoTertelan.javaRata-rata versi tertelan : 61.25
Versi jujur berhenti : nilai "abc" bukan angkaVersi tertelan menghasilkan 61.25, angka yang tampak sah padahal datanya kacau. Teks abc lenyap tanpa jejak, penyebutnya tetap dibagi empat, dan tidak ada satu pun petunjuk untuk mencari masalahnya. Versi jujur berhenti pada kegagalan pertama dengan pesan yang menyebut nilai bermasalahnya, dan exception aslinya dibawa sebagai penyebab. Bila penanganan di tempat itu memang tidak memungkinkan, langkah minimumnya adalah mencatat kejadiannya, bukan membiarkan blok catch kosong.
Exception bukan alur kendali#
Exception dirancang untuk kejadian tak terduga, bukan sebagai cara keluar dari perulangan atau percabangan. Program berikut mencari elemen terakhir array dengan dua cara, dan keduanya menghasilkan angka yang sama. Cara pertama memakai exception sebagai penanda akhir data, sedangkan cara kedua cukup membaca panjang arraynya.
public class DemoAlurKendali {
public static void main(String[] args) {
int[] nilai = {70, 85, 90};
System.out.println("Terakhir (dengan exception): " + cariTerakhirSalah(nilai));
System.out.println("Terakhir (dengan length) : " + cariTerakhirBenar(nilai));
}
static int cariTerakhirSalah(int[] data) {
int hasil = -1;
int i = 0;
try {
while (true) {
hasil = data[i];
i++;
}
} catch (ArrayIndexOutOfBoundsException e) {
return hasil;
}
}
static int cariTerakhirBenar(int[] data) {
return data[data.length - 1];
}
}Terakhir (dengan exception): 90
Terakhir (dengan length) : 90Keluarannya sama, tetapi kedua versi tidak setara. Versi exception memakai ArrayIndexOutOfBoundsException sebagai sinyal akhir data, sehingga pembaca kode harus menebak apakah lemparan itu memang diharapkan ataukah bug. Risiko terbesarnya adalah kegagalan asli yang tersamar. Bila suatu hari ada bug indeks di dalam tubuh perulangan, lemparannya akan ditelan oleh catch yang sama, dan kesalahan itu tampak sebagai akhir data yang normal. Pembuatan objek exception juga memuat snapshot stack trace, sehingga jauh lebih mahal daripada sekadar membaca data.length.
Pemisahnya cukup sederhana, yaitu tanyakan apakah kejadian itu bagian wajar dari pemakaian fitur. Akhir data, pilihan yang kosong, dan masukan yang perlu diulang adalah kondisi normal yang ditangani pemeriksaan biasa atau nilai kembalian. Kegagalan yang tidak wajar, seperti berkas yang hilang di tengah pembacaan, barulah layak melempar exception.
Membawa konteks dan mencatat dengan benar#
Exception yang sampai ke lapisan atas sering kehilangan konteksnya, sehingga melempar ulang dengan konteks baru sambil membawa penyebab asli adalah kebiasaan yang bernilai. Program berikut membungkus NumberFormatException dengan keterangan yang menyebut data mana yang gagal, lalu menelusuri rantai penyebabnya secara eksplisit. Konteksnya berupa data yang ditolak, sedangkan penyebab teknisnya tetap bisa ditelusuri sampai ke bawah.
public class DemoRantaiPenyebab {
public static void main(String[] args) {
try {
prosesAntrean("dua");
} catch (IllegalStateException e) {
System.out.println("Pesan utama : " + e.getMessage());
Throwable penyebab = e.getCause();
while (penyebab != null) {
System.out.println("Penyebab : " + penyebab);
penyebab = penyebab.getCause();
}
}
}
static void prosesAntrean(String jumlahTeks) {
try {
int jumlah = Integer.parseInt(jumlahTeks);
System.out.println("Jumlah: " + jumlah);
} catch (NumberFormatException e) {
throw new IllegalStateException("jumlah antrean tidak sah: " + jumlahTeks, e);
}
}
}Pesan utama : jumlah antrean tidak sah: dua
Penyebab : java.lang.NumberFormatException: For input string: "dua"Cetakan java.lang.NumberFormatException: For input string: "dua" berasal dari toString() objek penyebab, dan JVM menyusun rantai yang sama secara otomatis pada stack trace sebagai bagian Caused by. Dengan penyebab yang dibawa, pembaca log bisa berhenti di lapisan mana pun dan tetap sampai ke akar masalahnya. Lapisan tengah pun bebas menambah keterangan karena akar masalahnya tidak pernah hilang dari rantainya.
Untuk pencatatan, kebiasaan yang sehat adalah mencatat exception sekali di batas lapisan, misalnya di metode main atau di penangan permintaan paling luar, bukan di setiap tingkat perantara. Pada proyek nyata, pencatatan dilakukan dengan pustaka log seperti java.util.logging atau SLF4J yang menyertakan objek exception sebagai parameter terakhir, sehingga stack trace lengkapnya masuk ke log. Pemanggilan e.printStackTrace() di System.err hanya sesuai untuk program sederhana, karena keluarannya tidak terkelola dan mudah bercampur dengan keluaran normal.
Tingkat catatan juga menentukan kegunaannya. Kegagalan yang membutuhkan tindakan dicatat pada tingkat paling serius, kegagalan yang bisa dipulihkan pada tingkat peringatan, sedangkan detail penyelidikan pada tingkat yang lebih rendah. Konsistensi tingkat ini membuat log bisa disaring saat insiden, alih-alih dibaca baris demi baris.
Kesalahan umum#
- Membiarkan blok catch kosong atau hanya berisi komentar. Kegagalan tidak berhenti, melainkan berpindah menjadi data yang rusak. Catat kejadiannya atau lempar ulang dengan konteks.
- Mencatat exception lalu tetap melempar ulang di tingkat yang sama tanpa konteks baru. Log berisi duplikasi kejadian yang sama, dan tingkat yang benar-benar bisa menangani jadi sulit ditemukan.
- Memakai exception untuk kondisi yang memang normal, seperti mengakhiri pembacaan data. Gunakan pemeriksaan biasa agar alurnya terbaca dan bug indeks tidak tersamar.
- Menangkap
Exceptiondi banyak tempat hanya agar program terus hidup. Kegagalan tersembunyi menumpuk, dan gejalanya muncul jauh dari penyebabnya. Tangkap tipe yang spesifik di tempat yang memang mampu pulih.
Ringkasan#
- Blok catch yang kosong menyembunyikan kegagalan dan menghasilkan data yang tampak sah padahal rusak, seperti rata-rata 61.25 yang dihitung dari data bermasalah.
- Exception bukan alur kendali, karena memakainya untuk kondisi normal menutupi bug dan jauh lebih mahal daripada pemeriksaan biasa.
- Melempar ulang dengan konteks baru dan penyebab asli menjaga rantai kegagalan tetap terbaca lewat
getCause()maupun stack trace. - Catat exception sekali di batas lapisan terluar dengan pustaka log, bukan di setiap tingkat dan bukan lewat
printStackTraceyang tak terkelola.
Referensi#
- Exceptions (The Java Tutorials) — Oracle — ikhtisar seluruh mekanisme exception beserta kebiasaan pemakaiannya.
- Advantages of Exceptions — Oracle — alasan exception dipisahkan dari alur normal program.
- Throwable (Java SE 21) — Oracle — kontrak getMessage, getCause, dan getSuppressed pada dokumentasi resmi.