Lär dig hur du hanterar fel i Go
När du skriver dina program måste du överväga de olika sätt som dina program kan misslyckas på och du måste hantera fel. Användarna behöver inte se ett långt och förvirrande stackspårningsfel. Det är bättre om de ser meningsfull information om vad som gick fel. Som du har sett har Go inbyggda funktioner som panic
och recover
för att hantera undantag eller oväntat beteende i dina program. Men fel är kända fel som dina program ska skapas för att hantera.
Gos metod för felhantering är helt enkelt en kontrollflödesmekanism där endast en if
och en return
-instruktion behövs. När du till exempel anropar en funktion för att hämta information från ett employee
objekt kanske du vill veta om medarbetaren finns. Gos åsiktssätt för att hantera ett sådant förväntat fel skulle se ut så här:
employee, err := getInformation(1000)
if err != nil {
// Something is wrong. Do something.
}
Observera hur getInformation
funktionen returnerar struct och employee
även ett fel som ett andra värde. Felet kan vara nil
. Om felet är nil
betyder det att lyckas. Om det inte nil
är det innebär det fel. Ett icke-felnil
kommer med ett felmeddelande om att du antingen kan skriva ut eller helst logga. Så här hanterar du fel i Go. Vi går igenom några andra strategier i nästa avsnitt.
Du kommer förmodligen att märka att felhantering i Go kräver att du ägnar mer uppmärksamhet åt hur du rapporterar och hanterar ett fel. Det är precis det som är poängen. Låt oss titta på några andra exempel som hjälper dig att bättre förstå Gos metod för felhantering.
Vi använder kodfragmentet som vi använde för structs för att öva på olika strategier för felhantering:
package main
import (
"fmt"
"os"
)
type Employee struct {
ID int
FirstName string
LastName string
Address string
}
func main() {
employee, err := getInformation(1001)
if err != nil {
// Something is wrong. Do something.
} else {
fmt.Print(employee)
}
}
func getInformation(id int) (*Employee, error) {
employee, err := apiCallEmployee(1000)
return employee, err
}
func apiCallEmployee(id int) (*Employee, error) {
employee := Employee{LastName: "Doe", FirstName: "John"}
return &employee, nil
}
Härifrån fokuserar vi på att getInformation
ändra funktionerna , apiCallEmployee
och main
för att visa hur du hanterar fel.
Strategier för felhantering
När en funktion returnerar ett fel blir det vanligtvis det sista returvärdet. Det är anroparens ansvar att kontrollera om det finns ett fel och hantera det, som du såg i föregående avsnitt. Så en vanlig strategi är att fortsätta använda det mönstret för att sprida felet i en underrutin. Till exempel kan en underrutin (som getInformation
i föregående exempel) returnera felet till anroparen utan att göra något annat, så här:
func getInformation(id int) (*Employee, error) {
employee, err := apiCallEmployee(1000)
if err != nil {
return nil, err // Simply return the error to the caller.
}
return employee, nil
}
Du kanske också vill ta med mer information innan du sprider felet. För det ändamålet fmt.Errorf()
kan du använda funktionen, som liknar det vi har sett tidigare, men den returnerar ett fel. Du kan till exempel lägga till mer kontext till felet och fortfarande returnera det ursprungliga felet, så här:
func getInformation(id int) (*Employee, error) {
employee, err := apiCallEmployee(1000)
if err != nil {
return nil, fmt.Errorf("Got an error when getting the employee information: %v", err)
}
return employee, nil
}
En annan strategi är att köra logik för återförsök när fel är tillfälliga. Du kan till exempel använda en återförsöksprincip för att anropa en funktion tre gånger och vänta i två sekunder, så här:
func getInformation(id int) (*Employee, error) {
for tries := 0; tries < 3; tries++ {
employee, err := apiCallEmployee(1000)
if err == nil {
return employee, nil
}
fmt.Println("Server is not responding, retrying ...")
time.Sleep(time.Second * 2)
}
return nil, fmt.Errorf("server has failed to respond to get the employee information")
}
I stället för att skriva ut fel till konsolen kan du logga fel och dölja all implementeringsinformation från slutanvändarna. Vi går igenom loggning i nästa modul. Nu ska vi titta på hur du kan skapa och använda anpassade fel.
Skapa återanvändbara fel
Ibland ökar antalet felmeddelanden och du vill behålla ordningen. Eller så kanske du vill skapa ett bibliotek för vanliga felmeddelanden som du vill återanvända. I Go kan du använda errors.New()
funktionen för att skapa fel och återanvända dem i flera delar, så här:
var ErrNotFound = errors.New("Employee not found!")
func getInformation(id int) (*Employee, error) {
if id != 1001 {
return nil, ErrNotFound
}
employee := Employee{LastName: "Doe", FirstName: "John"}
return &employee, nil
}
Koden för getInformation
funktionen ser bättre ut, och om du behöver ändra felmeddelandet gör du det bara på ett ställe. Observera också att konventionen är att inkludera prefixet Err
för felvariabler.
När du har en felvariabel kan du slutligen vara mer specifik när du hanterar ett fel i en anroparfunktion. Med errors.Is()
funktionen kan du jämföra vilken typ av fel du får, så här:
employee, err := getInformation(1000)
if errors.Is(err, ErrNotFound) {
fmt.Printf("NOT FOUND: %v\n", err)
} else {
fmt.Print(employee)
}
Rekommenderade metoder för felhantering
När du hanterar fel i Go, här är några rekommenderade metoder att tänka på:
- Sök alltid efter fel, även om du inte förväntar dig dem. Hantera dem sedan korrekt för att undvika att exponera onödig information för slutanvändarna.
- Inkludera ett prefix i ett felmeddelande så att du vet orsaken till felet. Du kan till exempel inkludera namnet på paketet och funktionen.
- Skapa återanvändbara felvariabler så mycket du kan.
- Förstå skillnaden mellan att använda återkommande fel och panik. Panik när det inte finns något annat du kan göra. Om ett beroende till exempel inte är klart är det inte meningsfullt att programmet fungerar (såvida du inte vill köra ett standardbeteende).
- Loggfel med så mycket information som möjligt (vi går igenom hur det går till i nästa avsnitt) och skriver ut fel som slutanvändaren kan förstå.