Defer, Panic, Recover — Go

Defer, Panic, Recover Tiga mekanisme Go untuk cleanup dan error luar biasa. Kombinasi ketiganya menggantikan peran try/finally/catch di bahasa lain — tapi…

defer menjadwalkan sebuah panggilan fungsi supaya baru jalan saat fungsi yang memuatnya selesai. panic menghentikan program secara darurat, dan recover menangkap panic itu dari dalam fungsi yang di-defer. Bayangin Mas Bimo membuka bon untuk meja 4. Begitu bon dibuka, ia langsung menempel catatan "tutup bon" di papan. Apa pun yang terjadi sesudahnya, pesanan beres atau kompor tiba-tiba mati, catatan di papan itu dikerjakan sebelum ia meninggalkan meja. Catatan yang ditempel paling akhir ada di paling atas, jadi dikerjakan paling dulu.

🧪 Tebakan cepat: layani(4, 5) meminta menu[5] dari slice yang isinya cuma 3, dan program-nya panic. Apakah baris meja 4 tutup bon dari defer masih sempat tercetak? Tebak dulu, jawabannya ada di bagian "Panic: berhenti darurat".

defer: jalan saat fungsi selesai

package main

import "fmt"

var menu = []string{"bakso urat", "mie ayam", "es teh"}

func layani(meja, nomor int) {
	fmt.Println("meja", meja, "buka bon")
	defer fmt.Println("meja", meja, "tutup bon")
	fmt.Println("meja", meja, "pesan", menu[nomor])
}

func main() {
	layani(4, 1)
	fmt.Println("warung tetap buka")
}
$ go run bakso.go
meja 4 buka bon
meja 4 pesan mie ayam
meja 4 tutup bon
warung tetap buka

Urutan LIFO, dan argumen yang dihitung saat itu juga

Kalau ada beberapa defer, yang dipasang terakhir jalan pertama. Urutan ini disebut LIFO (last in, first out), seperti tumpukan piring:

func main() {
	defer fmt.Println("A")
	defer fmt.Println("B")
	defer fmt.Println("C")
	fmt.Println("start")
}

Output-nya start, lalu C, B, A, masing-masing di barisnya sendiri.

Argumen defer dihitung saat baris defer dijalankan, bukan saat fungsinya selesai:

porsi := 1
defer fmt.Println("defer melihat porsi:", porsi)
porsi = 3
fmt.Println("porsi sekarang:", porsi)

Ini mencetak porsi sekarang: 3 lalu defer melihat porsi: 1. Nilai 1 sudah disimpan waktu baris defer lewat. Kalau yang di-defer adalah closure, defer func() { fmt.Println(porsi) }(), closure itu membaca variabelnya saat jalan, jadi melihat 3.

Pola cleanup: buka, lalu langsung defer tutup

Pasangan buka dan tutup ditulis berdampingan, jadi tutupnya gak mungkin lupa: f, err := os.Open("menu.txt") lalu defer f.Close(), atau mu.Lock() lalu defer mu.Unlock(). Latihan lesson ini memakai pola yang sama dengan dua sumber daya. openResource mencetak buka: <nama>, closeResource mencetak tutup: <nama>:

a := openResource("file-A")
defer closeResource(a)

b := openResource("file-B")
defer closeResource(b)

fmt.Println("kerja...")
buka: file-A
buka: file-B
kerja...
tutup: file-B
tutup: file-A

file-B dibuka terakhir, jadi ditutup pertama. Ini urutan yang benar untuk sumber daya yang saling bergantung.

Panic: berhenti darurat

menu[5] dari slice berisi 3 gak bisa dipenuhi, jadi Go panic:

$ go run bakso.go
meja 4 buka bon
meja 4 tutup bon
panic: runtime error: index out of range [5] with length 3

goroutine 1 [running]:
main.layani(0x4, 0x5)
	/home/bimo/bakso/bakso.go:10 +0x219
main.main()
	/home/bimo/bakso/bakso.go:14 +0x1d
exit status 2

Recover: menangkap panic di dalam defer

func layani(meja, nomor int) {
	fmt.Println("meja", meja, "buka bon")
	defer fmt.Println("meja", meja, "tutup bon")
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("meja", meja, "gagal:", r)
		}
	}()
	fmt.Println("meja", meja, "pesan", menu[nomor])
}
$ go run bakso.go
meja 4 buka bon
meja 4 gagal: runtime error: index out of range [5] with length 3
meja 4 tutup bon
warung tetap buka

Kapan panic, kapan return error

Kejadian Pakai
Input pembeli salah, stok habis, file gak ketemu, jaringan timeout return error
Bug programmer: nilai yang menurut logika mustahil panic
Gagal saat program mulai, sampai program gak bisa jalan sama sekali panic

Hampir semua kegagalan di Go adalah nilai error biasa. recover bukan pengganti try/catch: pakai di batas tertentu, misalnya supaya satu pesanan yang gagal gak mematikan seluruh kasir.

⚠️ Jebakan yang sering ketemu

TL;DR: defer f() menjalankan f saat fungsi yang memuatnya selesai, lewat return maupun panic. Banyak defer jalan LIFO: yang dipasang terakhir jalan pertama, jadi defer A, B, C lalu start mencetak start C B A. Argumen defer dihitung saat baris defer lewat. Pola cleanup: buka, lalu langsung defer tutup. panic menghentikan fungsi, menjalankan semua defer, mencetak panic: ... dengan stack trace, lalu keluar dengan exit status 2. recover() di dalam fungsi yang di-defer menangkap panic dan membuat program lanjut; di tempat lain hasilnya nil. Pakai error untuk kegagalan biasa, panic untuk bug dan kegagalan saat start.

Yang akan kamu pelajari