Exception

Penanganan exception dengan Try/Catch/Finally dan pembuatan exception kustom.

Daftar isi

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.

vb Contoh: kerangka Try/Catch/Finally
' 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 Try

Blok 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.

Alur eksekusi Try/Catch/FinallyBlok TrydijalankanTerjadiexception?Catch pertama yangcocok dijalankanBlok FinallydijalankanProgramberlanjutyatidakTanpa Catch yang cocok, exception diteruskan ke pemanggil
Alur eksekusi Try/Catch/Finally

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.

vb StatistikNilai.vb
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 Module
text Keluaran
80 \ 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.txt

Keluaran 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.

vb Contoh: alternatif tanpa exception dengan TryParse
' tidak-dijalankan
If Integer.TryParse(teks, nilai) Then
    Console.WriteLine($"'{teks}' menjadi {nilai}")
Else
    Console.WriteLine($"'{teks}' bukan angka yang sah")
End If

Gunakan 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.

vb RiwayatCadangan.vb
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 Module
text Keluaran
Pengukuran 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 dibuat

Keluaran 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.

vb Contoh: kerangka exception kustom
' 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#