Exception adalah objek yang dibuat oleh runtime ketika sebuah kesalahan terjadi saat program berjalan, misalnya pembagian dengan nol atau pembacaan berkas yang tidak ada. Tanpa penanganan, exception menghentikan program dan menampilkan jejak tumpukan (stack trace) di terminal sebelum prosesnya berakhir. Topik ini membahas blok Try/Catch/Finally, urutan blok Catch dari yang spesifik ke yang umum, filter Catch dengan When, melempar ulang dengan Throw, serta pembuatan exception kustom yang membawa data tambahan.
Struktur Try/Catch/Finally#
Pernyataan Try/Catch/Finally membungkus kode yang berpotensi gagal ke dalam tiga bagian dengan tugas yang tegas. Blok Try memuat kode yang dapat memunculkan exception. Blok Catch menangkap exception tertentu dan menjalankan penanganannya. Blok Finally selalu dijalankan, baik exception terjadi maupun tidak, sehingga blok ini menjadi tempat yang tepat untuk pekerjaan pembersihan.
Kerangkanya terbaca sebagai berikut.
' tidak-dijalankan
Try
' kode yang berpotensi memunculkan exception
Catch ex As JenisTertentuException
' penanganan untuk jenis yang spesifik
Catch ex As Exception
' penanganan paling umum, ditulis paling akhir
Finally
' pembersihan yang selalu dijalankan
End TryBlok Catch dan Finally sama-sama opsional, tetapi minimal satu dari keduanya harus ada, sehingga Try yang tidak memuat keduanya tidak dapat dikompilasi. Satu Try boleh memuat beberapa blok Catch untuk jenis exception yang berbeda. Apabila tidak ada exception yang terjadi, seluruh blok Catch dilewati dan eksekusi langsung menuju Finally. Sebaliknya, blok Finally tetap berjalan meskipun exception terjadi dan ditangkap, sehingga jaminan pembersihannya tidak bergantung pada hasil eksekusi.
Diagram berikut merangkum alur tersebut, yaitu blok Try dieksekusi terlebih dahulu, percabangan bergantung pada munculnya exception, dan blok Finally selalu ditempuh sebelum program berlanjut.
Urutan blok Catch dari spesifik ke umum#
Ketika exception terjadi, runtime memeriksa blok Catch satu per satu dari atas ke bawah dan menggunakan blok pertama yang jenisnya cocok. Karena itu blok untuk jenis yang spesifik harus ditulis lebih dahulu. Contoh konkretnya adalah FileNotFoundException yang merupakan turunan IOException, sehingga Catch FileNotFoundException wajib berada di atas Catch IOException. Blok yang lebih umum di atasnya akan merebut setiap kejadian sebelum sempat menurun ke blok di bawahnya.
Program berikut memuat tiga situasi yang sering memunculkan exception, yaitu pembagian dengan nol, penguraian teks yang bukan angka, dan pembacaan berkas yang tidak ada. Jalankan dengan dotnet run, lalu cocokkan urutan keluarannya dengan susunan blok Catch pada kode.
Option Strict On
Imports System.IO
Module Program
Sub CobaBagi(total As Integer, jumlah As Integer)
Try
Dim rata As Integer = total \ jumlah
Console.WriteLine($"{total} \ {jumlah} = {rata}")
Catch ex As DivideByZeroException
Console.WriteLine($"DivideByZeroException: {ex.Message}")
Finally
Console.WriteLine("Finally percobaan pembagian dijalankan")
End Try
End Sub
Sub Main()
CobaBagi(80, 4)
CobaBagi(80, 0)
Dim masukan() As String = {"95", "sembilan puluh", "x9"}
For Each teks As String In masukan
Try
Dim nilai As Integer = Integer.Parse(teks)
Console.WriteLine($"'{teks}' diurai menjadi {nilai}")
Catch ex As FormatException When teks.Length > 5
Console.WriteLine($"'{teks}' tidak sah dan teksnya panjang")
Catch ex As FormatException
Console.WriteLine($"FormatException: {ex.Message}")
End Try
Next
Try
Dim isi As String = File.ReadAllText("arsip-nilai.txt")
Console.WriteLine(isi)
Catch ex As FileNotFoundException
Console.WriteLine($"FileNotFoundException: {IO.Path.GetFileName(ex.FileName)}")
Catch ex As IOException
Console.WriteLine($"IOException: {ex.Message}")
End Try
End Sub
End Module80 \ 4 = 20
Finally percobaan pembagian dijalankan
DivideByZeroException: Attempted to divide by zero.
Finally percobaan pembagian dijalankan
'95' diurai menjadi 95
'sembilan puluh' tidak sah dan teksnya panjang
FormatException: The input string 'x9' was not in a correct format.
FileNotFoundException: arsip-nilai.txtKeluaran memperlihatkan perilaku tiap bagian secara berurutan. Percobaan pertama berhasil karena 80 dibagi 4 sama dengan 20, sedangkan percobaan kedua memunculkan DivideByZeroException yang ditangkap dan pesannya ditampilkan. Pesan exception pada terminal berbahasa Inggris karena berasal dari runtime. Penguraian “95” berhasil, “sembilan puluh” jatuh ke blok dengan filter When yang dibahas pada bagian berikutnya, dan “x9” jatuh ke Catch FormatException tanpa filter. Pembacaan arsip-nilai.txt yang tidak ada ditangkap FileNotFoundException sebelum sempat mencapai Catch IOException di bawahnya, yang membuktikan urutan pemeriksaan dari yang spesifik.
Filter Catch dengan When#
Kata When menambahkan syarat tambahan pada blok Catch sehingga exception yang sama dapat diarahkan ke penanganan yang berbeda. Blok Catch bersyarat hanya dijalankan apabila syaratnya benar, sedangkan exception yang tidak lolos filter berlanjut ke blok Catch berikutnya. Urutan penulisannya tetap dari yang syaratnya paling sempit ke yang paling luas.
Pada program StatistikNilai.vb, masukan “sembilan puluh” memicu FormatException dan panjangnya melebihi lima karakter, sehingga masuk ke blok dengan When. Masukan “x9” juga memicu FormatException, tetapi panjangnya hanya dua karakter sehingga filter menolaknya dan Catch FormatException tanpa filter yang mengambil alih. Pola ini berguna untuk memberi pesan berbeda menurut kondisi masukan tanpa menambah jenis exception baru.
Apabila kegagalan penguraian merupakan kemungkinan yang wajar, metode Integer.TryParse lebih tepat daripada blok Try/Catch. Metode ini mengembalikan nilai Boolean dan mengisi variabel tujuan tanpa memunculkan exception sama sekali.
' tidak-dijalankan
If Integer.TryParse(teks, nilai) Then
Console.WriteLine($"'{teks}' menjadi {nilai}")
Else
Console.WriteLine($"'{teks}' bukan angka yang sah")
End IfGunakan TryParse untuk masukan yang memang sering salah, dan cadangkan Try/Catch untuk kegagalan yang jarang terjadi. Dengan pembagian tugas itu, exception tetap bermakna sebagai sinyal kejadian yang tidak normal, bukan jalur logika sehari-hari.
Melempar ulang dengan Throw#
Kata Throw memunculkan exception, dan penulisan Throw tanpa argumen di dalam blok Catch melempar ulang exception yang sedang ditangani. Melempar ulang berguna apabila sebuah prosedur hanya ingin mencatat kejadian, tetapi keputusan penanganannya diserahkan kepada pemanggil. Jejak tumpukan aslinya tetap dipertahankan, sehingga lokasi kesalahan pertama tidak hilang tertutup lokasi lemparan ulangnya.
Program berikut menggabungkan exception kustom dan melempar ulang dalam satu alur pencatatan suhu. Prosedur SuhuTerkakhir membaca berkas cadangan yang memang belum dibuat pada contoh ini, mencatat kejadian itu ke log, lalu melempar ulang FileNotFoundException kepada pemanggilnya.
Option Strict On
Imports System.IO
Module Program
' Exception kustom untuk pengukuran suhu yang berada di luar batas wajar
Class SuhuTidakWajarException
Inherits Exception
Public Property Suhu As Integer
Public Sub New(pesan As String, nilaiSuhu As Integer)
MyBase.New(pesan)
Suhu = nilaiSuhu
End Sub
End Class
Sub CatatPengukuran(suhu As Integer)
If suhu < -50 OrElse suhu > 60 Then
Throw New SuhuTidakWajarException(
$"Suhu {suhu} di luar batas -50 sampai 60", suhu)
End If
Console.WriteLine($"Pengukuran {suhu} dicatat")
End Sub
Function SuhuTerkakhir() As Integer
Try
Dim isi As String = File.ReadAllText("cadangan-suhu.txt")
Return Integer.Parse(isi)
Catch ex As FileNotFoundException
Console.WriteLine("[log] berkas cadangan tidak ada, exception dilempar ulang")
Throw
End Try
End Function
Sub Main()
CatatPengukuran(23)
Try
CatatPengukuran(75)
Catch ex As SuhuTidakWajarException
Console.WriteLine($"{ex.Message} (nilai tercatat: {ex.Suhu})")
End Try
Try
Dim suhu As Integer = SuhuTerkakhir()
Console.WriteLine($"Suhu terkakhir: {suhu}")
Catch ex As FileNotFoundException
Console.WriteLine("Ditangani di pemanggil: arsip cadangan belum dibuat")
End Try
End Sub
End ModulePengukuran 23 dicatat
Suhu 75 di luar batas -50 sampai 60 (nilai tercatat: 75)
[log] berkas cadangan tidak ada, exception dilempar ulang
Ditangani di pemanggil: arsip cadangan belum dibuatKeluaran menunjukkan tiga kejadian secara berurutan. Pengukuran 23 berada di dalam batas dan langsung dicatat. Pengukuran 75 memunculkan SuhuTidakWajarException yang ditangkap di Main beserta properti Suhu miliknya. Pada pemanggilan SuhuTerkakhir, prosedur bagian dalam mencatat log lalu melempar ulang, sehingga pesan terakhir berasal dari Catch yang berada di Main dan bukan dari prosedur pembacanya.
Exception kustom#
Exception kustom dibuat dengan kelas yang mewarisi System.Exception. Kelas semacam itu menambahkan data yang relevan dengan masalahnya, misalnya nilai yang ditolak atau batas yang dilanggar, sehingga blok Catch di pemanggil dapat membaca rincian tersebut tanpa mengurai pesan teksnya. Nama kelasnya diakhiri kata Exception agar jenis exception langsung terbaca dari kode yang melemparkannya.
' tidak-dijalankan
Class NamaKustomException
Inherits Exception
Public Property DataTambahan As String
Public Sub New(pesan As String, data As String)
MyBase.New(pesan)
DataTambahan = data
End Sub
End Class
' Lemparkan dengan Throw New NamaKustomException("pesan", "data")Pada RiwayatCadangan.vb, kelas SuhuTidakWajarException menambahkan properti Suhu dan meneruskan pesannya ke konstruktor kelas dasar melalui MyBase.New. Konstruktor wajib ditulis karena Throw selalu membuat objek exception melaluinya. Properti tambahan boleh lebih dari satu, dan pola yang sama berlaku untuk membawa data apa pun yang berguna bagi penanganan, misalnya nama berkas, rentang yang sah, atau waktu kejadian.
Kesalahan umum#
Kesalahan pertama adalah menulis satu blok Catch ex As Exception untuk semua kejadian. Blok semacam itu juga menangkap kesalahan pemrograman seperti NullReferenceException, sehingga bug tersembunyi di balik penanganan yang tampak normal. Tulis blok Catch untuk jenis yang dikenal lebih dahulu dan jadikan Catch generik sebagai penutup terakhir.
Kesalahan kedua adalah menggunakan exception sebagai alur kendali biasa. Melempar dan menangkap jauh lebih mahal daripada percabangan If, dan TryParse telah tersedia untuk penguraian yang sering gagal. Sisakan exception untuk keadaan yang benar-benar tidak dapat dilanjutkan.
Kesalahan ketiga adalah menaruh kode biasa di dalam blok Try. Kode semacam itu tidak memperoleh perlindungan apa pun dan hanya memperlebar area yang perlu diperiksa saat mencari sumber exception. Pindahkan pekerjaan yang tidak berisiko ke luar Try, dan serahkan pembersihannya kepada Finally atau blok Using yang dibahas pada topik tentang disposisi.
Ringkasan#
- Try/Catch/Finally membungkus kode yang berpotensi gagal, blok Catch menangkap jenis exception tertentu, dan Finally selalu dijalankan baik terjadi exception maupun tidak.
- Blok Catch diperiksa dari atas ke bawah, sehingga jenis yang spesifik ditulis lebih dahulu, dan filter When menambahkan syarat pada Catch yang bersangkutan.
- Throw melempar exception, bentuk Throw tanpa argumen melempar ulang yang sedang ditangani, dan exception kustom dibuat dengan kelas turunan Exception yang membawa data tambahan melalui konstruktor serta propertinya.
Referensi#
- Try…Catch…Finally Statement — Microsoft Learn — Sintaks Try/Catch/Finally pada VB beserta aturan urutan dan jumlah blok Catch.
- Throw Statement — Microsoft Learn — Bentuk Throw dengan dan tanpa argumen untuk melempar ulang.
- Exception Class — Microsoft Learn — Kelas dasar semua exception beserta properti Message yang dipakai pada contoh.
- Best Practices for Exceptions — Microsoft Learn — Kapan exception ditangkap dan kapan percabangan biasa lebih tepat.