Rabu, 14 Desember 2011

Pengecualian dan Pernyataan "try ... catch"



Membuat program untuk bekerja dalam kondisi ideal jauh lebih mudah daripada membuat program yang tangguh. Program tangguh dapat tahan dari kondisi yang tidak biasa atau dalam kondisi "pengecualian". Salah satu pendekatanya adalah dengan melihat kemungkinan masalah yang mungkin terjadi dan menambahkan tes/pengujian pada program tersebut untuk setiap kemungkinan masalah yang mungkin terjadi.

Misalnya, program akan berhenti jika mencoba mengakses elemen array A[i], di mana i tidak berada di dalam rentang yang dibolehkan. Program tangguh harus dapat mengantisipasi kemungkinan adanya indeks yang tak masuk akal dan menjaganya dari kemungkinan itu. Misalnya bisa dilakukan dengan :
if (i < 0 || i >= A.length) {
    ...  // Lakukan sesuatu untuk menangani indeks i diluar rentang
} else {
    ...  // Proses elemen A[i]
}


Ada beberapa masalah yang mungkin terjadi dengan pendekatan seperti ini. Adalah hal yang sangat sulit dan kadang kala tidak mungkin untuk mengantisipasi segala kemungkinan yang dapat terjadi. Kadang tidak selalu jelas apa yang harus dilakukan apabila suatu kesalahan ditemui. Untuk mengantisipasi semua masalah yang mungkin terjadi bisa jadi membuat program sederhana menjadi lautan pernyataan if.

Java (seperti C++) membutuhkan metode alternatif yang lebih rapi dan terstruktur untuk menghadapi masalah yang mungkin terjadi ketika program dijalankan. Metode ini disebut sebagai penanganan pengecualian (exception-handling). Kata "pengecualian" diartikan sesuatu yang lebih umum daripada "kesalahan". Termasuk di antaranya adalah kondisi yang mungkin terjadi yang berada di luar aliran suatu program. Pengecualian, bisa berupa kesalahan, atau bisa juga kasus tertentu yang kita inginkan terpisah dari algoritma kita.

Ketika pengecualian terjadi dalam eksekusi suatu program, kita sebut bahwa pengecualian tersebut di-lempar-kan (thrown). Ketika ini terhadi, aliran program artinya terlempar dari jalurnya, dan program berada dalam bahaya akan crash. Akan tetapi crash bisa dihindari jika pengecualian tersebut ditangkap (catch) dan ditangani dengan cara tertentu. Suatu pengecualian dapat dilempar oleh satu bagian program dan ditangkap oleh bagian program lain. Pengecualian yang tidak ditangkap secara umum akan menyebabkan program berhenti. (Lebih tepat apabila disebut thread yang melemparkan pengecualian tersebut akan berhenti. Dalam program multithreading, mungkin saja thread lain akan terus berjalan apabila thread yang satu berhenti.)

Karena program Java dijalankan di dalam interpreter, program yang crash berarti bahwa program tersebut berhenti berjalan secara prematur. Tidak berarti bahwa interpreter juga akan crash. Atau dengan kata lain, interpreter akan menangkap pengecualian yang tidak ditangkap oleh program. Interpreter akan merespon dengan menghentikan jalannya program. Dalam banyak bahasa pemrograman lainnya, program yang crash sering menghentikan seluruh sistem hingga kita menekan tombol reset. Dalam Java, kejadian seperti itu tidak mungkin -- yang artinya ketika hal itu terjadi, maka yang salah adalah komputer kita, bukan program kita.

