Kesalahan program tidak datang dari satu pintu. Sebagian tertangkap compiler javac sebelum program sempat berjalan, sebagian baru muncul ketika JVM mengeksekusi bytecode, dan ada pula yang tidak menghasilkan pesan error sama sekali padahal hasil program salah. Ketiga momen tersebut menuntut strategi perbaikan yang berbeda. Pada Chapter ini kita bedah garis batas compile time dan runtime, melihat contoh nyata masing-masing, lalu mengenali jenis ketiga: kesalahan logika yang lolos dari keduanya.
Dua Tahap Kehidupan Program #
Kode Java melewati dua tahap besar sebelum hasilnya sampai ke pengguna. Diagram berikut memetakan ke mana program bisa belok di tiap tahap:
Compiler javac membaca kode sumber tanpa menjalankannya — pemeriksaannya bersifat statis, berdasarkan apa yang tertulis di berkas. JVM sebaliknya bekerja dinamis: nilai variabel, isi array, dan kondisi saat program berjalan baru diketahui pada tahap ini. Pembedaan statis versus dinamis itulah yang menentukan jenis kesalahan apa yang bisa tertangkap di masing-masing tahap.
Kesalahan yang Ditangkap Compiler #
Compiler memeriksa tiga hal utama: sintaks, tipe, dan keberadaan simbol. Program berikut melanggar aturan tipe — memasukkan String ke variabel int:
public class TipeTidakCocok {
public static void main(String[] args) {
int umur = "tujuh belas";
System.out.println(umur);
}
}Saat javac TipeTidakCocok.java dijalankan, kompilasi berhenti dengan pesan berikut:
TipeTidakCocok.java:3: error: incompatible types: String cannot be converted to int
int umur = "tujuh belas";
^
1 errorCompiler tahu secara pasti bahwa "tujuh belas" bertipe String karena informasi tersebut tertulis di kode — tidak perlu menjalankan program untuk membuktikannya. Pemeriksaan serupa berlaku untuk sintaks (titik koma atau kurung kurawal yang kurang) dan simbol (variabel yang belum dideklarasi, method yang tidak ada). Semua pesan berformat berkas:baris: error: penjelasan, dengan tanda sisip yang menunjuk posisi kejadian.
Kesalahan yang Baru Muncul saat Runtime #
Sekarang lihat program berikut. Secara tipe program ini sah — int dibagi int memang menghasilkan int — sehingga compiler meloloskannya tanpa keluhan:
public class PembagianNol {
public static void main(String[] args) {
int pembilang = 10;
int penyebut = 0;
System.out.println("Menghitung " + pembilang + " / " + penyebut);
System.out.println("Hasil: " + (pembilang / penyebut));
}
}Program tersebut memang sengaja berakhir dengan error. Saat dijalankan, satu baris berhasil dicetak lebih dulu, lalu JVM menghentikan program dengan keluaran berikut di terminal:
Menghitung 10 / 0
Exception in thread "main" java.lang.ArithmeticException: / by zero
at PembagianNol.main(PembagianNol.java:7)Mari kita bedah mengapa kesalahan ini lolos dari compiler: nilai penyebut adalah data biasa yang boleh bernilai berapa pun, dan baru saat JVM mengeksekusi pembagian pada baris 7 diketahui nilainya 0. Momen pengecekan tipe (compile time) dan momen diketahuinya nilai (runtime) berbeda — di sinilah celahnya. ArithmeticException adalah exception: sinyal dari JVM bahwa operasi tidak dapat dilanjutkan, lengkap dengan nama exception dan baris kejadiannya.
Lolos dari Keduanya namun Tetap Salah #
Jenis ketiga paling senyap: program berjalan tanpa pesan error, tetapi hasilnya tidak sesuai harapan. Program berikut membagi 7 kue untuk 2 anak:
public class PembagianBulat {
public static void main(String[] args) {
int totalKue = 7;
int jumlahAnak = 2;
// 7 / 2 pada tipe int menghasilkan 3, bukan 3,5
int kuePerAnak = totalKue / jumlahAnak;
System.out.println("Kue per anak: " + kuePerAnak);
System.out.println("Sisa kue : " + (totalKue % jumlahAnak));
}
}Output yang Dihasilkan:
Kue per anak: 3
Sisa kue : 1Tidak ada error dari compiler karena int / int memang sah. Tidak ada exception dari JVM karena pembagian bilangan bulat memang didefinisikan membuang bagian pecahan. Jika niat kita adalah membagi adil 3,5 kue per anak, maka hasil 3 adalah kesalahan logika — dan hanya manusia (atau pengujian otomatis) yang bisa menangkapnya. Perhatikan juga operator modulo % yang justru berguna di sini: sisa 1 memberi tahu bahwa pembagian tidak rata.
Membedakan Ketiganya dengan Cepat #
Ketiga jenis kesalahan dapat dikenali dari wajahnya:
| Jenis | Momen terdeteksi | Ciri pesan | Contoh |
|---|---|---|---|
| Compile-time error | Saat javac dijalankan | berkas:baris: error: ... | incompatible types, ';' expected |
| Runtime error | Saat java menjalankan program | Exception in thread ... diikuti stack trace | ArithmeticException: / by zero |
| Logic error | Tidak terdeteksi otomatis | Tidak ada pesan; hasil salah | 7 / 2 menghasilkan 3 |
Urutan perbaikannya juga berbeda. Compile-time error harus diselesaikan sebelum program bisa berjalan — berita baiknya, pesannya selalu menyebut berkas dan baris. Runtime error menuntut kita membaca stack trace dan menelusuri kondisi yang memicunya. Logic error menuntut pemahaman niat: kita harus tahu hasil yang benar lebih dulu, idealnya dipatenkan sebagai pengujian otomatis di Section Testing.
Kita kini memahami peta kesalahan: statis di compile time, dinamis di runtime, dan senyap pada logika. Tahap berikutnya adalah keterampilan membaca pesan itu sendiri — membedah baris demi baris keluaran javac dan stack trace exception agar kebingungan pertama berubah menjadi langkah diagnosis yang tertib. Chapter penutup Section ini mengulasnya secara sistematis.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
PembagianBulat.java
Unduh berkaspublic class PembagianBulat {
public static void main(String[] args) {
int totalKue = 7;
int jumlahAnak = 2;
// 7 / 2 pada tipe int menghasilkan 3, bukan 3,5
int kuePerAnak = totalKue / jumlahAnak;
System.out.println("Kue per anak: " + kuePerAnak);
System.out.println("Sisa kue : " + (totalKue % jumlahAnak));
}
}
PembagianNol.java
Unduh berkaspublic class PembagianNol {
public static void main(String[] args) {
int pembilang = 10;
int penyebut = 0;
System.out.println("Menghitung " + pembilang + " / " + penyebut);
System.out.println("Hasil: " + (pembilang / penyebut));
}
}