Exception

raise, rescue, ensure, dan penyusunan hierarki exception khusus.

Daftar isi

Mekanisme exception menyediakan jalur pelaporan error yang terpisah dari nilai balik fungsi biasa. Topik ini membahas pernyataan raise untuk melontarkan exception, klausa rescue untuk menangkapnya, klausa else dan ensure untuk memisahkan jalur berhasil dari jalur pembersihan, serta penyusunan hierarki exception khusus untuk kebutuhan program. Setelah membaca topik ini, kegagalan operasi dapat dilaporkan dengan tipe exception yang tepat dan program tetap dapat melanjutkan eksekusinya.

Menangkap exception dengan rescue#

Struktur begin dan rescue membungkus kode yang berpotensi gagal. Pernyataan raise melontarkan sebuah exception, yaitu objek yang membawa informasi kegagalan, dan klausa rescue menangkap objek itu sehingga program tidak berhenti. Objek yang ditangkap diterima sebuah variabel pada klausa, dan properti message pada objek tersebut berisi uraian kegagalannya.

crystal Dua bentuk raise beserta penangkapannya
begin
  raise ArgumentError.new("nilai di luar rentang")
rescue e : ArgumentError
  puts e.message
end

begin
  raise "kegagalan tak terduga"
rescue e
  puts e.message
end
text Keluaran
nilai di luar rentang
kegagalan tak terduga

Pernyataan raise menerima instance dari class Exception atau turunannya, misalnya ArgumentError. Bentuk singkat raise diikuti string juga sah, dan Crystal membungkus string itu menjadi instance Exception baru. Pada kedua bentuk, properti message berisi teks yang diberikan. Selain message, objek exception juga menyediakan backtrace berupa daftar posisi pemanggilan yang mengarah ke lokasi pelontaran.

Klausa rescue dapat menyebut tipe tertentu sehingga hanya exception dengan tipe itu yang ditangkap. Klausa rescue tanpa tipe menangkap semua exception dan berperan sebagai penangkap umum. Klausa-klausa tersebut diperiksa berurutan dari atas ke bawah, dan klausa pertama yang tipenya cocok digunakan.

crystal Klausa rescue bertipe dan penangkap umum
begin
  raise KeyError.new("kunci tidak ada")
rescue e : ArgumentError
  puts "argumen salah"
rescue e
  puts "ditangkap umum: #{e.message}"
end
text Keluaran
ditangkap umum: kunci tidak ada

Contoh di atas melontarkan KeyError, sedangkan klausa pertama hanya menangkap ArgumentError. Karena tidak cocok, pemeriksaan berlanjut ke klausa kedua yang tanpa tipe, sehingga pesan tampil melalui jalur penangkap umum.

Apabila tidak ada klausa rescue yang cocok, exception naik ke pemanggil di atasnya. Apabila tidak ada satu klausa pun yang menangkap sampai batas terluar program, eksekusi berhenti dengan pesan Unhandled exception diikuti uraian kegagalannya. Menangkap exception pada lapisan yang tepat menjaga program tetap hidup sekaligus mempertahankan informasi kegagalan.

Hierarki exception khusus#

Semua exception pada Crystal mewarisi class Exception. Program dapat menambah tipe kegagalan sendiri dengan membuat class turunan, baik langsung dari Exception maupun dari sebuah class perantara yang mengelompokkan kegagalan sejenis. Hierarki semacam ini membuat klausa rescue dapat memilih antara menangkap satu kegagalan spesifik atau seluruh kelompoknya.

Class exception khusus biasanya menerima pesan pada def initialize dan meneruskannya ke class induk melalui super. Data tambahan tentang kegagalan disimpan sebagai properti biasa sehingga pemanggil rescue dapat membacanya. Program hierarki-stok.cr berikut menggunakan pola tersebut untuk operasi stok gudang.

crystal hierarki-stok.cr
# Hierarki exception khusus untuk operasi stok gudang.
# Jalankan dengan: crystal hierarki-stok.cr

class StokError < Exception
end

class StokKurang < StokError
  getter kekurangan : Int32

  def initialize(pesan : String, @kekurangan : Int32)
    super(pesan)
  end
end

class StokHabis < StokError
end

def ambil_barang(stok : Int32, jumlah : Int32)
  raise StokHabis.new("stok habis") if stok == 0
  raise StokKurang.new("stok kurang #{jumlah - stok}", jumlah - stok) if jumlah > stok
  stok - jumlah
end

permintaan = [{3, 2}, {3, 5}, {0, 1}]
permintaan.each do |(stok, jumlah)|
  begin
    puts "sisa stok: #{ambil_barang(stok, jumlah)}"
  rescue e : StokKurang
    puts "ditolak: #{e.message} (kekurangan #{e.kekurangan})"
  rescue e : StokError
    puts "ditolak: #{e.message}"
  end
