Lär dig hur du hanterar fel i Go

Slutförd

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 nilbetyder 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 , apiCallEmployeeoch 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)
}

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å.