Ruby menyatakan kegagalan pada waktu program berjalan sebagai objek bernama exception, yaitu nilai yang dibangkitkan saat operasi gagal dan ditangkap oleh kode yang siap mengatasinya. Topik ini membahas hierarki singkat kelas exception, struktur begin/rescue/else/ensure, metode raise dengan pesan dan kelas, urutan penangkapan beberapa jenis error, kelas exception kustom, serta bentuk modifier rescue satu baris pada metode. Setelah membacanya, kegagalan program dapat ditangani secara terkendali alih-alih program berhenti begitu saja.
Hierarki kelas exception#
Semua kelas exception di Ruby mewarisi kelas Exception, dan hampir seluruh error harian merupakan turunan dari StandardError. Turunan yang paling sering ditemui antara lain RuntimeError, ArgumentError, TypeError, NoMethodError, NameError, dan ZeroDivisionError. Pernyataan rescue yang ditulis tanpa nama kelas hanya menangkap turunan StandardError, sehingga error di luar golongan itu tetap menghentikan program. Objek exception membawa informasi kejadian yang dapat dibaca melalui metode message untuk teks penjelasannya, sedangkan kelasnya diperoleh dari metode class.
nilai = [10, 5, 0]
nilai.each do |pembagi|
begin
hasil = 100 / pembagi
puts "100 / #{pembagi} = #{hasil}"
rescue => e
puts "#{e.class}: #{e.message}"
end
end100 / 10 = 10
100 / 5 = 20
ZeroDivisionError: divided by 0Program membagi 100 dengan setiap anggota array. Pembagian dengan 10 dan 5 menghasilkan nilai seperti biasa, sedangkan pembagian dengan 0 membangkitkan ZeroDivisionError. Karena pembangkitan berada di dalam blok begin, alur program melompat ke cabang rescue dan mencetak kelas beserta pesannya, lalu perulangan berlanjut ke anggota berikutnya. Bentuk rescue => e menampung objek exception ke dalam variabel e sehingga kelas dan pesannya dapat dibaca. Tanpa penangkapan ini, program berhenti pada pembagian terakhir dan baris setelahnya tidak pernah dijalankan.
begin/rescue/else/ensure#
Struktur penanganan yang lengkap menggunakan empat bagian, yaitu begin, rescue, else, dan ensure. Bagian rescue berjalan apabila terjadi error, bagian else berjalan apabila tidak ada error sama sekali, dan bagian ensure selalu berjalan, baik error terjadi maupun tidak. Pembagian tugas ini memisahkan kode utama dari kode pembersihan, sehingga penanganan error tidak bercampur dengan langkah yang memang harus dilakukan di akhir. Urutan penulisannya juga tetap, yaitu rescue lebih dahulu, kemudian else, dan ensure paling akhir. Struktur ini boleh membungkus sekelompok pernyataan apa pun, bukan hanya satu panggilan metode.
# membaca suhu dari sebuah array dengan begin/rescue/else/ensure
def ambil_suhu(daftar, indeks)
begin
nilai = daftar.fetch(indeks)
rescue IndexError => e
puts "Gagal: #{e.message}"
else
puts "Berhasil: #{nilai} derajat"
ensure
puts "Selesai memeriksa indeks #{indeks}"
end
end
suhu = [24, 26, 25]
ambil_suhu(suhu, 1)
ambil_suhu(suhu, 9)Berhasil: 26 derajat
Selesai memeriksa indeks 1
Gagal: index 9 outside of array bounds: -3...3
Selesai memeriksa indeks 9Berkas di atas dijalankan dengan perintah ruby baca-suhu.rb. Pemanggilan pertama mengambil indeks 1 yang ada, sehingga cabang else mencetak keberhasilan sebelum ensure mencatat penyelesaian. Pemanggilan kedua menggunakan indeks 9 yang berada di luar batas array, dan metode fetch membangkitkan IndexError dengan pesan yang menyebutkan batas sahnya. Cabang rescue menangkapnya, kemudian ensure tetap mencetak pesan penyelesaian. Urutan keluaran ini menunjukkan bahwa ensure dijalankan pada kedua kasus, baik berhasil maupun gagal, sehingga ensure merupakan tempat yang tepat untuk menutup sumber daya atau mencatat akhir pekerjaan.
Memunculkan error dengan raise#
Metode raise membangkitkan exception secara sengaja dan biasanya digunakan untuk menolak masukan yang tidak sah. Bentuk paling ringkas adalah raise "pesan", yang membangkitkan RuntimeError dengan pesan tersebut, sedangkan bentuk raise Kelas, "pesan" memilih kelasnya secara eksplisit. Kelas yang dipilih sebaiknya menggambarkan jenis kesalahannya, misalnya ArgumentError untuk masukan yang salah, agar pemanggil dapat menangkap jenis tertentu tanpa menebak-nebak. Eksekusi metode berhenti pada titik raise, dan error merambat ke pemanggilnya sampai menemukan cabang rescue yang cocok. Apabila tidak ada yang menangkap, program berhenti dan menampilkan pesan errornya.
def validasi_suhu(suhu)
raise ArgumentError, "suhu di bawah -273 derajat" if suhu < -273
suhu
end
begin
validasi_suhu(-300)
rescue ArgumentError => e
puts "#{e.class}: #{e.message}"
endArgumentError: suhu di bawah -273 derajatMetode validasi menolak suhu di bawah nol absolut dengan membangkitkan ArgumentError beserta pesan penjelasnya. Pemanggil memasukkan nilai -300, sehingga cabang rescue menangkapnya dan mencetak kelas serta pesan yang dibangkitkan. Pola ini membuat metode berhenti pada titik masalah, sedangkan keputusan cara mengatasinya diserahkan kepada pemanggil.
Urutan rescue dari spesifik ke umum#
Satu blok begin boleh memuat beberapa cabang rescue dengan kelas yang berbeda-beda. Ruby menilai cabang-cabang itu dari atas ke bawah dan menjalankan cabang pertama yang cocok, sehingga urutan penulisannya menentukan hasil. Cabang dengan kelas paling spesifik diletakkan terlebih dahulu, sedangkan cabang dengan StandardError sebagai penangkap terakhir diletakkan paling bawah. Cabang terakhir boleh menggunakan bentuk rescue StandardError => e agar objek errornya tetap tersedia untuk dicetak. Kelas induk yang ditulis di atas kelas turunannya juga membuat cabang tersebut tak pernah tercapai, karena kecocokan pertama selalu menang.
def konversi(teks)
Integer(teks)
end
["25", "dua", nil].each do |masukan|
begin
puts "Hasil: #{konversi(masukan)}"
rescue ArgumentError
puts "ArgumentError: teks bukan bilangan"
rescue TypeError
puts "TypeError: masukan bukan teks"
rescue StandardError => e
puts "Error umum: #{e.class}"
end
endHasil: 25
ArgumentError: teks bukan bilangan
TypeError: masukan bukan teksTeks "25" berhasil dikonversi menjadi bilangan. Teks "dua" membangkitkan ArgumentError karena isinya bukan bilangan, sedangkan nil membangkitkan TypeError karena nil bukan teks sama sekali. Setiap error jatuh pada cabang yang paling cocok, dan cabang StandardError berfungsi sebagai penangkap sisanya meskipun tidak terpakai pada contoh ini. Cara yang sama berlaku untuk kelas exception kustom, yaitu menuliskan cabang kustomnya di atas cabang StandardError.
Kelas exception kustom#
Error khusus untuk suatu domain dapat dinyatakan sebagai kelas yang mewarisi StandardError. Kelas semacam ini memberi nama yang bermakna bagi kegagalan tertentu, sehingga pemanggil dapat menangkapnya dengan tepat alih-alih menangkap error umum. Kelas kustom boleh menambahkan atribut, misalnya nilai yang menyebabkan kegagalan, dengan memanggil super untuk meneruskan pesan ke kelas induknya. Nama kelasnya sebaiknya menyatakan kegagalan itu sendiri, sehingga cabang rescue yang membacanya langsung bermakna. Karena mewarisi StandardError, exception kustom yang tidak ditangkap tetap menghentikan program seperti error lain.
# Kelas exception kustom untuk sensor suhu ruangan.
class SuhuTidakWajar < StandardError
attr_reader :suhu
def initialize(suhu, batas)
@suhu = suhu
super("Suhu #{suhu} derajat melampaui batas #{batas} derajat")
end
end
def periksa_suhu(suhu)
batas = 40
raise SuhuTidakWajar.new(suhu, batas) if suhu > batas
puts "Suhu #{suhu} derajat aman"
end
[31, 44].each do |suhu|
begin
periksa_suhu(suhu)
rescue SuhuTidakWajar => e
puts "#{e.class}: #{e.message}"
end
endSuhu 31 derajat aman
SuhuTidakWajar: Suhu 44 derajat melampaui batas 40 derajatBerkas di atas dijalankan dengan perintah ruby pantau-suhu.rb. Kelas SuhuTidakWajar menyimpan nilai suhu pada atribut suhu, kemudian meneruskan pesan ke StandardError melalui super. Suhu 31 masih di bawah batas sehingga program mencetak pesan bahwa suhu tersebut aman, sedangkan suhu 44 membangkitkan exception kustom yang ditangkap berdasarkan kelasnya. Pemanggil cukup menyebut rescue SuhuTidakWajar tanpa perlu memeriksa pesan, dan nilai suhu milik objek error tetap dapat dibaca dari atributnya.
rescue satu baris pada metode#
Ruby menyediakan bentuk modifier satu baris berupa ekspresi rescue cadangan, yang menangkap StandardError dari ekspresi tersebut dan menggantinya dengan nilai cadangan. Bentuk ini paling sering digunakan sebagai badan metode yang ringkas, misalnya untuk memberi nilai bawaan ketika data yang diminta tidak ada. Penanganan yang memerlukan lebih dari satu langkah tetap lebih jelas ditulis dengan struktur begin/rescue penuh. Modifier ini menangkap StandardError beserta turunannya saja, sama seperti rescue tanpa kelas. Nilai cadangannya langsung menggantikan error tanpa jejak, sehingga bentuk ini hanya layak bila kegagalan memang wajar dan tidak perlu dicatat.
def ambil_skor(nilai, indeks)
nilai.fetch(indeks) rescue 0
end
skor = [82, 91, 77]
p ambil_skor(skor, 0)
p ambil_skor(skor, 9)82
0Pemanggilan pertama menemukan indeks 0 dan mengembalikan 82 tanpa menyentuh cadangan. Pemanggilan kedua membangkitkan IndexError di dalam ekspresi, dan modifier rescue menggantinya dengan 0. Metode menjadi tahan terhadap indeks yang salah tanpa struktur begin eksplisit, meskipun informasi tentang error aslinya tidak tersimpan di mana pun.
Kesalahan umum#
Kesalahan pertama adalah menangkap Exception alih-alih StandardError. Bentuk rescue Exception menangkap segalanya, termasuk Interrupt saat program dihentikan dengan Ctrl+C dan NoMemoryError saat memori habis, sehingga program menjadi sulit dihentikan dan kegagalan serius tersamar. Gunakan rescue tanpa kelas atau sebutkan kelas yang memang ingin ditangkap.
Kesalahan kedua adalah meletakkan cabang umum di atas cabang spesifik. Karena penilaian berhenti pada kecocokan pertama, cabang yang lebih spesifik di bawahnya tidak pernah tercapai.
begin
Integer("dua")
rescue StandardError
puts "Tertangkap oleh cabang umum"
rescue ArgumentError
puts "Cabang ini tidak pernah tercapai"
endTertangkap oleh cabang umumError ArgumentError seharusnya jatuh pada cabangnya sendiri, tetapi StandardError di atasnya menangkap lebih dahulu. Perbaikannya adalah menyusun cabang dari yang paling spesifik ke yang paling umum.
Kesalahan ketiga adalah menelan error tanpa jejak, misalnya rescue tanpa pencetakan apa pun. Program tidak berhenti, tetapi penyebab masalahnya juga tidak terlihat pada saat diperiksa. Catat setiap penangkapan secara minimal dengan e.class dan e.message, atau biarkan error merambat bila memang tidak ada tindakan yang bermakna.
Ringkasan#
- Semua kelas exception mewarisi
Exception, danrescuetanpa kelas menangkap turunanStandardErrorsepertiZeroDivisionError,ArgumentError, danTypeError. begin/rescue/else/ensurememisahkan kode utama, penanganan error, langkah saat berhasil, dan pembersihan yang selalu berjalan.raisemembangkitkan exception dengan pesan atau dengan kelas dan pesan, sedangkan kelas kustom mewarisiStandardErrordan dapat menambah atribut sendiri.- Beberapa cabang
rescuedinilai dari atas ke bawah, sehingga urutan dari spesifik ke umum menentukan cabang penangkapnya. - Bentuk modifier
ekspresi rescue cadanganmemberikan nilai pengganti secara ringkas pada badan metode.
Referensi#
- Exception — Ruby — Dokumentasi kelas Exception beserta hierarki turunannya dan metode message.
- Exceptions — Ruby — Dokumentasi resmi sintaks begin/rescue/ensure beserta raise dan modifier rescue.
- Kernel#raise — Ruby — Dokumentasi metode raise dengan seluruh bentuk pemanggilannya.