Ketika pengecualian terjadi, yang terjadi adalah program tersebut melemparkan suatu objek. Objek tersebut membawa informasi (dalam variabel instansinya) dari tempat di mana pengecualian terjadi ke titik di mana ia bisa ditangkap dan ditangani. Informasi ini selalu terdiri dari tumpukan panggilan subrutin (subrutin call stack), yaitu daftar di mana dan dari mana subrutin tersebut dipanggil dan kapan pengecualian tersebut dilemparkan. (Karena suatu subrutin bisa memanggil subrutin yang lain, beberapa subrutin bisa aktif dalam waktu yang sama.) Biasanya, objek pengecualian juga memiliki pesan kesalahan mengapa ia dilemparkan, atau bisa juga memiliki data lain. Objek yang dilemparkan harus diciptakan dari kelas standar java.lang.Throwable atau kelas turunannya.

Secara umum, setiap jenis pengecualian dikelompokkan dalam kelas turunan Throwable. Throwable memiliki dua kelas turunan langsung, yaitu Error dan Exception. Kedua kelas turunan ini pada akhirnya memiliki banyak kelas turunan lain. Kita juga bisa membuat kelas pengecualian baru untuk melambangkan jenis pengecualian baru.

Kebanyakan turunan dari kelas Error merupakan kesalahan serius dalam mesin virtual Java yang memang seharusnya menyebabkan berhentinya program karena sudah tidak dapat diselamatkan lagi. Kita sebaiknya tidak mencoba untuk menangkap dan menangani kesalahan jenis ini. Misalnya ClassFormatError dilempar karena mesin virtual Java menemukan data ilegal dalam suatu file yang seharusnya berisi kelas Java yang sudah terkompilasi. Jika kelas tersebut dipanggil ketika program sedang berjalan, maka kita tidak bisa melanjutkan program tersebut sama sekali.

Di lain pihak, kelas turunan dari kelas Exception melambangkan pengecualian yang memang seharusnya ditangkap. Dalam banyak kasus, pengecualian seperti ini adalah sesuatu yang mungkin biasa disebut "kesalahan", akan tetapi kesalahan seperti ini adalah jenis yang bisa ditangani dengan cara yang baik. (Akan tetapi, jangan terlalu bernafsu untuk menangkap semua kesalahan hanya karena kita ingin program kita tidak crash. Jika kita tidak memiliki cara untuk menanganinya, mungkin menangkap pengecualian dan membiarkannya akan menyebabkan masalah lain di tempat lain).

Kelas Exception memiliki kelas turunan lainnnya, misalnya RuntimeException. Kelas ini mengelompokkkan pengecualian umum misalnya ArithmeticException yang terjadi misalnya ada pembagian dengan nol, ArrayIndexOutOfBoundsException yang terjadi jika kita mencoba mengakses indeks array di luar rentang yang diijinkan, dan NullPointerException yang terjadi jika kita mencoba menggunakan referensi ke null di mana seharusnya referensi objek diperlukan.

RuntimeException biasanya menginidikasikan adanya bug dalam program yang harus diperbaiki oleh programmer.RuntimeException dan Error memiliki sifat yang sama yaitu program bisa mengabaikannya. ("Mengabaikan" artinya kita membiarkan program crash jika pengecualian tersebut terjadi). Misalnya, program yang melemparkanArrayIndexOutOfBoundsException dan tidak menanganinya akan menghentikan program saat itu juga. Untuk pengecualian lain selain Error dan RuntimeException beserta kelas turunannya, pengecualian wajib ditangani.

Diagram berikut menggambarkan hirarki suatu kelas turunan dari kelas Throwable. Kelas yang membutuhkan penanganan pengecualian wajib ditunjukkan dalam kotak merah.


Untuk menangkap pengecualian pada program Java, kita menggunakan pernyataan try. Maksudnya memberi tahu komputer untuk "mencoba" (try) menjalankan suatu perintah. Jika berhasil, semuanya akan berjalan seperti biasa. Tapi jika pengecualian dilempar pada saat mencoba melaksanakan perintah tersebut, kita bisa menangkapnya dan menanganinya. Misalnya,
try {
    double determinan = M[0][0]*M[1][1] - M[0][1]*M[1][0];
    System.out.println("Determinan matriks M adalah " + determinan);
}
catch ( ArrayIndexOutOfBoundsException e ) {
    System.out.println("Determinan M tidak bisa dihitung karena ukuran M salah.");
}


