โ† Semua pembelajaran / Go Nol โ†’ Enterprise
Fase 1 ยท Tipe, Interface & Error

panic, recover & defer

Panic dimaksudkan untuk kesalahan program yang tidak bisa dilanjutkan, bukan untuk kegagalan yang wajar. Tapi di server, satu panic di satu permintaan tidak boleh mematikan seluruh proses.

Sumber asli go.dev Resmi Rangkuman ~6 menit baca

Intisari

  • panic untuk bug, error untuk kegagalan yang diperkirakan. Input pengguna yang salah bukan alasan panic.
  • recover hanya bekerja di dalam defer, di fungsi yang sedang panik.
  • Panic di goroutine mana pun mematikan seluruh proses โ€” recover tidak melintasi goroutine.
  • net/http sudah memulihkan panic per permintaan, tapi tulis middleware sendiri supaya bisa dicatat dan dihitung.
  • Panic yang wajar di produksi: nil map, indeks di luar batas, type assertion tanpa ok, pembagian nol integer.

Tiga kata kunci, satu mekanisme

func demo() {
	defer fmt.Println("1. defer selalu jalan")

	defer func() {
		if r := recover(); r != nil {
			fmt.Println("2. dipulihkan:", r)
		}
	}()

	panic("sesuatu rusak")
	fmt.Println("tidak pernah tercetak")
}

Saat panic dipanggil, fungsi berhenti, semua defer-nya dijalankan, lalu prosesnya naik ke pemanggil dan mengulangi hal yang sama. Kalau tidak ada yang memanggil recover di sepanjang jalan, program mati dengan mencetak stack trace.

Kapan panic sah

SituasiPakai
Input pengguna tidak validerror
Baris database tidak adaerror
API luar mengembalikan 500error
Konfigurasi wajib kosong saat startuppanic / log.Fatal โ€” lebih baik mati sekarang daripada salah melayani
Template atau regex konstan gagal dikompilasipanic (MustCompile) โ€” itu bug programmer
Kondisi yang secara logika mustahilpanic โ€” dengan pesan yang menjelaskan invarian mana yang dilanggar
// Konvensi "Must": panic saat inisialisasi, karena kalau ini gagal,
// program memang tidak layak berjalan sama sekali.
var polaEmail = regexp.MustCompile(`^[^@\s]+@[^@\s]+$`)
var tmpl = template.Must(template.ParseFS(templateFS, "web/*.html"))

Batas yang jelas: panic sah sebelum server mulai melayani permintaan. Setelah itu, kegagalan apa pun yang bisa dipicu dari luar harus jadi error โ€” kalau tidak, siapa pun bisa mematikan aplikasimu dengan mengirim permintaan yang tepat.

Middleware pemulih untuk server HTTP

func Pulihkan(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			rec := recover()
			if rec == nil {
				return
			}

			// Klien memutus koneksi di tengah tulis: bukan bug kita.
			if rec == http.ErrAbortHandler {
				panic(rec)
			}

			slog.ErrorContext(r.Context(), "panic pada handler",
				"panic", rec,
				"path", r.URL.Path,
				"stack", string(debug.Stack()))

			w.Header().Set("Connection", "close")
			http.Error(w, "terjadi kesalahan", http.StatusInternalServerError)
		}()

		next.ServeHTTP(w, r)
	})
}

net/http memang sudah memulihkan panic sendiri supaya satu permintaan bermasalah tidak mematikan server. Tapi pemulihan bawaan itu hanya mencetak ke log standar dan menutup koneksi โ€” tanpa request_id, tanpa metrik, tanpa struktur. Middleware sendiri memberimu tiga hal yang dibutuhkan saat insiden: stack trace lengkap, konteks permintaan, dan angka yang bisa dijadikan alarm (Fase 9).

Panic di goroutine tidak bisa dipulihkan dari luar

// โŒ recover di sini TIDAK menangkap panic goroutine di bawahnya
func salah() {
	defer func() { recover() }()

	go func() {
		panic("boom")   // seluruh PROSES mati
	}()
}

// โœ… tiap goroutine mengurus pemulihannya sendiri
func amanJalankan(f func()) {
	go func() {
		defer func() {
			if r := recover(); r != nil {
				slog.Error("panic di goroutine", "panic", r,
					"stack", string(debug.Stack()))
			}
		}()
		f()
	}()
}

Ini penyebab paling umum container mati tanpa jejak. Handler HTTP-mu terlindung middleware, tapi goroutine latar (pemroses antrean, pembersih cache, pengirim metrik) tidak. Satu nil pointer di sana mematikan seluruh task, dan yang terlihat di ECS hanyalah container yang restart. Setiap go func() yang berumur panjang butuh pemulihnya sendiri.

Panic yang paling sering terjadi di produksi

PesanPenyebab khas
nil pointer dereferenceHasil fungsi dipakai tanpa memeriksa error lebih dulu
index out of rangebagian[1] setelah strings.Split tanpa memeriksa panjangnya
assignment to entry in nil mapField map di struct tidak diisi konstruktor
interface conversionType assertion tanpa bentuk , ok
concurrent map writesMap dipakai dari beberapa goroutine tanpa penguncian (Fase 2)
send on closed channelChannel ditutup oleh sisi penerima, bukan pengirim (Fase 2)

Sejak Go 1.25, pesan panic nil pointer jauh lebih informatif โ€” ia menyebut ekspresi mana yang bernilai nil, bukan sekadar nomor baris. Kalau kamu masih melihat trace yang tidak menjelaskan apa pun, periksa versi Go di image produksimu.

Latihan: tulis server HTTP kecil dengan satu handler yang sengaja memanggil panic("uji"). Panggil endpoint itu, lalu panggil endpoint lain โ€” buktikan servernya masih hidup. Sekarang pindahkan panic-nya ke dalam go func(){}() di handler tersebut, panggil sekali lagi, dan lihat seluruh prosesmu mati. Itu perbedaan yang harus kamu ingat sebelum menulis worker di Fase 5.

Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.