Bir panic neye benzer
Bir panic mevcut fonksiyonu durdurur, ertelenmiş çağrılarını çalıştırır, sonra aynısını çağıranında yapar ve stack boyunca yukarı böyle devam eder. Goroutine'in en üstüne ulaşırsa program çöker.
Bu program 2 durumuyla çıkar. Çıktı:
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
Ertelenmiş çağrı çökme raporundan önce çalıştı. Trace goroutine'i, fonksiyonu ve satırı adlandırır; bu genellikle hatayı bulmak için yeterlidir.
Yaygın çalışma zamanı panic'leri
| Mesaj | Neden |
|---|---|
index out of range [5] with length 3 | sonu aşan bir slice, dizi ya da string indeksi |
slice bounds out of range [:7] with capacity 5 | kapasitenin ötesine dilimleme |
invalid memory address or nil pointer dereference | nil bir pointer üzerinden alan okumak ya da çağrı yapmak |
assignment to entry in nil map | hiç oluşturulmamış bir map'e yazmak |
interface conversion: interface {} is int, not string | yanlış tipe tek değerli type assertion |
integer divide by zero | 0'a tam sayı bölmesi ya da mod (float'lar bunun yerine +Inf ya da NaN verir) |
close of closed channel, send on closed channel | channel'ın yanlış kullanımı |
all goroutines are asleep - deadlock! | her goroutine bloklanmış (panic değil, ölümcül bir hata) |
Bunların her biri programdaki bir bug'dır, ele alınacak bir durum değil. Çözüm bir recover değil; bir sınır kontrolü, bir nil kontrolü, bir make ya da virgül-ok assertion'ıdır.
Recover etmek
recover() bir panic'i durdurur. Yalnızca doğrudan ertelenmiş bir fonksiyonun içinde çağrıldığında çalışır, çünkü bir panic geri sarılırken çalışan tek kod ertelenmiş fonksiyonlardır.
Çıktı:
5 <nil>
0 recovered: runtime error: integer divide by zero
program continues
İkinci çağrıda olanlar:
a / bpanic oldu.- Ertelenmiş closure çalıştı ve
recover()panic değerini (birruntime.Error) döndürdü. - Geri sarma durdu.
safeDivide, closure'ın ayarladığı adlı sonuçerrilemain'e normal biçimde döndü.
Ertelenmiş fonksiyonun bir hatayı geri vermesini sağlayan adlı sonuçtur. Adlı sonuç olmadan fonksiyon sıfır değerlerini döndürür. Ertelenmiş closure'ların sonuçları nasıl değiştirdiğini defer sayfası anlatıyor.
Panic olmadığında recover() nil döndürür, bu yüzden if r != nil kontrolü ertelenmiş fonksiyonu normal yolda zararsız yapar. Ertelenmiş bir fonksiyonun dışında ya da ertelenmiş fonksiyonun çağırdığı bir fonksiyonda çağrılırsa recover nil döndürür ve hiçbir şey yapmaz.
Kendi değerinizle panic
panic herhangi bir değer alır. Bir error ya da bir string tipiktir.
Beklediğinizi recover edin, geri kalan her şey için yeniden panic yapın. Her panic'i yutmak gerçek bug'ları gizler.
Go 1.21'den beri panic(nil) bir *runtime.PanicNilError'a dönüştürülür, bu yüzden recover()'ın nil döndürmesi artık güvenilir biçimde "panic yok" anlamına gelir.
Goroutine'lerde panic'ler
recover yalnızca kendi goroutine'indeki panic'leri yakalar. Recover'ı olmayan herhangi bir goroutine'deki panic, main ve diğer tüm goroutine'ler dahil bütün süreci öldürür.
İki worker satırı herhangi bir sırayla yazdırılabilir; main finished her zaman en son gelir. main içindeki bir defer recover() programı ikinci worker'dan kurtaramazdı. HTTP sunucularının istek başına recover etmesinin nedeni budur: net/http her handler goroutine'indeki panic'leri recover eder, loglar ve o bağlantıyı kapatır; böylece tek bir hatalı istek sunucuyu çökertmez.
Bazı başarısızlıklar panic değil ölümcül hatadır ve hiç recover edilemez: concurrent map writes, belleğin tükenmesi ve deadlock dedektörünün all goroutines are asleep mesajı.
Panic ne zaman doğru seçimdir
Go'nun genel kuralı: çalışma zamanında ters gidebilecek her şey için hata döndürün, yalnızca programcı hataları için panic yapın. Somut olarak panic şu durumlarda uygundur:
- Bir değişmez bozulduğunda. Kendi enum'unuz üzerindeki bir
switch, olamayacak bir case'e ulaşır. Devam etmek veriyi bozar. - Bir
Mustyardımcısı hatalı sabit girdi aldığında.regexp.MustCompile,template.Mustveuuid.MustParsehata döndüren bir fonksiyonu sarar ve başarısızlıkta panic yapar. Bunları derleme zamanında bilinen değerler için, genellikle başarısızlığın kaynak kodun yanlış olduğu anlamına geldiği paket seviyesi değişkenler için kullanın:
var emailRE = regexp.MustCompile(`^[^@\s]+@[^@\s]+$`)
- Başlangıç devam edemediğinde.
mainiçinde eksik zorunlu yapılandırma. Burada bile hatayı yazdırıpos.Exit(1)çağırmak çoğu zaman bir stack trace'ten daha temizdir.
Panic şunlar için yanlış araçtır:
- Beklenen başarısızlıklar: geçersiz kullanıcı girdisi, eksik bir dosya, bir timeout. Bir
errordöndürün; bkz. hata yönetimi. - Kontrol akışı: panic ve recover'ı büyük bir çağrı ağacında exception gibi kullanmak kodu takip edilmesi zor hale getirir. Standart kütüphane bunu kendi içinde birkaç yerde yapar (
encoding/jsonencoder'ı), ama dönmeden önce her zaman recover eder, böylece hiçbir panic paketten dışarı kaçmaz. - Kütüphane API'leri: hatalı girdide panic yapan bir kütüphane her çağıranı recover eklemeye zorlar. Bir hata döndürün.
Sık yapılan hatalar
- recover'ı ertelenmiş bir fonksiyonun dışında çağırmak.
nildöndürür. - Bir goroutine'in panic'i için
mainiçinde recover etmek. Her goroutine'in kendi recover'ı gerekir. - Tüm panic'leri yutmak. Stack ile birlikte loglayın (
runtime/debug'dakidebug.Stack()) ve beklemediğiniz şeyler için yeniden panic yapın. - Nil map yazmalarını ya da aralık dışı indeksleri ele almak için
recoverkullanmak. Bunun yerine bug'ı düzeltin.
Sıkça Sorulan Sorular
Go'da panic nedir?
Mevcut goroutine'in normal akışını durduran bir çalışma zamanı başarısızlığıdır. Go, stack'teki her fonksiyonun ertelenmiş çağrılarını en içten dışarıya doğru çalıştırır ve hiçbir şey recover etmezse program panic değerini ve bir stack trace'i yazdırıp 2 durumuyla çıkar. Panic'ler bug'lardan (index out of range, nil pointer dereference, nil map'e yazma) ya da açık bir panic(v) çağrısından gelir.
Go'da bir panic'ten nasıl kurtulunur?
recover()'ı ertelenmiş bir fonksiyonun içinde çağırın: defer func() { if r := recover(); r != nil { ... } }(). panic'e aktarılan değeri döndürür ve geri sarmayı durdurur, böylece onu erteleyen fonksiyon çağıranına normal biçimde döner. Başka herhangi bir yerde çağrılırsa recover nil döndürür ve hiçbir şey yapmaz.
Başka bir goroutine'deki panic recover edilebilir mi?
Hayır. recover yalnızca çalıştığı goroutine'deki bir panic'i durdurur. Başlattığınız ve içinde recover olmayan bir goroutine'deki panic tüm programı çökertir. Panic yapabilecek her goroutine'in kendi ertelenmiş recover'ına ihtiyacı vardır.
Hata döndürmek yerine ne zaman panic kullanmalıyım?
Beklenen başarısızlıklar için değil, bug'lar ve imkânsız durumlar için. Hatalı girdi, eksik dosyalar ve ağ hataları birer error'dur. Bir değişmez bozulduğunda, bir Must yardımcısına her zaman geçerli olması gereken bir sabit verildiğinde (regexp.MustCompile) ya da program hiç başlayamadığında panic makuldür. Kütüphaneler panic'lerin genel API'lerinden dışarı kaçmasına izin vermemelidir.