Komputer mencoba menjalankan perintah di dalam blok setelah kata "try". Jika tidak ada pengecualian, maka bagian "catch" akan diabaikan. Akan tetapi jika ada pengecualian ArrayIndexOutOfBoundsException, maka komputer akan langsung lompat ke dalam blok setelah pernyataan "catch (ArrayIndexOutOfBoundsException)". Blok ini disebut blok yang menangani pengecualian (exception handler) untuk pengecualian ArrayIndexOutOfBoundsException". Dengan cara ini, kita mencegah program berhenti tiba-tiba.

Mungkin kita akan sadar bahwa ada kemungkinan kesalahan lagi dalam blok di dalam pernyataan try. Jika variabel M berisi null, maka pengecualian NullPointerException akan dilemparkan. Dalam pernyataan try di atas, pengecualianNullPointerException tidak ditangkap, sehingga akan diproses seperti biasa (yaitu menghentikan program saat itu juga, kecuali pengecualian ini ditangkap di tempat lain). Kita bisa menangkap pengecualian NullPointerException dengan menambah klausa catch lain, seperti :
try {
    double determinan = M[0][0]*M[1][1] - M[0][1]*M[1][0];
    System.out.println("Determinan matriks M adalah " + determinan);
}
catch ( ArrayIndexOutOfBoundsException e ) {
    System.out.println("Determinan M tidak bisa dihitung karena ukuran M salah.");
}
catch ( NullPointerException e ) {
    System.out.print("Kesalahan program!  M tidak ada:  " + );
    System.out.println( e.getMessage() );
}


Contoh ini menunjukkan bagaimana caranya menggunakan beberapa klausa catch. e adalah nama variabel (bisa kita ganti dengan nama apapun terserah kita). Ingat kembali bahwa ketika pengecualian terjadi, sebenarnya yang dilemparkan adalah objek. Sebelum menjalankan klausa catch, komputer mengisi variabel ini dengan objek yang akan ditangkap. Objek ini mengandung informasi tentang pengecualian tersebut.

Misalnya, pesan kesalahan yang menjelaskan tentang pengecualian ini bisa diambil dengan metode getMessage() seperti contoh di atas. Metode ini akan mencetak daftar subrutin yang dipanggil sebelum pengecualian ini dilempar. Informasi ini bisa menolong kita untuk melacak dari mana kesalahan terjadi.

Ingat bahwa baik ArrayIndexOutOfBoundsException dan NullPointerException adalah kelas turunan dariRuntimeException. Kita bisa menangkap semua pengecualian dalam kelas RuntimeException dengan klausa catch tunggal, misalnya :
try {
    double determinan = M[0][0]*M[1][1] - M[0][1]*M[1][0];
    System.out.println("Determinan matriks M adalah " + determinan);
}
catch ( RuntimeException e ) {
    System.out.println("Maaf, ada kesalahan yang terjadi.");
    e.printStackTrace();
}


Karena objek yang bertipe ArrayIndexOutOfBoundsException maupun NullPointerException juga bertipeRuntimeException, maka perintah seperti di atas akan menangkap kesalahan indeks maupun kesalahan pointer kosong dan juga pengecualian runtime yang lain. Ini menunjukkan mengapa kelas pengecualian disusun dalam bentuk hirarki kelas. Kita bisa membuat klausa catch secara spesifik hingga tahu persis apa yang salah, atau menggunakan klausa penangkap umum.

Contoh di atas mungkin tidak begitu realistis. Sepertinya kita jarang harus menangani kesalahan indeks ataupun kesalahan pointer kosong seperti di atas. Masalahnya mungkin terlalu banyak, dan mungkin kita akan bosan jika harus menulis try ... catchsetiap kali kita menggunakan array. Yang penting kita mengisi variabel tersebut dengan sesuatu yang bukan null dan menjaga agar program tidak keluar indeks sudah cukup. Oleh karena itu kita sebut penanganan ini tidak wajib. Akan ada banyak hal yang bisa jadi masalah. (Makanya penanganan pengecualian tidak menyebabkan program makin tangguh. Ia hanya memberikan alat yang mungkin kita gunakan dengan cara memberi tahu di mana kesalahan mungkin muncul).