end
bash
# menjalankan program hierarki stok
crystal hierarki-stok.cr
text Keluaran
sisa stok: 1
ditolak: stok kurang 2 (kekurangan 2)
ditolak: stok habis

Program di atas menetapkan class perantara StokError dengan dua turunannya. Class StokKurang membawa properti kekurangan berisi selisih permintaan terhadap stok. Klausa rescue untuk StokKurang diletakkan di atas klausa untuk StokError karena pemeriksaan berhenti pada kecocokan pertama. Permintaan pertama berhasil tanpa exception, permintaan kedua jatuh ke klausa StokKurang beserta propertinya, dan permintaan ketiga yang berupa StokHabis ditangkap klausa StokError. Dengan hierarki seperti ini, satu klausa rescue e : StokError mampu menangkap semua kegagalan stok tanpa menuliskan setiap tipenya satu per satu.

Klausa else dan ensure#

Klausa ensure memuat pembersihan yang wajib dijalankan baik terjadi exception maupun tidak. Blok begin diproses terlebih dahulu, exception apabila ada ditangkap rescue, dan ensure tetap dieksekusi paling akhir. Pola ini cocok untuk menutup berkas, membebaskan sumber daya, atau mencatat log penutupan. Klausa else melengkapi rescue dan hanya dijalankan ketika tidak ada exception yang ditangkap, sehingga jalur berhasil, jalur gagal, dan jalur pembersihan terpisah secara eksplisit. Ketika keempat klausa digunakan sekaligus, urutannya tetap, yaitu blok begin, rescue, else, dan diakhiri ensure.

crystal Klausa else pada jalur berhasil
begin
  puts "mencoba"
rescue e
  puts "gagal: #{e.message}"
else
  puts "berhasil tanpa exception"
end
text Keluaran
mencoba
berhasil tanpa exception

Program penangan-dasar.cr berikut merangkum raise, rescue, ensure, dan nilai balik begin dalam satu program yang dapat dijalankan.

crystal penangan-dasar.cr
# Demonstrasi raise, rescue, dan ensure.
# Jalankan dengan: crystal penangan-dasar.cr

def bagi(a : Int32, b : Int32) : Int32
  raise ArgumentError.new("pembagi tidak boleh nol") if b == 0
  a // b
end

# begin/rescue adalah ekspresi: nilainya bisa diassign
hasil = begin
  bagi(10, 0)
rescue e : ArgumentError
  0
end
puts "hasil pembagian: #{hasil}"

# rescue tanpa tipe menangkap semua exception
begin
  raise "kegagalan tak terduga"
rescue e
  puts "ditangkap: #{e.message}"
end

# ensure selalu dijalankan
begin
  puts "membuka berkas"
  puts "memproses data"
ensure
  puts "menutup berkas"
end
bash
# menjalankan program penanganan dasar
crystal penangan-dasar.cr
text Keluaran
hasil pembagian: 0
ditangkap: kegagalan tak terduga
membuka berkas
memproses data
menutup berkas

Fungsi bagi melontarkan ArgumentError ketika pembaginya nol. Ekspresi begin hingga rescue pada penugasan variabel hasil bernilai 0 karena jalur rescue yang dieksekusi, dan nilai itu langsung dicetak. Bagian kedua memperlihatkan penangkap umum yang menangkap exception berbentuk string. Bagian ketiga tidak melontarkan exception apa pun, tetapi pesan menutup berkas dari klausa ensure tetap tampil.

Kesalahan umum#

  • Menulis retry untuk mengulang blok begin. Pernyataan itu tersedia di bahasa lain, tetapi Crystal tidak memilikinya sehingga compiler menolak programnya. Ulangan dilakukan dengan perulangan while yang memanggil kembali operasi di dalam begin dan mengubah kondisinya pada klausa rescue.
  • Menangkap semua exception dengan klausa tanpa tipe pada setiap panggilan. Kebiasaan ini menyembunyikan bug yang seharusnya terlihat, misalnya kesalahan logika yang terbungkus pesan umum. Tangkap tipe selengkap mungkin dan letakkan penangkap umum hanya pada batas terluar program.
  • Menempatkan penangkap umum di atas klausa bertipe. Kecocokan pertama selalu menang, sehingga klausa bertipe di bawahnya tidak pernah terjangkau. Urutan yang benar adalah klausa paling spesifik di atas dan penangkap umum paling bawah.

Ringkasan#

  • Pernyataan raise melontarkan instance Exception atau turunannya, dengan bentuk singkat raise "pesan" untuk string.
  • Klausa rescue menangkap exception berdasarkan tipe secara berurutan dari atas ke bawah, dan klausa tanpa tipe menjadi penangkap umum.
  • Klausa else hanya dijalankan ketika tidak ada exception, sedangkan ensure selalu dijalankan untuk pembersihan.
  • Struktur begin hingga rescue adalah ekspresi yang nilainya dapat ditugaskan, dan class exception khusus dibuat sebagai turunan Exception yang dapat membawa data tambahan.

Referensi#