Custom Exception
Merancang exception sendiri: hierarki error yang bermakna dan informatif.
Error yang bercerita
throw Exception('gagal') itu seperti alarm yang berbunyi tanpa menyebut ruangan mana. Custom exception memberi error identitas, data, dan perilaku: SaldoKurang(kurangnya: 50000) langsung menjelaskan segalanya.
Analogi: exception generik itu seperti surat tanpa nama pengirim. Custom exception itu seperti surat berkop resmi: siapa, apa, berapa, jelas.
Kenapa custom exception ada?
Tiga alasan: presisi penangkapan (tangkap SaldoKurang tanpa menangkap bug lain), data kontekstual (bawa info relevan: kurang berapa, id mana), dan dokumentasi (daftar exception = daftar hal yang bisa salah).
Merancang custom exception
// Pola 1: implements Exception (paling umum)
class SaldoKurang implements Exception {
final double kurangnya;
const SaldoKurang(this.kurangnya);
@override
String toString() => 'SaldoKurang: kurang Rp${kurangnya.toStringAsFixed(0)}';
}
class Rekening {
double saldo;
Rekening(this.saldo);
void tarik(double jumlah) {
if (jumlah <= 0) {
throw ArgumentError('Jumlah harus positif, dapat: $jumlah');
}
if (jumlah > saldo) {
throw SaldoKurang(jumlah - saldo);
}
saldo -= jumlah;
}
}
void main() {
var r = Rekening(50000);
try {
r.tarik(80000);
} on SaldoKurang catch (e) {
print('Transaksi ditolak: $e');
print('Kurangnya: ${e.kurangnya}'); // akses data spesifik!
}
}Penangkap bisa mengakses e.kurangnya karena tipenya spesifik. Dengan Exception('...') generik, data itu terkubur dalam string.
Contoh kedua, hierarki exception:
// Induk untuk semua error domain pembayaran
abstract class ErrorPembayaran implements Exception {
String get pesan;
}
class SaldoKurang2 implements ErrorPembayaran {
final double kurangnya;
const SaldoKurang2(this.kurangnya);
@override
String get pesan => 'Saldo kurang Rp$kurangnya';
@override
String toString() => pesan;
}
class LimitHarian implements ErrorPembayaran {
final double limit;
const LimitHarian(this.limit);
@override
String get pesan => 'Melebihi limit harian Rp$limit';
@override
String toString() => pesan;
}
void prosesBayar(double jumlah, double saldo) {
if (jumlah > saldo) throw SaldoKurang2(jumlah - saldo);
if (jumlah > 10000000) throw LimitHarian(10000000);
print('Bayar Rp$jumlah berhasil');
}
void main() {
for (var jml in [50000, 50000000, 5000]) {
try {
prosesBayar(jml, 100000);
} on SaldoKurang2 catch (e) {
print('Khusus saldo: $e');
} on ErrorPembayaran catch (e) {
print('Error pembayaran umum: $e');
}
}
}Hierarki memungkinkan dua level penangkapan: spesifik (on SaldoKurang2) atau umum (on ErrorPembayaran). Urutan catch dari spesifik ke umum penting!
implements Exception vs extends
// Opsi A: implements Exception (disarankan)
class AppError implements Exception {
final String pesan;
const AppError(this.pesan);
@override
String toString() => 'AppError: $pesan';
}
// Opsi B: extends StateError/Exception konkret
class ValidasiGagal extends ArgumentError {
ValidasiGagal(String pesan) : super(pesan);
}implements Exception: mulai dari nol, bebas total. Paling umum.extendsexception bawaan: mewarisi perilaku + otomatis tertangkap oleh catch induknya.
Kesalahan umum
1. Exception tanpa data
// Kurang berguna
class Gagal implements Exception {}Exception tanpa field memaksa penangkap menebak. Selalu bawa minimal pesan/data relevan.
2. toString tidak di-override
Tanpa toString, print(e) menghasilkan Instance of 'SaldoKurang'. Selalu override agar log bermakna.
3. Hierarki terlalu dalam
2 level (induk domain + spesifik) cukup untuk kebanyakan kasus. Hierarki 5 level membuat penangkapan membingungkan.
4. Exception untuk alur normal
Jangan throw untuk kondisi yang diharapkan sering terjadi (mis. "data tidak ditemukan" dalam pencarian rutin). Exception untuk keadaan luar biasa; alur normal pakai return null/Result.
Kesimpulan
Custom exception = error dengan identitas dan data. Rancang hierarki 2 level, bawa data kontekstual, override toString, dan tangkap dari spesifik ke umum. Error yang bercerita mempercepat debugging berkali lipat.
Catatan teknis: Untuk API publik, dokumentasikan exception apa saja yang bisa dilempar tiap method (di doc comment). Pengguna API-mu berhak tahu tanpa membaca implementasi.
Tantangan
Exception umur
Buat UmurTidakValid implements Exception dengan field nilai dan pesan bermakna. Fungsi daftarUmur(int u): throw jika u < 0 atau > 150. Tangkap spesifik dan cetak nilainya.
class UmurTidakValid implements Exception {
// TODO: field nilai + toString
}
void daftarUmur(int u) {
// TODO: throw jika u < 0 || u > 150
}
void main() {
for (var u in [25, -5, 200]) {
// TODO: try-catch spesifik
}
}