Sejauh ini seluruh kegagalan kita lewat nilai balikan error. Go memiliki jalur kedua bernama panic: alur program berhenti seketika, fungsi-fungsi yang tertunda dijalankan, dan tanpa penangkap, program mati dengan pesan serta stack trace. Jalur ini bukan pengganti error, melainkan untuk keadaan tak terpulihkan — bug dan invariant yang dilanggar. Pada chapter ini kita akan membatasi penggunaan panic, lalu memakai recover di dalam fungsi defer sebagai jaring pengaman yang menjaga program tetap hidup.

Kapan panic Wajar #

Panduan praktisnya ringkas: panic untuk kesalahan programmer, error untuk kegagalan operasi. Kesalahan programmer berarti kode pemanggil sudah salah — misalnya memanggil fungsi dengan state nil yang seharusnya diinisialisasi lebih dulu. Beberapa situasi yang lazim:

  • Invariant internal dilanggar: nilai nil muncul di tempat yang mustahil, indeks di luar jangkauan saat memakai slice internal.
  • Konfigurasi wajib hilang saat program start: kegagalan init membuat program tidak berfungsi sama sekali.
  • Bug pemetaan yang tidak mungkin dipulihkan di runtime, misalnya bug logika mapping.

Untuk kegagalan yang pemanggil bisa atasi — berkas tidak ada, jaringan putus, masukan tidak valid — jalurnya tetap return err. Library yang memakai panic untuk kondisi yang bisa dikembalikan sebagai error memaksa seluruh pemanggilnya memasang jaring pengaman; itu desain API yang buruk.

recover Hanya Efektif dalam Fungsi Deferred #

Fungsi builtin recover() menghentikan proses unwinding dan mengembalikan nilai yang dilempar panic. Satu syarat menentukan: pemanggilan recover harus berada di dalam fungsi yang ditunda dengan defer. Panggilan recover() di badan fungsi biasa selalu mengembalikan nil dan tidak menghentikan apa pun — panic tetap berjalan sampai program mati.

Nilai panic boleh bertipe apa pun: string seperti panic("pesan"), error, atau nilai runtime seperti runtime.Error yang dihasilkan pembagian nol. Recover mendapat nilai itu apa adanya.

Program Utuh #

Berkas panic-recover-demo.go memasang dua jaring pengaman: satu untuk invariant nil, satu untuk panic runtime pembagian nol:

go
package main

import "fmt"

// inisialisasiState memvalidasi invariant sebelum dipakai.
func inisialisasiState(state map[string]int) {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("panic ditangkap:", r)
		}
	}()
	if state == nil {
		panic("state belum diinisialisasi")
	}
	fmt.Println("state siap, jumlah kunci:", len(state))
}

// bagi mengembalikan hasil pembagian, atau menangkap panic pembagi nol.
func bagi(pembilang, pembagi int) (hasil int, tertangkap bool) {
	defer func() {
		if recover() != nil {
			tertangkap = true
		}
	}()
	return pembilang / pembagi, false
}

func main() {
	inisialisasiState(map[string]int{"stok": 12})
	inisialisasiState(nil)

	hasil, tertangkap := bagi(100, 4)
	fmt.Println("hasil:", hasil, "tertangkap:", tertangkap)

	hasil, tertangkap = bagi(100, 0)
	fmt.Println("hasil:", hasil, "tertangkap:", tertangkap)

	fmt.Println("program tetap berjalan sampai baris ini")
}

Jalankan program dari direktori chapter dengan perintah berikut:

bash
go run panic-recover-demo.go

Output yang Dihasilkan:

text
state siap, jumlah kunci: 1
panic ditangkap: state belum diinisialisasi
hasil: 25 tertangkap: false
hasil: 0 tertangkap: true
program tetap berjalan sampai baris ini

Mari kita telaah baris demi baris program di atas:

  1. Baris 7-11 (defer func() { if r := recover() ... }): fungsi anonim ini ditunda; ketika panic melewati inisialisasiState, fungsi deferred berjalan dan recover menghentikan unwinding.
  2. Baris 12-14 (if state == nil ... panic(...)): pelanggaran invariant memicu panic dengan nilai string; string tersebut kembali sebagai nilai balikan recover.
  3. Baris 19 (func bagi(...) (hasil int, tertangkap bool)): named return value memungkinkan fungsi deferred mengubah hasil akhir walaupun jalur normalnya terputus panic.
  4. Baris 20-24 (defer func() { if recover() != nil ... }): pembagian nol memicu panic bertipe runtime.Error; fungsi deferred menandai tertangkap = true dan hasil tetap zero value.
  5. Baris 25 (return pembilang / pembagi, false): evaluasi 100 / 0 memicu panic sebelum return selesai — di sinilah jaring pengaman bekerja.
  6. Baris 29-38 (func main): pemanggilan berbahaya tidak menghentikan program; baris terakhir tetap tercetak dan program berakhir dengan exit code 0 karena semua panic tertangkap.

Pola di Dunia Nyata #

Dalam server HTTP, satu handler yang panic tidak boleh menjatuhkan seluruh proses. Package net/http sudah memasang pemulih bawaan per koneksi — panic handler dicatat ke log dan koneksi ditutup, server tetap hidup. Praktik umum menambahkan recovery middleware di lapisan middleware agar panic tiap handler dikonversi menjadi respons 500 yang rapi; pola lengkapnya dibahas pada chapter Middleware .

Pemahaman panic dan recover melengkapi kotak peralatan kita. Berikutnya, kita mengangkat pandangan ke arsitektur: bagaimana menjaga error dari lapisan dalam tidak membocorkan detail ke lapisan publik — materi chapter Error Boundary .

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

panic-recover-demo.go

Unduh berkas
go
package main

import "fmt"

// inisialisasiState memvalidasi invariant sebelum dipakai.
func inisialisasiState(state map[string]int) {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("panic ditangkap:", r)
		}
	}()
	if state == nil {
		panic("state belum diinisialisasi")
	}
	fmt.Println("state siap, jumlah kunci:", len(state))
}

// bagi mengembalikan hasil pembagian, atau menangkap panic pembagi nol.
func bagi(pembilang, pembagi int) (hasil int, tertangkap bool) {
	defer func() {
		if recover() != nil {
			tertangkap = true
		}
	}()
	return pembilang / pembagi, false
}

func main() {
	inisialisasiState(map[string]int{"stok": 12})
	inisialisasiState(nil)

	hasil, tertangkap := bagi(100, 4)
	fmt.Println("hasil:", hasil, "tertangkap:", tertangkap)

	hasil, tertangkap = bagi(100, 0)
	fmt.Println("hasil:", hasil, "tertangkap:", tertangkap)

	fmt.Println("program tetap berjalan sampai baris ini")
}