dartexceptionoopMahir4 mnt baca

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

dart
// 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:

dart
// 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

dart
// 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.
  • extends exception bawaan: mewarisi perilaku + otomatis tertangkap oleh catch induknya.

Kesalahan umum

1. Exception tanpa data

dart
// 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.

text
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
  }
}