Rabu, 14 Desember 2011

Pemrograman dengan Pengecualian



Pengecualian bisa digunakan untuk membantu kita menulis program tangguh. Pengecualian adalah pendekatan yang terstruktur dan terorganisir untuk membuat program tangguh. Tanpa pengecualian, program akan dipenuhi dengan pernyataan if untuk menguji berbagai macam kondisi kesalahan. Dengan pengecualian, kita bisa menulis program yang algoritma yang lebih jelas, di mana kasus-kasus pengecualian akan ditangani di bagian lain, yaitu di dalam klausa catch.

Membuat Kelas Pengecualian Baru


Ketika suatu program menemukan kondisi yang tidak biasa dan tidak ada cara yang masuk akal untuk ditangani pada saat itu juga, program akan melempar pegecualian. Dalam beberapa kasus, mungkin akan lebih mudah apabila pengecualian yang dilemparkan merupakan objek dari salah satu kelas pengecualian bawaah Java, seperti IllegalArgumentException atau IOException.

Akan tetapi, jika tidak ada kelas standar yang cukup mewakili jenis pengecualian tersebut, programmer bisa membuat kelas pengecualian baru. Kelas baru tersebut harus diturunkan dari kelas standar Throwable atau kelas turunannya. Secara umum, kelas baru merupakan turunan dari kelas RuntimeExceptionatau kelas turunannya jika programmer tidak mewajibkan penanganan kesalahan. Untuk membuat kelas pengecualian baru yang mewajibkan penanganan kesalahan, programmer dapat membuat turunan dari kelas Exception atau salah satu kelas turunannya.

Berikut ini adalah contoh suatu kelas yang merupakan turunan dari kelas Exception yang mewajibkan penanganan kesalahan apabila ia dilemparkan :
public class KelasahanBaca extends Exception {
    public KelasahanBaca(String pesan) {
        // Konstruktor. Membuat objek dari KesalahanBaca yang berisi
        // pesan kesalahan
        super(pesan);
    }
}


Kelas tersebut hanya memiliki konstruktor sehingga kita bisa membuat objek dari kelas KesalahanBaca yang berisi pesan kesalahan. (Pernyataan "super(pesan)" memanggil konstruktor di kelas supernya, yaitu Exception. Lihat bagian sebelumnya). Tentunya kelas tersebut juga mewariskan metode getMessage() dan printStackTrace() dari kelas supernya. Jika e merujuk pada objek dengan tipe KesalahanBaca maka perintah e.getMessage() akan mengambil pesan kesalahan yang diberikan pada konstruktornya.

Apabila objek dengan tipe KesalahanBaca dilempar, ini berarti jenis kesalahan tertentu telah terjadi. (Mungkin misalnya terjadi apabila pembacaan suatu String yang diproses program tidak sesuai dengan format yang diharapkan).

Pernyataan throw bisa digunakan untuk melempar kesalahan dengan tipe KesalahanBaca. Konstruktor objek ini harus memiliki pesan kesalahan, misalnya :



throw new ParseError("Ditemukan bilangan negatif ilegal.");


atau

throw new ParseError("Kata '" + word + "' bukan nama file yang benar.");

Jika pernyataan throw tidak terdapat dalam pernyataan try yang menangkap kesalahan tersebut, maka subrutin yang melemparnya harus dideklarasikan di awal bahwa subrutin tersebut bisa melempar KesalahanBaca, yaitu dengan menambah klausa "throws KesalahanBaca". Misalnya :


void ambilNamaUser() throws KesalahanBaca {
    . . .
}


Klausa ini tidak diperlukan apabila KesalahanBaca didefinisikan sebagai turunan dari kelas RuntimeException, karena pengecualian ini tidak wajib untuk ditangani.

Suatu subrutin yang ingin menangani KesalahanBaca dapat menggunakan pernyataan try dengan klausa catch untuk menangkap KesalahanBaca. Misalnya
try {
    ambilNamaUser();
    olahNamaUser();
}
catch (KesalahanBaca kb) {
    . . .  // Tangani kesalahan
}


Ingat bahwa karena KesalahanBaca adalah kelas turunan dari Exception, maka klausa catch dalam bentuk "catch(Exception e)" juga akan menangkap KesalahanBaca dan juga objek yang bertipe Exception.

Kadang-kadang, ada gunanya untuk menyimpan data dalam objek pengecualian, misalnya :
class KapalMeledak extends RuntimeException {
    Kapal kapal;  // Kapal yang meledak
    int lokasi_x, lokasi_y;  // Lokasi tempat kapal meledak
    KapalMeledak(String pesan, Kapal k, int x, int y) {
        // Konstruktor: Buat objek KapalMeledak yang menyimpan
        // pesan kesalahan dan informasi bahwa kapal k
        // meledak pada lokasi x,y pada layar
        super(pesan);
        kapal = k;
        lokasi_x = x;
        lokasi_y = y;
    }
}
Di sini, objek KapalMeledak berisi pesan kesalahan dan informasi tambahan tentang kapal yang meledak, yang bisa digunakan dalam perintah berikut:
if ( kapalUser.isTertembak() )
    throw new KapalMeledak("Anda Tertembak!", kapalUser, xLok, yLok);


Ingat bahwa kondisi objek KapalMeledak mungkin bukan suatu kesalahan. Mungkin hanya merupakan jalan lain dari alur suatu game. Pengecualian bisa juga digunakan sebagai percabangan besar seperti ini dengan cara yang lebih rapi.

Kelas dan Subrutin Pengecualian


