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)memintamenu[5]dari slice yang isinya cuma 3, dan program-nya panic. Apakah barismeja 4 tutup bondaridefermasih 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
defer fmt.Println(...)gak mencetak apa-apa di baris itu. Panggilannya disimpan, lalu dijalankan tepat sebelumlayanikembali kemain.- Itu sebabnya
tutup bontercetak sesudahpesan mie ayam, walau ditulis di atasnya. - Defer jalan di semua jalan keluar:
returnbiasa,returndi tengah fungsi, atau panic.
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
- Begitu panic, sisa fungsi gak dijalankan, tapi defer yang sudah dipasang tetap jalan. Jadi
meja 4 tutup bonmasih tercetak. Itu jawaban tebakannya. - Sesudah semua defer jalan, Go mencetak
panic:dan pesannya, lalu jejak panggilan (stack trace): fungsi mana di baris berapa. Angka setelah+0xbisa beda di komputermu. - Program berhenti dengan exit status 2, dan
warung tetap bukagak pernah tercetak. - Kamu juga bisa memanggil sendiri:
panic("kompor meledak")mencetakpanic: kompor meledak.
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
recover()mengembalikan nilai panic-nya dan menghentikan panic itu.layaniberhenti di baris yang panic, tapi kembali kemainseperti biasa.- Closure recover dipasang terakhir, jadi jalan pertama, sebelum
tutup bon. recover()cuma bekerja kalau dipanggil langsung oleh fungsi yang di-defer. Dipanggil di tempat lain, atau saat gak ada panic, hasilnyanil. Panic di satu goroutine juga gak bisa ditangkap dari goroutine lain.
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
- Mengira defer langsung jalan. Baris defer baru jalan saat fungsinya selesai, dengan urutan terbalik.
- Mengira argumen defer dibaca belakangan.
defer fmt.Println(porsi)menyimpan nilaiporsisaat baris itu lewat. - Memanggil
recover()di luar defer. Hasilnyanil, dan panic-nya tetap mematikan program. - Defer di dalam loop yang panjang. Semuanya baru jalan saat fungsi selesai, bukan di akhir tiap putaran. Pindahkan isi loop ke fungsi sendiri kalau tiap putaran perlu ditutup.
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.