Panic menghentikan alur normal fungsi yang sedang berjalan, sedangkan recover mengembalikan kendali sebelum panic mengakhiri program. Topik ini membahas perilaku program ketika panic terjadi, cara menangkapnya dengan recover, kondisi yang pantas memicu panic, dan alasan panic bukan alur kontrol untuk kegagalan yang lazim. Setelah membacanya, batas antara error sebagai nilai dan panic dapat ditarik dengan tegas.
Apa yang terjadi saat panic#
Panggilan panic menghentikan fungsi yang sedang berjalan, lalu menyebar keluar sambil menjalankan seluruh pemanggilan yang ditunda dengan defer di sepanjang jalurnya. Apabila tidak ada yang menahannya, panic mencetak pesan dan jejaknya (stack trace, yaitu daftar panggilan fungsi yang sedang aktif), kemudian mengakhiri program dengan status keluar bukan nol. Program berikut sengaja melakukan panic tanpa penangkap.
// tidak-dijalankan di halaman ini, programnya dieksekusi oleh blok bash
// dengan penyaring keluaran supaya alamat memori tidak ditampilkan mentah.
package main
import "fmt"
func main() {
fmt.Println("sebelum panic")
panic("pengaturan wajib tidak ada")
}Keluaran mentahnya memuat alamat memori yang berbeda pada setiap mesin dan setiap versi program, sehingga jalannya program dicoba lewat terminal dengan penyaring teks. Alamat memori diganti 0xALAMAT, jalur berkas diganti BERKAS.go, dan spasi di awal baris jejak dibuang.
# siapkan folder eksperimen di /tmp
rm -rf /tmp/gopanic && mkdir -p /tmp/gopanic
# salin program dari bundle ke folder eksperimen
cp tanpa-recover.go /tmp/gopanic/main.go
# jalankan programnya, lalu samarkan alamat memori, jalur berkas, dan spasi awal baris
go run /tmp/gopanic/main.go 2>&1 | sed -E 's/0x[0-9a-f]+/0xALAMAT/g' | sed -E 's#/tmp/gopanic/[a-z_.]+#BERKAS.go#g' | sed -E 's/^[[:space:]]+//' | head -8sebelum panic
panic: pengaturan wajib tidak ada
goroutine 1 [running]:
main.main()
BERKAS.go:10 +0xALAMAT
exit status 2Baris panic: menampilkan nilai yang dikirim ke panic, dan baris goroutine 1 [running]: membuka jejak panggilan tempat panic berasal. Baris main.main() menyebut fungsi pelakunya, sedangkan BERKAS.go:10 dan 0xALAMAT adalah hasil penyamaran atas nomor baris serta alamat memori yang memang berbeda antarmesin. Baris exit status 2 berasal dari go run dan menegaskan bahwa program berakhir tidak normal.
Menangkap panic dengan recover#
Fungsi recover menghentikan penyebaran panic dan mengembalikan nilai yang dikirim kepadanya. Fungsi ini hanya berdaya guna di dalam fungsi yang ditunda dengan defer, sebab hanya di sana penyebaran panic masih dapat dibaca. Pola yang lazim adalah mengubah panic menjadi error biasa lewat hasil bernama.
package main
import (
"errors"
"fmt"
)
var errBerat = errors.New("berat harus positif")
// konversi mengubah berat dalam kilogram menjadi teks.
// Panic yang terjadi di dalam fungsi diubah menjadi error biasa.
func konversi(beratKg int) (hasil string, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("konversi gagal: %v", r)
}
}()
if beratKg <= 0 {
panic(errBerat)
}
return fmt.Sprintf("%d kg", beratKg), nil
}
func main() {
hasil, err := konversi(24)
fmt.Printf("24: hasil=%q err=%v\n", hasil, err)
hasil, err = konversi(0)
fmt.Printf("0: hasil=%q err=%v\n", hasil, err)
fmt.Println("program berlanjut normal")
}24: hasil="24 kg" err=<nil>
0: hasil="" err=konversi gagal: berat harus positif
program berlanjut normalPemanggilan konversi(0) memicu panic, tetapi fungsi tertunda menjalankan recover sebelum panic keluar dari konversi. Hasil bernama err diisi error biasa, sehingga pemanggil menerima kegagalan dalam bentuk yang lazim dan program berlanjut normal. Apabila recover dipanggil di luar situasi panic, ia mengembalikan nil tanpa dampak apa pun.
Nilai kembalian recover bertipe any, yaitu alias untuk interface{}, karena nilai panic boleh berupa apa pun. Konvensi yang lazim mengirimkan nilai error ke panic, sebagaimana panic(errBerat) pada program di atas, sehingga hasil recover dapat diperlakukan sebagai error tanpa konversi tambahan. Apabila panic terjadi sebelum pernyataan return sempat mengisi hasil bernama, hasil bernama itu berisi nilai nol tipenya. Mengisi err di dalam fungsi tertunda adalah cara mengembalikan hasil yang bermakna kepada pemanggil.
Panic yang pantas dipakai#
Panic pantas dipicu untuk keadaan yang menandakan bug program, yaitu pelanggaran asumsi yang tidak seharusnya terjadi jika kode ditulis dengan benar. Pustaka standar menyediakan keluarga fungsi Must untuk kebutuhan ini, misalnya regexp.MustCompile yang memeriksa pola saat program dimulai.
package main
import (
"fmt"
"regexp"
)
// polaKata dipastikan sah saat program dimulai.
// Jika polanya tidak sah, MustCompile melakukan panic.
var polaKata = regexp.MustCompile(`^[a-z]+$`)
func main() {
fmt.Println(polaKata.MatchString("catatan"))
fmt.Println(polaKata.MatchString("Catatan 42"))
}true
falsePola ^[a-z]+$ sah, sehingga program berjalan dan mengenali kata yang hanya terdiri atas huruf kecil. Bila polanya salah tulis, MustCompile melakukan panic sebelum main sempat berjalan, dan masalah itu terlihat seketika pada kali pertama program dijalankan. Kegagalan semacam ini adalah kesalahan pemrogram yang tidak ada gunanya ditangani secara rutin, berbeda dengan masukan pengguna yang salah dan wajib dilaporkan sebagai error.
Mengapa panic bukan alur kontrol#
Kegagalan yang dapat diantisipasi, seperti teks pengaturan yang tidak dapat diuraikan atau berkas yang tidak ada, sebaiknya dilaporkan sebagai nilai error. Alasannya, error terlihat pada tipe kembalian fungsi dan wajib diperiksa secara eksplisit, sedangkan panic tidak tampak dari tanda tangannya dan melewati seluruh pemanggil sampai ada yang menangkapnya. Pemakaian panic sebagai alur kontrol menyembunyikan jalur program dari pembaca kodenya.
Konvensi ini juga menjaga batas antarpaket. Pustaka yang panic untuk kondisi yang wajar memaksa seluruh pemakainya memasang recover secara defensif, padahal pemanggil tidak dapat mengetahui jalur mana yang berpotensi panic hanya dengan membaca tanda tangan fungsinya. Pada batas layanan seperti handler server, recover memang dipasang agar satu permintaan yang panic tidak menghentikan seluruh layanan, tetapi itu penangkal terakhir, bukan mekanisme laporan kegagalan.
Batas lain berkaitan dengan goroutine, yaitu unit eksekusi ringan yang dijalankan Go secara independen. Panic yang tidak tertangkap pada satu goroutine mengakhiri seluruh program, dan recover hanya berdaya guna pada goroutine yang sama dengan tempat panic terjadi. Karena itu, kode yang dijalankan pada goroutine lain perlu memasang recover pada goroutine itu sendiri apabila kelangsungan program harus dijaga.
Ringkasan#
panicmenghentikan fungsi yang berjalan, menjalankan seluruhdeferdi sepanjang jalurnya, lalu mengakhiri program bila tidak ditangkap.recoverhanya berdaya guna di dalam fungsi yang ditunda dengandeferpada goroutine yang sama dan mengembalikan nilai panic-nya.- Pola hasil bernama ditambah fungsi tertunda mengubah panic menjadi error biasa bagi pemanggil.
- Panic pantas untuk pelanggaran asumsi yang menandakan bug program, sedangkan kegagalan yang dapat diantisipasi tetap dilaporkan sebagai error.
Referensi#
- Defer, Panic, and Recover — The Go Blog — Mekanisme panic dan recover beserta contoh idiomnya.
- The Go Programming Language Specification — The Go Authors — Semantik eksekusi panic dan recover menurut spesifikasi bahasa.
- Package builtin — The Go Authors
— Dokumentasi fungsi
panicdanrecover.