Kemungkinan untuk melempar pengecualian akan berguna dalam penulisan subrutin dan kelas umum yang digunakan oleh lebih dari satu program. Dalam hal ini orang yang menulis subrutin atau kelas tersebut tidak memiliki cara yang umum untuk menangani kesalahan tersebut. Terutama karena ia tidak tahu bagaimana subrutin atau kelas tersebut akan digunakan. Dalam kondisi seperti itu, programmer pemula biasanya lebih memilih untuk mencetak pesan kesalahan dan melanjutkan program, akan tetapi cara ini tidak memuaskan karena mungkin akan ada masalah di kemudian hari. Mencetak pesan kesalahan dan menghentikan program juga bukan solusi karena program tidak berkesempatan untuk mengatasi kesalahan tersebut.

Program yang memanggil subrutin atau menggunakan kelas tersebut perlu tahu bahwa suatu kesalahan telah terjadi. Dalam bahasa yang tidak memiliki pengecualian, satu-satunya alternatif adalah mengembalikan nilai khusus atau mengeset nilai variabel tertentu untuk memberi tahu bahwa suatu kesalahan telah terjadi. Misalnya, fungsi ambilDouble() bisa saja mengembalikan nilai NaN jika input dari user salah. Akan tetapi, cara ini hanya efektif jika program utama mengecek nilai keluarannya. Pengecualian akan lebih rapi jika suatu subrutin memiliki cara untuk tahu apabila suatu kesalahan telah terjadi.

Asersi


Ingat bahwa kondisi awal adalah kondisi yang harus benar pada suatu titik di dalam program sehingga program akan berjalan benar dari titik tersebut dan seterusnya. Dalam hal ini, ada kemungkin bahwa suatu kondisi awal mungkin tidak bisa dipenuhi. Untuk itu, akan lebih baik jika kita letakkan pernyataan if untuk mengujinya. Pertanyaan berikutnya adalah apa yang harus kita lalukan jika kondisi awal tidak benar? Salah satunya adalah melempar pengecualian, yang kemudian akan menghentikan program kecuali jika pengecualian tersebut ditangkap dan ditangani di tempat lain.

Bahasa pemrograman seperti C dan C++ memiliki fasilitas untuk menambah asersi (assertions) dalam program. Asersi dapat berbentuk assert(kondisi), di mana kondisi adalah ekspresi bernilai boolean. Kondisi adalah suatu kondisi awal yang harus benar pada satu titik di dalam program. Ketika komputer menemukan asersi dalam eksekusi suatu program, ia akan mengevaluasi kondisi tersebut. Jika kondisi tersebut salah, maka program akan berhenti. Jika benar, maka program akan terus berjalan. Asersi dalam bentuk ini tidak tersedia pada Java, akan tetapi sesuatu yang mirip seperti ini bisa dilakukan dengan pengecualian.

Bentuk asersi assert(kondisi) dapat diganti dalam bahasa Java dalam bentuk :
if (kondisi == false)
    throw new IllegalArgumentException("Asersi gagal.");


Kita bisa mengganti pesan kesalahan dengan pesan yang lebih baik, dan mungkin akan lebih cantik apabila kelas pengecualiannya juga diganti dengan kelas yang lebih spesifik.

Asersi sangat umum digunakan dalam pengujian dan debugging. Setelah kita merilis program kita, kita tidak ingin program kita crash. Akan tetapi banyak program yang dibuat pada dasarnya seperti
try {
    .
    .  // Jalankan program
    .
}
catch (Exception e) {
    System.out.println("Pengecualian dalam program terjadi.");
    System.out.println("Harap kirimkan pesan bug kepada programmernya.");
    System.out.println("Detail kesalahan:"):
    e.printStackTrace();
}


Jika suatu program memiliki banyak asersi, maka akan menyebabkan program lebih lambat. Salah satu keuntungan asersi pada C dan C++ adalah asersi bisa "dimatikan". Dalam arti jika program dikompilasi dengan cara lain, maka asersi akan dibuang dari dalam program utama. Versi rilis dari program dikompilasi dengan asersi yang dimatikan. Dengan cara ini versi rilis akan lebih efisien, karena komputer tidak perlu mengevaluasi semua asersi tersebut. Keuntungan lainnya adalah kita tidak perlu membuang asersi tersebut dari kode sumber programnya.

Ada cara seperti ini yang mungkin juga bisa diterapkan pada Java, yang tergantung dari seberapa canggih kompiler kita. Misalnya kita tentukan suatu konstanta
static final boolean DEBUG = true;
dan kita menulis asersi seperti
if (DEBUG == true && kondisi <span style="color: #00bb00;">span>== false)
    throw new IllegalArgumentException("Asersi Gagal.");
Karena DEBUG bernilai true, maka nilai "DEBUG == true && kondisi == false" sama dengan nilai kondisi, sehingga pernyataan if ini sama dengan pengujian suatu kondisi awal. Sekarang misalnya kita telah selesai melakukan debugging. Sebelum kita mengkompilasi versi rilis suatu program, kita ganti definisi DEBUG menjadi
static final boolean DEBUG = false;
Sekarang, nilai "DEBUG == true && kondisi == false" selalu bernilai false, dan kompiler canggih akan bisa mendeteksi ini pada saat kompilasi. Karena nilai if ini akan selalu bernilai false, kompiler canggih akan mengabaikan perintah ini dalam hasil kompilasinya, karena if ini tidak akan pernah dieksekusi. Hasilnya, kode ini tidak akan dimasukkan ke dalam versi rilis program. Dan kita hanya cukup mengganti satu baris saja pada kode sumbernya.

Tidak ada komentar:

Posting Komentar