← Kembali ke kursus
Pelajaran kursus · Go
Pelajaran 32

Error, Panic, & Recover

Pola error Go, plus panic dan recover untuk keadaan darurat.

Pahami konsepnya

Go menangani kegagalan yang diharapkan lewat nilai biasa, bukan pengecualian. Fungsi yang bisa gagal mengembalikan satu nilai tambahan bertipe error; nil berarti sukses, selain itu berarti masalah. Konvensinya, nilai error menempati posisi terakhir dan diperiksa dengan if err != nil sesegera mungkin.

Buat error baru dengan errors.New("pesan") atau fmt.Errorf dengan verb %w untuk membungkus error lama dengan konteks tambahan — bungkutan %w bisa dibuka lagi dengan errors.Is dan errors.As untuk memeriksa akar masalahnya. Pemanggil bebas memilih: berhenti dengan return atau log.Fatal, mencoba ulang, atau meneruskan masalah ke atas.

Panic adalah jalur darurat yang berbeda: untuk keadaan yang tak seharusnya terjadi (indeks nyasar, invariant pecah). Panic merambat naik dan menghentikan program — kecuali ditangkap oleh recover yang dipasang lewat defer di fungsi yang sama. Pasangan defer + recover mengubah panic menjadi error biasa; ini yang dilakukan framework HTTP agar satu handler rusak tidak menjatuhkan server.

Coba contoh kode

package main

import (
    "errors"
    "fmt"
)

func bagi(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("pembagi tidak boleh nol")
    }
    return a / b, nil
}

func main() {
    hasil, err := bagi(10, 2)
    if err != nil {
        fmt.Println("Gagal:", err)
        return
    }
    fmt.Println("Hasil:", hasil)
}

bagi(10, 2) berhasil sehingga err bernilai nil dan blok pengecekan dilewati. Panggil dengan pembagi nol dan jalur gagal aktif: pesan errors.New tercetak lewat fmt.Println. Perhatikan return setelahnya — sekadar mencetak tidak cukup; eksekusi selanjutnya harus dihentikan agar hasil yang tidak sahih tidak ikut terpakai.

Membungkus error dengan konteks

var errStokHabis = errors.New("stok habis")

func pesanProduk(sisa int) error {
    if sisa == 0 {
        return fmt.Errorf("pesan %q: %w", "kaos", errStokHabis)
    }
    return nil
}

func main() {
    err := pesanProduk(0)
    if errors.Is(err, errStokHabis) {
        fmt.Println("akar masalah:", errStokHabis)
    }
}

fmt.Errorf dengan %w membungkus error asli di balik konteks baru tanpa memusnahkannya — errors.Is masih bisa mencocokkan akarnya. Hasil cetakan err tetap informatif ("pesan \"kaos\": stok habis") sementara program bisa bereaksi terhadap penyebab spesifik.

Menangkap panic dengan recover

func aman(f func()) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("terjadi panic: %v", r)
        }
    }()
    f()
    return nil
}

func main() {
    err := aman(func() {
        panic("indeks nyasar")
    })
    fmt.Println(err) // terjadi panic: indeks nyasar
}

recover hanya sah di dalam fungsi yang di-defer. Saat panic merambat, defer di aman berjalan, recover menangkap nilainya, dan alur kembali normal — panic diubah menjadi error yang bisa dikembalikan. Program tetap hidup; tanpa ini ia mati dengan pesan goroutine panic.

Latihan singkat

Uji: panggil bagi(7, 0) dan tangani errornya — program harus mencetak persis `Gagal: pembagi tidak boleh nol`.