Gå till bästa praxis - Felhantering

Detta är den första artikeln i en serie lektioner som jag har lärt mig under de par år jag arbetat med Go i produktionen. Vi driver ett stort antal Go-tjänster i produktion på Saltside Technologies (psst, jag anställer flera positioner i Bangalore för Saltside) och jag driver också min egen verksamhet där Go är en integrerad del.

Vi kommer att täcka ett brett spektrum av ämnen, stora och små.

Det första ämnet jag ville täcka i den här serien är felhantering. Det orsakar ofta förvirring och irritation för nya Go-utvecklare.

Lite bakgrund - Felgränssnittet

Bara så vi är på samma sida. Som du kanske vet att ett fel i Go är helt enkelt allt som implementerar felgränssnittet. Så här ser gränssnittsdefinitionen ut:

typfelgränssnitt {
    Fel () sträng
}

Så allt som implementerar strängmetoden Error () kan användas som ett fel.

Kontrollerar för fel

Med hjälp av felstrukturer och typkontroll

När jag började skriva Go gjorde jag ofta strängjämförelser av felmeddelanden för att se vad feltypen var (ja, pinsamt att tänka på men ibland måste du titta tillbaka för att gå framåt).

En bättre metod är att använda feltyper. Så du kan (naturligtvis) skapa strukturer som implementerar felgränssnittet och sedan göra jämförelse i ett switch-uttalande.

Här är ett exempel på felimplementering.

typ ErrZeroDivision struct {
    meddelandesträng
}
func NewErrZeroDivision (meddelandesträng) * ErrZeroDivision {
    return & ErrZeroDivision {
        meddelande: meddelande,
    }
}
func (e * ErrZeroDivision) Fel () sträng {
    tillbaka e.message
}

Nu kan detta fel användas så här.

func main () {
    resultat, fel: = dela (1.0, 0.0)
    om fel! = noll {
        växla fel. (typ) {
        fall * ErrZeroDivision:
            fmt.Println (err.Error ())
        standard:
            fmt.Println ("Vad h * just hände?")
        }
    }
    fmt.Println (resultat)
}
func divide (a, b float64) (float64, error) {
    om b == 0,0 {
        returnera 0,0, NewErrZeroDivision ("Kan inte dela med noll")
    }
    returnera a / b, noll
}

Här är Go Play-länken för hela exemplet. Lägg märke till brytarens felaktiga (typ) -mönster, vilket gör det möjligt att kontrollera för olika feltyper snarare än något annat (som strängjämförelse eller något liknande).

Använda felpaketet och direkt jämförelse

Ovanstående metod kan alternativt hanteras med hjälp av felpaketet. Det här tillvägagångssättet rekommenderas för felkontroller i paketet där du behöver en snabb felrepresentation.

var errNotFound = error.New ("Objekt hittades inte")
func main () {
    err: = getItem (123) // Detta skulle kasta errNotFound
    om fel! = noll {
        växla fel {
        case errNotFound:
            log.Println ("Begäran hittades inte")
        standard:
            log.Println ("Okänt fel inträffade")
        }
    }
}

Detta tillvägagångssätt är mindre bra när du behöver mer komplexa felobjekt med t.ex. felkoder etc. I så fall ska du skapa din egen typ som implementerar felgränssnittet.

Omedelbar felhantering

Ibland stöter jag på kod som nedan (men vanligtvis med mer fluff runt ..):

func example1 () -fel {
    fel: = call1 ()
    tillbaka fel
}

Poängen här är att felet inte hanteras omedelbart. Detta är ett bräckligt tillvägagångssätt eftersom någon kan infoga kod mellan err: = call1 () och retursfel, vilket skulle bryta avsikten, eftersom det kan skugga det första felet. Två alternativa metoder:

// Komprimera returen och felet.
func example2 () -fel {
    återuppringning1 ()
}
// Gör explicita felhantering direkt efter samtalet.
func example3 () -fel {
    fel: = call1 ()
    om fel! = noll {
        tillbaka fel
    }
    tillbaka noll
}

Båda ovanstående metoderna är bra med mig. De uppnår samma sak, som är; om någon behöver lägga till något efter samtal1 () måste de ta hand om felhanteringen.

Det är allt för idag

Håll ögonen öppna för nästa artikel om Go Best Practices. Gå stark :).

func main () {
    err: = readArticle ("Go Best Practices - Error handling")
    om fel! = noll {
        ping ( "@ sebdah")
    }
}