Program dapat menemui keadaan yang tidak dapat dilanjutkan oleh alur normalnya, misalnya teks yang seharusnya berisi angka ternyata tidak dapat diuraikan. Dart menyediakan mekanisme exception untuk memisahkan alur utama program dari penanganan keadaan semacam itu, sehingga kode utama tetap terbaca dan penanganan kegagalannya terkumpul di satu tempat. Topik ini membahas throw untuk memunculkan exception, try dengan on dan catch untuk menangkapnya, finally untuk pembersihan yang wajib dijalankan, rethrow untuk meneruskan exception ke pemanggil, serta pembuatan tipe exception kustom. Perbedaan antara Exception dan Error juga dibahas karena keduanya menuntut perlakuan yang berbeda.
Menangkap exception dengan try dan on#
Pernyataan throw memunculkan sebuah objek exception dan langsung menghentikan eksekusi kode setelahnya, sedangkan blok try membungkus kode yang berpotensi memunculkan exception. Klausa on yang diikuti nama tipe membatasi penanganan hanya pada tipe tersebut, misalnya FormatException yang dimunculkan int.parse apabila teksnya bukan bilangan bulat yang sah. Apabila objek exceptionnya perlu diperiksa, tambahkan catch (e) setelah klausa on, sehingga bentuk lengkapnya menjadi on FormatException catch (e). Bentuk catch (e) tanpa on menangkap exception bertipe apa pun, sedangkan bentuk catch (e, s) menyediakan pula stack trace, yaitu jejak panggilan yang menunjukkan titik kejadian error.
Contoh berikut menguraikan tiga teks menjadi bilangan bulat. Dua di antaranya gagal, tetapi program tetap menyelesaikan sisa pekerjaannya karena kegagalan tersebut tertangkap. Ketiga teks tersebut sengaja dipilih karena masing-masing mewakili bentuk masukan yang berbeda, yaitu bilangan bulat yang sah, kata yang tidak dapat diuraikan, dan bilangan pecahan.
void main() {
final masukan = ["42", "tujuh", "3.5"];
for (final teks in masukan) {
try {
final nilai = int.parse(teks);
print("$teks berhasil diuraikan menjadi $nilai");
} on FormatException {
print("$teks bukan bilangan bulat yang sah");
}
}
print("program tetap berjalan sampai akhir");
}# menjalankan program dari folder topik
dart run uraian.dart42 berhasil diuraikan menjadi 42
tujuh bukan bilangan bulat yang sah
3.5 bukan bilangan bulat yang sah
program tetap berjalan sampai akhirKeluaran menunjukkan perbedaan mendasar antara kode yang gagal dan program yang gagal. Pemanggilan int.parse("tujuh") memunculkan FormatException, alur kode melompat ke klausa on, dan pesan errornya dicetak tanpa menghentikan perulangan. Elemen berikutnya tetap diproses, demikian pula pernyataan terakhir setelah blok try. Kegagalan yang sudah diperkirakan seperti ini layak ditangkap karena program memang memiliki langkah penanganannya.
finally untuk pembersihan#
Klausa finally menampung kode yang wajib dijalankan apa pun hasil blok try, baik blok itu selesai dengan sukses, selesai dengan exception tertangkap, maupun selesai dengan pernyataan return. Tempatnya paling tepat untuk pembersihan sumber daya, misalnya menutup berkas, menutup koneksi, atau mengosongkan status sementara. Tanpa finally, pembersihan hanya dijalankan pada jalur sukses, sehingga setiap jalur kegagalan perlu menulis ulang kode yang sama.
Contoh berikut menyimulasikan pembukaan dan penutupan sebuah berkas log. Exception dimunculkan sengaja di tengah blok try, dan return dipanggil di dalam catch, sehingga ketiga jalur keluar dari blok tersebut teruji sekaligus. Nama berkasnya hanya berupa teks biasa karena fokus contoh adalah urutan cetakan, bukan operasi berkas sungguhan.
void main() {
final berkas = "log-uji.txt";
try {
print("membuka $berkas");
throw Exception("isi berkas rusak");
} catch (e) {
print("ditangkap: $e");
return;
} finally {
print("menutup $berkas");
}
}# menjalankan program dari folder topik
dart run penutup.dartmembuka log-uji.txt
ditangkap: Exception: isi berkas rusak
menutup log-uji.txtBaris terakhir keluaran membuktikan perilaku utama finally. Pernyataan return di dalam catch seharusnya menghentikan fungsi main, tetapi blok finally tetap dijalankan lebih dahulu sebelum fungsi benar-benar kembali. Urutan keluarannya selalu sama karena finally tidak tergantung pada jalur yang diambil, dan inilah alasan pembersihan ditempatkan di sana. Apabila finally sendiri memunculkan exception baru, exception lama dari blok try tertutup oleh yang baru, sehingga tubuh finally sebaiknya hanya berisi pembersihan yang tidak berpotensi gagal.
Exception kustom#
Exception kustom dibuat dengan mendefinisikan sebuah kelas yang mengimplementasikan Exception. Kelas semacam itu membawa data konteks kegagalan, misalnya nilai yang ditolak dan bagian program tempat kegagalan itu terjadi, sehingga penanganannya dapat mengambil keputusan berdasarkan data tersebut. Mendefinisikan toString membuat objek exception mencetak pesan yang terbaca ketika muncul di log. Pilihan lainnya adalah extends Exception, yaitu mewarisi kelas dasar tersebut, tetapi mengimplementasikan antarmukanya saja sudah cukup karena Exception tidak memiliki anggota yang wajib diwarisi.
Contoh berikut berasal dari pencatatan nilai ujian. Fungsi catat menolak nilai di luar rentang 0 sampai 100 dengan memunculkan exception kustom yang membawa mata uji dan nilai yang bermasalah. Kelasnya didefinisikan pada bagian atas berkas yang sama, sehingga tipe baru itu dapat langsung digunakan oleh fungsi perekam dan penanganannya.
class NilaiTidakSahException implements Exception {
final String mataUji;
final int nilai;
NilaiTidakSahException(this.mataUji, this.nilai);
@override
String toString() =>
"NilaiTidakSahException: nilai $nilai untuk $mataUji "
"di luar rentang 0 sampai 100";
}
void catat(String mataUji, int nilai) {
if (nilai < 0 || nilai > 100) {
throw NilaiTidakSahException(mataUji, nilai);
}
print("$mataUji: $nilai dicatat");
}
void main() {
catat("Matematika", 86);
try {
catat("Sejarah", 120);
} on NilaiTidakSahException catch (e) {
print(e);
}
}# menjalankan program dari folder topik
dart run nilai-uji.dartMatematika: 86 dicatat
NilaiTidakSahException: nilai 120 untuk Sejarah di luar rentang 0 sampai 100Pemanggilan catat("Matematika", 86) berjalan normal, sedangkan pada pemanggilan kedua exception dimunculkan dan tertangkap oleh klausa on NilaiTidakSahException. Pesan yang dicetak berasal sepenuhnya dari toString kelas kustom tersebut, lengkap dengan konteks mata uji dan nilai yang ditolak. Klausa on pada tipe kustom juga memberi analyzer informasi tipe yang presisi, sehingga bidang mataUji dan nilai dapat diakses di dalam blok penanganan tanpa konversi tambahan.
Meneruskan exception dengan rethrow#
Pernyataan rethrow memunculkan kembali exception yang sedang ditangkap tanpa mengubah jejaknya. Kegunaannya muncul ketika sebuah fungsi hanya mampu menangani sebagian keadaan gagal, misalnya mencatat kejadian ke log, tetapi keputusan akhir seharusnya diambil oleh pemanggil. Menggunakan throw e pada objek yang sama bukan padanan yang tepat, karena throw memulai jejak baru dan menghilangkan titik kejadian aslinya, sedangkan rethrow mempertahankan jejak semula sehingga sumber error tetap dapat dilacak.
Contoh berikut memisahkan tanggung jawab menjadi dua tingkat. Fungsi bacaPengaturan mencatat kegagalan penguraian, lalu meneruskan exception tersebut kepada main yang menanganinya sampai tuntas. Pembagian ini meniru pola pustaka nyata, yaitu lapisan dalam melaporkan kejadian dan lapisan luar memutuskan akibatnya.
void bacaPengaturan(String teks) {
try {
final jumlah = int.parse(teks);
print("jumlah catatan: $jumlah");
} on FormatException {
print("pengaturan tidak dapat diuraikan");
rethrow;
}
}
void main() {
try {
bacaPengaturan("banyak");
} on FormatException {
print("ditangani oleh pemanggil tertinggi");
}
}# menjalankan program dari folder topik
dart run rethrow.dartpengaturan tidak dapat diuraikan
ditangani oleh pemanggil tertinggiKeluaran memperlihatkan dua penanganan yang bekerja pada satu exception yang sama. Pesan pertama berasal dari fungsi yang menangkap, dan pesan kedua berasal dari pemanggil yang menerima penerusan rethrow. Apabila rethrow diganti dengan throw e, pesan kedua tetap sama, tetapi jejak error kehilangan baris asalnya di dalam bacaPengaturan, sehingga kebiasaan yang dianjurkan adalah rethrow setiap kali exceptionnya diteruskan. Pembagian tanggung jawab seperti ini juga menghindari duplikasi logika penanganan yang sama pada beberapa tingkat fungsi.
Perbedaan Exception dan Error#
Dart membedakan dua keluarga objek yang dapat dimunculkan dengan throw. Keluarga Exception menampung keadaan gagal yang telah diperkirakan dan memiliki langkah penanganannya, misalnya teks yang gagal diuraikan. Keluarga Error menampung pelanggaran kontrak program, misalnya RangeError ketika indeks di luar panjang list atau _TypeError ketika tipe tidak sesuai. Objek Error biasanya berarti bug yang harus diperbaiki di kode sumbernya, bukan keadaan yang diharapkan tertangkap, sehingga dokumentasi resmi menganjurkan agar Error dibiarkan menghentikan program.
Contoh berikut sengaja mengakses indeks 10 pada list yang hanya memiliki tiga anggota tanpa penanganan apa pun. Perintah pembuatan berkasnya ditulis di folder uji sementara agar berkas contoh ini tidak bercampur dengan aset topik. Pengaksesan indeks di luar batas memunculkan RangeError dari dart:core tanpa memerlukan import tambahan.
# membuat berkas yang memunculkan error tak tertangkap
mkdir -p /tmp/uji/exception
cat > /tmp/uji/exception/range-uncaught.dart <<'EOF'
void main() {
final daftar = <int>[10, 24, 8];
print(daftar[10]);
}
EOF
# menjalankan berkas untuk melihat akibat error yang dibiarkan
dart run /tmp/uji/exception/range-uncaught.dartUnhandled exception:
RangeError (length): Invalid value: Not in inclusive range 0..2: 10
#0 List.[] (dart:core-patch/growable_array.dart)
#1 main (file:///tmp/uji/exception/range-uncaught.dart:3:15)
#2 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:313:19)
#3 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:192:12)Program berhenti dengan pesan Unhandled exception karena tidak ada blok try di jalur pemanggilannya. Jejak di bawah pesan terbaca dari atas ke bawah, dengan frame bernomor nol sebagai titik kejadian, yaitu List.[] pada baris 3 berkas contoh. Bandingkan dengan contoh FormatException pada bagian pertama, yang membiarkan program berjalan sampai akhir. Keduanya memperlihatkan pembagian peran yang dianjurkan, yaitu Exception untuk keadaan gagal yang ditangkap dan ditangani, sedangkan Error untuk bug yang dibiarkan terlihat dan diperbaiki.
Ringkasan#
throwmemunculkan objek exception dan menghentikan alur kode, sedangkantrydenganondancatchmenangkapnya berdasarkan tipe.- Blok
finallydijalankan pada semua jalur keluar daritry, termasuk ketikareturndipanggil di dalamcatch. - Exception kustom dibuat dengan kelas yang mengimplementasikan
Exceptionbeserta bidang konteks dantoStringyang terbaca. rethrowmeneruskan exception yang ditangkap tanpa menghilangkan jejak asalnya, berbeda denganthrow eyang memulai jejak baru.Exceptionmenampung keadaan gagal yang diperkirakan dan ditangani, sedangkanErrormenandai bug program yang sebaiknya dibiarkan menghentikan program.
Referensi#
- Error handling — dart.dev
— Dokumentasi resmi
throw,try,on,catch,finally, danrethrowbeserta anjuran menangkapException. - Exception class — dart.dev
— Referensi API kelas
Exceptionbeserta daftar subkelas bawaannya. - RangeError class — dart.dev
— Referensi API
RangeErrorsebagai contoh keluargaError.