Einführung
Fehler, auf die ein Programm stößt, lassen sich in zwei große Kategorien einteilen: Fehler, die der Programmierer erwartet hat, und Fehler, die der Programmierer nicht erwartet hat. Dieerror
-Schnittstelle, die wir in unseren beiden vorherigen Artikeln zuerror handlingbehandelt haben, befasst sich hauptsächlich mit Fehlern, die wir beim Schreiben von Go-Programmen erwarten. Dieerror
-Schnittstelle ermöglicht es uns sogar, die seltene Möglichkeit eines Fehlers bei Funktionsaufrufen zu erkennen, damit wir in diesen Situationen angemessen reagieren können.
Paniken fallen in die zweite Kategorie von Fehlern, die vom Programmierer nicht erwartet werden. Diese unvorhergesehenen Fehler führen dazu, dass ein Programm spontan beendet und das laufende Go-Programm beendet wird. Häufige Fehler sind häufig für die Entstehung von Panik verantwortlich. In diesem Lernprogramm werden einige Möglichkeiten untersucht, mit denen allgemeine Vorgänge in Go zu Paniken führen können. Außerdem werden Möglichkeiten zur Vermeidung dieser Paniken aufgezeigt. Wir werden auch die Anweisungen vondefer
zusammen mit der Funktion vonrecover
verwenden, um Panik zu erfassen, bevor sie unsere laufenden Go-Programme unerwartet beenden können.
Panik verstehen
Es gibt bestimmte Operationen in Go, die automatisch Panik auslösen und das Programm stoppen. Zu den allgemeinen Operationen gehören das Indizieren vonarray über seine Kapazität hinaus, das Ausführen von Typzusicherungen, das Aufrufen von Methoden für Nullzeiger, die falsche Verwendung von Mutexen und der Versuch, mit geschlossenen Kanälen zu arbeiten. Die meisten dieser Situationen sind auf Fehler zurückzuführen, die der Compiler beim Kompilieren des Programms nicht erkennen kann.
Da Panics Details enthalten, die zur Behebung eines Problems hilfreich sind, verwenden Entwickler Panics häufig als Hinweis darauf, dass sie während der Entwicklung eines Programms einen Fehler gemacht haben.
Out of Bounds Panics
Wenn Sie versuchen, auf einen Index zuzugreifen, der über die Länge eines Slices oder die Kapazität eines Arrays hinausgeht, erzeugt die Go-Laufzeit eine Panik.
Das folgende Beispiel macht den häufigen Fehler, dass versucht wird, auf das letzte Element eines Slice zuzugreifen, indem die Länge des Slice verwendet wird, das von den eingebautenlen
zurückgegeben wird. Versuchen Sie, diesen Code auszuführen, um festzustellen, warum dies zu einer Panik führen kann:
package main
import (
"fmt"
)
func main() {
names := []string{
"lobster",
"sea urchin",
"sea cucumber",
}
fmt.Println("My favorite sea creature is:", names[len(names)])
}
Dies wird die folgende Ausgabe haben:
Outputpanic: runtime error: index out of range [3] with length 3
goroutine 1 [running]:
main.main()
/tmp/sandbox879828148/prog.go:13 +0x20
Der Name der Ausgabe der Panik gibt einen Hinweis:panic: runtime error: index out of range
. Wir haben eine Scheibe mit drei Meerestieren erstellt. Wir haben dann versucht, das letzte Element des Slice zu erhalten, indem wir dieses Slice mit der Länge des Slice mithilfe der integrierten Funktionlen
indiziert haben. Denken Sie daran, dass Slices und Arrays auf Null basieren. Das erste Element ist also Null und das letzte Element in diesem Slice hat den Index2
. Da wir versuchen, auf das Slice am dritten Index,3
, zuzugreifen, gibt es kein Element im Slice, das zurückgegeben werden kann, da es außerhalb der Grenzen des Slice liegt. Die Laufzeit hat keine andere Wahl, als zu beenden und zu beenden, da wir sie aufgefordert haben, etwas Unmögliches zu tun. Go kann während der Kompilierung auch nicht beweisen, dass dieser Code dies versucht, sodass der Compiler dies nicht abfangen kann.
Beachten Sie auch, dass der nachfolgende Code nicht ausgeführt wurde. Dies liegt daran, dass eine Panik ein Ereignis ist, das die Ausführung Ihres Go-Programms vollständig unterbricht. Die erzeugte Nachricht enthält mehrere Informationen, die für die Diagnose der Ursache der Panik hilfreich sind.
Anatomie einer Panik
Panik besteht aus einer Meldung, die die Ursache der Panik angibt, und einemstack trace, mit dem Sie feststellen können, wo in Ihrem Code die Panik ausgelöst wurde.
Der erste Teil jeder Panik ist die Nachricht. Es beginnt immer mit der Zeichenfolgepanic:
, gefolgt von einer Zeichenfolge, die je nach Ursache der Panik variiert. Die Panik aus der vorherigen Übung hat die Nachricht:
panic: runtime error: index out of range [3] with length 3
Die Zeichenfolgeruntime error:
nach dem Präfixpanic:
gibt an, dass die Panik durch die Sprachlaufzeit generiert wurde. Diese Panik sagt uns, dass wir versucht haben, einen Index[3]
zu verwenden, der außerhalb des Bereichs der Slice-Länge3
lag.
Nach dieser Nachricht folgt der Stack-Trace. Stapelverfolgungen bilden eine Karte, der wir folgen können, um genau zu ermitteln, welche Codezeile ausgeführt wurde, als die Panik ausgelöst wurde, und wie dieser Code durch früheren Code aufgerufen wurde.
goroutine 1 [running]:
main.main()
/tmp/sandbox879828148/prog.go:13 +0x20
Diese Stapelverfolgung aus dem vorherigen Beispiel zeigt, dass unser Programm die Panik aus der Datei/tmp/sandbox879828148/prog.go
in Zeile 13 generiert hat. Es sagt uns auch, dass diese Panik in der Funktionmain()
aus dem Paketmain
erzeugt wurde.
Die Stapelverfolgung ist in separate Blöcke unterteilt - einen für jedesgoroutine in Ihrem Programm. Die Ausführung jedes Go-Programms erfolgt durch eine oder mehrere Goroutinen, die jeweils unabhängig und gleichzeitig Teile Ihres Go-Codes ausführen können. Jeder Block beginnt mit dem Headergoroutine X [state]:
. Der Header gibt die ID-Nummer der Goroutine zusammen mit dem Zustand an, in dem sie sich befand, als die Panik auftrat. Nach dem Header zeigt der Stack-Trace die Funktion, die das Programm beim Auftreten der Panik ausgeführt hat, sowie den Dateinamen und die Zeilennummer, in der die Funktion ausgeführt wurde.
Die Panik im vorherigen Beispiel wurde durch einen uneingeschränkten Zugriff auf ein Slice ausgelöst. Panics können auch generiert werden, wenn Methoden für nicht gesetzte Zeiger aufgerufen werden.
Nil Empfänger
Die Programmiersprache Go verfügt über Zeiger, die auf eine bestimmte Instanz eines Typs verweisen, die zur Laufzeit im Arbeitsspeicher des Computers vorhanden ist. Zeiger können den Wertnil
annehmen, der angibt, dass sie auf nichts zeigen. Wenn wir versuchen, Methoden für einen Zeiger aufzurufen, dernil
ist, erzeugt die Go-Laufzeit eine Panik. In ähnlicher Weise führen Variablen, die Schnittstellentypen sind, auch zu Problemen, wenn Methoden für sie aufgerufen werden. Verwenden Sie das folgende Beispiel, um die in diesen Fällen generierten Panics anzuzeigen:
package main
import (
"fmt"
)
type Shark struct {
Name string
}
func (s *Shark) SayHello() {
fmt.Println("Hi! My name is", s.Name)
}
func main() {
s := &Shark{"Sammy"}
s = nil
s.SayHello()
}
Die Panik wird so aussehen:
Outputpanic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xdfeba]
goroutine 1 [running]:
main.(*Shark).SayHello(...)
/tmp/sandbox160713813/prog.go:12
main.main()
/tmp/sandbox160713813/prog.go:18 +0x1a
In diesem Beispiel haben wir eine Struktur namensShark
definiert. Shark
hat auf seinem Zeigerempfänger eine Methode namensSayHello
definiert, die beim Aufruf eine Begrüßung nach Standardausgabe druckt. Im Hauptteil unserermain
-Funktion erstellen wir eine neue Instanz dieserShark
-Struktur und fordern mit dem&
-Operator einen Zeiger darauf an. Dieser Zeiger ist der Variablens
zugeordnet. Anschließend weisen wir die Variables
mit der Anweisungs = nil
dem Wertnil
neu zu. Schließlich versuchen wir, die MethodeSayHello
für die Variables
aufzurufen. Anstatt eine freundliche Nachricht von Sammy zu erhalten, erhalten wir die Panik, dass wir versucht haben, auf eine ungültige Speicheradresse zuzugreifen. Da die Variables
nil
ist, versucht sie beim Aufrufen der FunktionSayHello
, auf das FeldName
des Typs*Shark
zuzugreifen. Da dies ein Zeigerempfänger ist und der Empfänger in diesem Fallnil
ist, gerät er in Panik, weil er einennil
-Zeiger nicht dereferenzieren kann.
Während wir in diesem Beispiels
explizit aufnil
gesetzt haben, geschieht dies in der Praxis weniger offensichtlich. Wenn Sie Panik mitnil pointer dereference
sehen, stellen Sie sicher, dass Sie alle von Ihnen erstellten Zeigervariablen ordnungsgemäß zugewiesen haben.
Panics, die aus Nullzeigern und Zugriffen außerhalb der Grenzen generiert werden, sind zwei häufig auftretende Panics, die von der Laufzeit generiert werden. Es ist auch möglich, mithilfe einer integrierten Funktion manuell eine Panik zu generieren.
Verwenden der integrierten Funktionpanic
Mit der integrierten Funktionpanic
können wir auch eigene Panik erzeugen. Es wird eine einzelne Zeichenfolge als Argument verwendet. Dies ist die Nachricht, die die Panik erzeugt. In der Regel ist diese Meldung weniger ausführlich als das Umschreiben unseres Codes, um einen Fehler zurückzugeben. Darüber hinaus können wir dies in unseren eigenen Paketen verwenden, um Entwicklern mitzuteilen, dass sie möglicherweise einen Fehler bei der Verwendung des Codes unseres Pakets gemacht haben. Wenn immer möglich, sollten Sie versuchen, den Verbrauchern unseres Paketserror
-Werte zurückzugeben.
Führen Sie diesen Code aus, um eine Panik zu sehen, die von einer Funktion generiert wurde, die von einer anderen Funktion aufgerufen wurde:
package main
func main() {
foo()
}
func foo() {
panic("oh no!")
}
Die erzeugte Panik-Ausgabe sieht folgendermaßen aus:
Outputpanic: oh no!
goroutine 1 [running]:
main.foo(...)
/tmp/sandbox494710869/prog.go:8
main.main()
/tmp/sandbox494710869/prog.go:4 +0x40
Hier definieren wir eine Funktionfoo
, die die mit der Zeichenfolge"oh no!"
eingebautenpanic
aufruft. Diese Funktion wird von unserermain
-Funktion aufgerufen. Beachten Sie, dass die Ausgabe die Nachrichtpanic: oh no!
enthält und die Stapelverfolgung eine einzelne Goroutine mit zwei Zeilen in der Stapelverfolgung zeigt: eine für die Funktionmain()
und eine für unsere Funktionfoo()
.
Wir haben festgestellt, dass Panics unser Programm an dem Ort beenden, an dem sie generiert wurden. Dies kann zu Problemen führen, wenn offene Ressourcen ordnungsgemäß geschlossen werden müssen. Go bietet einen Mechanismus, mit dem immer Code ausgeführt werden kann, auch wenn eine Panik vorliegt.
Aufgeschobene Funktionen
Ihr Programm verfügt möglicherweise über Ressourcen, die ordnungsgemäß bereinigt werden müssen, auch wenn die Laufzeit eine Panik verarbeitet. Mit Go können Sie die Ausführung eines Funktionsaufrufs verschieben, bis die aufrufende Funktion die Ausführung abgeschlossen hat. Aufgeschobene Funktionen werden auch bei Auftreten einer Panik ausgeführt und dienen als Sicherheitsmechanismus, um der chaotischen Natur von Panik vorzubeugen. Funktionen werden verzögert, indem sie wie gewohnt aufgerufen werden und der gesamten Anweisung das Schlüsselwortdefer
vorangestellt wird, wie indefer sayHello()
. Führen Sie dieses Beispiel aus, um zu sehen, wie eine Nachricht gedruckt werden kann, obwohl eine Panik ausgelöst wurde:
package main
import "fmt"
func main() {
defer func() {
fmt.Println("hello from the deferred function!")
}()
panic("oh no!")
}
Die Ausgabe dieses Beispiels sieht folgendermaßen aus:
Outputhello from the deferred function!
panic: oh no!
goroutine 1 [running]:
main.main()
/Users/gopherguides/learn/src/github.com/gopherguides/learn//handle-panics/src/main.go:10 +0x55
Innerhalb dermain
-Funktion dieses Beispiels rufen wir zuerstdefer
eine anonyme Funktion auf, die die Nachricht"hello from the deferred function!"
druckt. Diemain
-Funktion erzeugt dann sofort eine Panik unter Verwendung derpanic
-Funktion. In der Ausgabe dieses Programms sehen wir zuerst, dass die zurückgestellte Funktion ausgeführt wird und drucken ihre Nachricht. Darauf folgt die Panik, die wir inmain
erzeugt haben.
Aufgeschobene Funktionen bieten Schutz vor der Überraschung durch Panik. Innerhalb von verzögerten Funktionen bietet Go auch die Möglichkeit, das Beenden unseres Go-Programms durch eine Panik mithilfe einer anderen integrierten Funktion zu verhindern.
Umgang mit Panik
Panics haben einen einzigen Wiederherstellungsmechanismus - die inrecover
integrierte Funktion. Mit dieser Funktion können Sie eine Panik auf dem Weg durch den Aufrufstapel abfangen und verhindern, dass Ihr Programm unerwartet beendet wird. Es hat strenge Regeln für seine Verwendung, kann aber in einer Produktionsanwendung von unschätzbarem Wert sein.
Da es Teil des Paketsbuiltin
ist, kannrecover
aufgerufen werden, ohne zusätzliche Pakete zu importieren:
package main
import (
"fmt"
"log"
)
func main() {
divideByZero()
fmt.Println("we survived dividing by zero!")
}
func divideByZero() {
defer func() {
if err := recover(); err != nil {
log.Println("panic occurred:", err)
}
}()
fmt.Println(divide(1, 0))
}
func divide(a, b int) int {
return a / b
}
Dieses Beispiel gibt Folgendes aus:
Output2009/11/10 23:00:00 panic occurred: runtime error: integer divide by zero
we survived dividing by zero!
Unsere Funktionmain
in diesem Beispiel ruft eine von uns definierte FunktiondivideByZero
auf. Innerhalb dieser Funktion rufen wirdefer
eine anonyme Funktion auf, die für die Behandlung von Paniken verantwortlich ist, die während der Ausführung vondivideByZero
auftreten können. Innerhalb dieser verzögerten anonymen Funktion rufen wir die integrierte Funktion vonrecover
auf und weisen den zurückgegebenen Fehler einer Variablen zu. WenndivideByZero
in Panik gerät, wird dieser Wert fürerror
festgelegt, andernfalls ist esnil
. Durch Vergleichen der Variablenerr
mitnil
können wir feststellen, ob eine Panik aufgetreten ist, und in diesem Fall protokollieren wir die Panik mithilfe der Funktionlog.Println
, als wäre es eine andereerror
) s.
Nach dieser verzögerten anonymen Funktion rufen wir eine andere von uns definierte Funktion auf,divide
, und versuchen, ihre Ergebnisse mitfmt.Println
zu drucken. Die angegebenen Argumente bewirken, dassdivide
eine Division durch Null durchführt, was zu einer Panik führt.
In der Ausgabe zu diesem Beispiel sehen wir zuerst die Protokollnachricht der anonymen Funktion, die die Panik wiederherstellt, gefolgt von der Nachrichtwe survived dividing by zero!
. Wir haben dies in der Tat getan, dank der inrecover
integrierten Funktion, die eine ansonsten katastrophale Panik stoppt, die unser Go-Programm beenden würde.
Der vonrecover()
zurückgegebeneerr
-Wert ist genau der Wert, der für den Aufruf vonpanic()
angegeben wurde. Es ist daher wichtig sicherzustellen, dass der Wert vonerr
nur dann Null ist, wenn keine Panik aufgetreten ist.
Panik mitrecover
erkennen
Die Funktionrecover
basiert auf dem Wert des Fehlers, um festzustellen, ob eine Panik aufgetreten ist oder nicht. Da das Argument für die Funktionpanic
eine leere Schnittstelle ist, kann es sich um einen beliebigen Typ handeln. Der Nullwert für jeden Schnittstellentyp, einschließlich der leeren Schnittstelle, istnil
. Es muss darauf geachtet werden,nil
als Argument fürpanic
zu vermeiden, wie in diesem Beispiel gezeigt:
package main
import (
"fmt"
"log"
)
func main() {
divideByZero()
fmt.Println("we survived dividing by zero!")
}
func divideByZero() {
defer func() {
if err := recover(); err != nil {
log.Println("panic occurred:", err)
}
}()
fmt.Println(divide(1, 0))
}
func divide(a, b int) int {
if b == 0 {
panic(nil)
}
return a / b
}
Dies wird Folgendes ausgeben:
Outputwe survived dividing by zero!
Dieses Beispiel ist dasselbe wie das vorherige Beispiel mitrecover
mit einigen geringfügigen Änderungen. Die Funktiondivide
wurde geändert, um zu überprüfen, ob ihr Teilerb
gleich0
ist. Wenn dies der Fall ist, wird unter Verwendung der integriertenpanic
mit dem Argumentnil
eine Panik ausgelöst. Die Ausgabe enthält diesmal nicht die Protokollmeldung, die angibt, dass eine Panik aufgetreten ist, obwohl eine vondivide
erstellt wurde. Aus diesem Grund ist es sehr wichtig sicherzustellen, dass das Argument für die integrierte Funktionpanic
nichtnil
ist.
Fazit
Wir haben eine Reihe von Möglichkeiten gesehen, wiepanic+`s can be created in Go and how they can be recovered from using the `+recover
eingebaut wird. Während Siepanic
nicht unbedingt selbst verwenden müssen, ist die ordnungsgemäße Wiederherstellung nach Panik ein wichtiger Schritt, um Go-Anwendungen produktionsbereit zu machen.
Sie können auchour entire How To Code in Go series erkunden.