Bentuk pernyataan try sebenarnya lebih kompleks dari contoh di atas. Sintaksnya secara umum dapat ditulis seperti:
try {
    perintah
}
klausa_catch_tambahan
klausa_finally_tambahan
Ingat bahwa di sini blok ( yaitu { dan } ) diperlukan, meskipun jika perintah hanya terdiri dari satu perintah. Pernyataan try boleh tidak dimasukkan, dan juga klausa finally boleh ada boleh tidak. (Pernyataan try harus memiliki satu klausa finally ataucatch). Sintaks dari klausa catch adalah:
catch (nama_kelas_pengecualian nama_variabel) {
    pernyataan
}
dan sintaks klausa finally adalah
finally {
    pernyataan
}


Semantik dari klausa finally yaitu pernyataan di dalam klausa finally akan dijamin untuk dilaksanakan sebagai perintah akhir dari pernyataan try, tidak peduli apakah ada pengecualian yang dilempar atau tidak. Pada dasarnya klausa finally dimaksudkan untuk melakukan langkah pembersihan yang tidak boleh dihilangkan.

Ada beberapa kejadian di mana suatu program memang harus melempar pengecualian. Misalnya apabila program tersebut menemukan adanya kesalahan pengurutan atau kesalahan lain, tapi tidak ada cara yang tepat untuk menanganinya di tempat di mana kesalahan tersebut ditemukan. Program bisa melempar pengecualian dengan harapan di bagian lain pengecualian ini akan ditangkap dan diproses.

Untuk melempar pengecualian, kita bisa menggunakan pernyataan throw dengan sintaks :
throw objek_pengecualian;
objek_pengecualian harus merupakan objek yang bertipe kelas yang diturunkan dari Throwable. Biasanya merupakan kelas turunan dari kelas Exception. Dalam banyak kasus, biasanya objek tersebut dibuat dengan operator new, misalnya :
throw new ArithmeticException("Pembagian dengan nol");


Parameter dalam konstruktor adalah pesan kesalahan dalam objek pengecualian.

Pengecualian bisa dilempar baik oleh sistem (karena terjadinya kesalahan) atau oleh pernyataan throw. Pengecualian ini ditangani dengan cara yang sama. Misalnya suatu pengecualian dilempar dari pernyataan try. Jika pernyataan try tersebut memiliki klausacatch untuk menangani tipe pengecualian tersebut, maka klausa ini akan melompat ke klausa catch dan menjalankan perintah di dalamnya. Pengecualian tersebut telah ditangani.

Setelah menangani pengecualian tersebut, komputer akan menjalankan klausa finally, jika ada. Kemudian program akan melanjutkan program seperti biasa. Jika suatu pengecualian tidak ditangkap dan ditangani, maka pengolahan pengecualian akan berlanjut.

Jika pengecualian dilempar pada saat eksekusi suatu subrutin dan pengecualian tersebut tidak ditangani di dalam subrutin yang sama, maka subrutin tersebut akan dihentikan (setelah menjalankan klausa finally, jika tersedia). Kemudian rutin yang memanggil subrutin tersebut memiliki kesempatan untuk menangani pengecualian tersebut. Artinya, jika subrutin tersebut dipanggil di dalam pernyataan try dan memiliki klausa catch yang cocok, maka klausa catch tersebut akan dieksekusi dan program akan berlanjut seperti biasa.

Lagi-lagi jika rutin tersebut tidak menangani pengecualian tersebut, rutin tersebut akan dihentikan dan rutin yang memanggilnya akan mencoba menangani pengecualian tersebut. Pengecualian akan menghentikan program s

Tidak ada komentar:

Posting Komentar