Menu

Go Panic ve Recover: Çalışma Zamanı Panic'leri ve Ne Zaman Panic

Bir panic normal çalışmayı durdurur ve ertelenmiş çağrıları çalıştırarak stack'i geri sarar. Panic'lere neyin yol açtığını, ertelenmiş bir fonksiyondaki recover'ın onu nasıl durdurduğunu, göreceğiniz çalışma zamanı hata mesajlarını ve panic'in ne zaman doğru seçim olduğunu öğrenin.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

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

MesajNeden
index out of range [5] with length 3sonu aşan bir slice, dizi ya da string indeksi
slice bounds out of range [:7] with capacity 5kapasitenin ötesine dilimleme
invalid memory address or nil pointer dereferencenil bir pointer üzerinden alan okumak ya da çağrı yapmak
assignment to entry in nil maphiç oluşturulmamış bir map'e yazmak
interface conversion: interface {} is int, not stringyanlış tipe tek değerli type assertion
integer divide by zero0'a tam sayı bölmesi ya da mod (float'lar bunun yerine +Inf ya da NaN verir)
close of closed channel, send on closed channelchannel'ı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:

  1. a / b panic oldu.
  2. Ertelenmiş closure çalıştı ve recover() panic değerini (bir runtime.Error) döndürdü.
  3. Geri sarma durdu. safeDivide, closure'ın ayarladığı adlı sonuç err ile main'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 Must yardımcısı hatalı sabit girdi aldığında. regexp.MustCompile, template.Must ve uuid.MustParse hata 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. main içinde eksik zorunlu yapılandırma. Burada bile hatayı yazdırıp os.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 error dö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/json encoder'ı), 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. nil döndürür.
  • Bir goroutine'in panic'i için main içinde recover etmek. Her goroutine'in kendi recover'ı gerekir.
  • Tüm panic'leri yutmak. Stack ile birlikte loglayın (runtime/debug'daki debug.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 recover kullanmak. 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.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA