Seperti apa panic itu
Panic menghentikan fungsi saat ini, menjalankan pemanggilan yang di-defer di fungsi itu, lalu melakukan hal yang sama di pemanggilnya, dan seterusnya naik ke atas stack. Kalau panic mencapai puncak goroutine, program akan crash.
Program ini keluar dengan status 2. Output-nya:
before
deferred in main: still runs
panic: runtime error: index out of range [5] with length 3
goroutine 1 [running]:
main.main()
/tmp/main.go:11 +0x...
exit status 2
Pemanggilan yang di-defer berjalan sebelum laporan crash. Trace-nya menyebutkan goroutine, fungsi, dan barisnya, yang biasanya sudah cukup untuk menemukan bug-nya.
Runtime panic yang umum
| Pesan | Penyebab |
|---|---|
index out of range [5] with length 3 | indeks slice, array, atau string melewati ujungnya |
slice bounds out of range [:7] with capacity 5 | slicing melewati kapasitas |
invalid memory address or nil pointer dereference | membaca field atau memanggil method lewat pointer nil |
assignment to entry in nil map | menulis ke map yang belum pernah dibuat |
interface conversion: interface {} is int, not string | type assertion bentuk satu nilai ke tipe yang salah |
integer divide by zero | pembagian atau modulo integer dengan 0 (float justru menghasilkan +Inf atau NaN) |
close of closed channel, send on closed channel | pemakaian channel yang salah |
all goroutines are asleep - deadlock! | semua goroutine terblokir (fatal error, bukan panic) |
Masing-masing adalah bug di program, bukan kondisi yang perlu ditangani. Perbaikannya adalah pemeriksaan batas, pemeriksaan nil, make, atau assertion comma-ok, bukan recover.
Melakukan recover
recover() menghentikan panic. Fungsi ini hanya bekerja kalau dipanggil langsung di dalam fungsi yang di-defer, karena fungsi yang di-defer adalah satu-satunya kode yang berjalan saat panic membongkar stack.
Output:
5 <nil>
0 recovered: runtime error: integer divide by zero
program continues
Yang terjadi di pemanggilan kedua:
a / bmemicu panic.- Closure yang di-defer berjalan, dan
recover()mengembalikan nilai panic-nya (sebuahruntime.Error). - Pembongkaran stack berhenti.
safeDividekembali normal kemain, dengan named resulterryang diisi oleh closure.
Named result itulah yang memungkinkan fungsi yang di-defer mengembalikan error. Tanpa named result, fungsi akan mengembalikan zero value-nya. Halaman defer membahas cara closure yang di-defer mengubah hasil.
recover() mengembalikan nil saat tidak ada panic, jadi pemeriksaan if r != nil membuat fungsi yang di-defer tidak berbahaya di jalur normal. Kalau dipanggil di luar fungsi yang di-defer, atau di fungsi yang dipanggil oleh fungsi yang di-defer, recover mengembalikan nil dan tidak melakukan apa pun.
panic dengan nilaimu sendiri
panic menerima nilai apa pun. Biasanya berupa error atau string.
Lakukan recover untuk apa yang kamu harapkan, dan panic ulang untuk yang lainnya. Menelan setiap panic akan menyembunyikan bug yang sebenarnya.
Sejak Go 1.21, panic(nil) diubah menjadi *runtime.PanicNilError, jadi recover() yang mengembalikan nil kini selalu berarti "tidak ada panic".
Panic di goroutine
recover hanya menangkap panic di goroutine-nya sendiri. Panic di goroutine mana pun tanpa recover akan mematikan seluruh proses, termasuk main dan setiap goroutine lainnya.
Kedua baris worker bisa tercetak dalam urutan mana pun; main finished selalu muncul terakhir. defer recover() di main tidak akan menyelamatkan program dari worker kedua. Inilah sebabnya HTTP server melakukan recover per request: net/http melakukan recover terhadap panic di setiap goroutine handler, mencatatnya di log, lalu menutup koneksi itu, sehingga satu request yang buruk tidak menjatuhkan server.
Beberapa kegagalan adalah fatal error, bukan panic, dan sama sekali tidak bisa di-recover: concurrent map writes, kehabisan memori, dan all goroutines are asleep dari pendeteksi deadlock.
Kapan panic adalah pilihan yang tepat
Aturan umum Go: kembalikan error untuk apa pun yang bisa salah saat program berjalan, dan pakai panic hanya untuk kesalahan programmer. Secara konkret, panic tepat dipakai saat:
- Sebuah invariant rusak.
switchpada enum buatanmu sampai ke kasus yang tidak mungkin terjadi. Melanjutkan program justru akan merusak data. - Helper
Mustmenerima input konstanta yang salah.regexp.MustCompile,template.Must, danuuid.MustParsemembungkus fungsi yang mengembalikan error lalu memicu panic saat gagal. Pakai untuk nilai yang sudah diketahui saat kompilasi, biasanya variabel level package, di mana kegagalan berarti source code-nya yang salah:
var emailRE = regexp.MustCompile(`^[^@\s]+@[^@\s]+$`)
- Startup tidak bisa dilanjutkan. Konfigurasi wajib tidak ada di
main. Bahkan di sini, mencetak error lalu memanggilos.Exit(1)sering lebih rapi daripada stack trace.
Panic adalah alat yang salah untuk:
- Kegagalan yang bisa diperkirakan: input pengguna yang tidak valid, file yang tidak ada, timeout. Kembalikan
error; lihat penanganan error. - Alur kontrol: memakai panic dan recover seperti exception di sepanjang call tree yang besar membuat kode sulit diikuti. Standard library melakukannya secara internal di beberapa tempat (encoder
encoding/json), selalu dengan recover sebelum kembali, sehingga tidak ada panic yang lolos dari package-nya. - API library: library yang memicu panic saat menerima input yang salah memaksa setiap pemanggil menambahkan recover. Kembalikan error.
Kesalahan umum
- Memanggil recover di luar fungsi yang di-defer. Hasilnya
nil. - Melakukan recover di
mainuntuk panic dari goroutine. Setiap goroutine butuh recover-nya sendiri. - Menelan semua panic. Catat di log beserta stack-nya (
debug.Stack()dariruntime/debug) dan panic ulang untuk yang tidak kamu harapkan. - Memakai
recoveruntuk menangani penulisan ke map nil atau indeks di luar batas. Perbaiki bug-nya saja.
Pertanyaan yang sering diajukan
Apa itu panic di Go?
Kegagalan saat program berjalan yang menghentikan alur normal goroutine saat ini. Go menjalankan pemanggilan yang di-defer dari setiap fungsi di stack, dari yang paling dalam ke luar, dan kalau tidak ada yang melakukan recover, program mencetak nilai panic beserta stack trace lalu keluar dengan status 2. Panic berasal dari bug (index out of range, nil pointer dereference, menulis ke map nil) atau dari pemanggilan panic(v) secara eksplisit.
Bagaimana cara recover dari panic di Go?
Panggil recover() di dalam fungsi yang di-defer: defer func() { if r := recover(); r != nil { ... } }(). Fungsi ini mengembalikan nilai yang diberikan ke panic dan menghentikan pembongkaran stack, sehingga fungsi yang melakukan defer kembali normal ke pemanggilnya. Kalau dipanggil di tempat lain, recover mengembalikan nil dan tidak melakukan apa pun.
Bisakah saya melakukan recover terhadap panic dari goroutine lain?
Tidak. recover hanya menghentikan panic di goroutine tempat ia berjalan. Panic di goroutine yang kamu jalankan, tanpa recover di dalam goroutine itu, akan membuat seluruh program crash. Setiap goroutine yang mungkin panic butuh recover yang di-defer miliknya sendiri.
Kapan sebaiknya memakai panic alih-alih mengembalikan error?
Untuk bug dan kondisi yang mustahil, bukan untuk kegagalan yang bisa diperkirakan. Input yang salah, file yang tidak ada, dan error jaringan adalah error. Panic masuk akal saat sebuah invariant rusak, saat helper Must diberi konstanta yang seharusnya selalu valid (regexp.MustCompile), atau saat program sama sekali tidak bisa dimulai. Library tidak boleh membiarkan panic lolos dari API publiknya.