Gå til beste praksis - Feilhåndtering

Dette er den første artikkelen i en serie leksjoner jeg har lært gjennom par årene jeg har jobbet med Go i produksjonen. Vi driver en god del Go-tjenester i produksjon hos Saltside Technologies (psst, jeg ansetter flere stillinger i Bangalore for Saltside), og driver også min egen virksomhet der Go er en integrert del.

Vi vil dekke et bredt spekter av fag, store og små.

Det første emnet jeg ønsket å dekke i denne serien er feilhåndtering. Det skaper ofte forvirring og irritasjon for nye Go-utviklere.

Noe bakgrunn - Feilgrensesnittet

Bare så vi er på samme side. Som du kanskje vet, er en feil i Go ganske enkelt alt som implementerer feilgrensesnittet. Slik ser grensesnittdefinisjonen ut:

type feilgrensesnitt {
    Feil () streng
}

Så alt som implementerer strengmetoden Error () kan brukes som en feil.

Kontroller for feil

Bruker feilstrukturer og typekontroll

Da jeg begynte å skrive Go gjorde jeg ofte sammenligninger av feilmeldinger for å se hva feiltypen var (ja, flaut å tenke på, men noen ganger må du se tilbake for å gå frem).

En bedre tilnærming er å bruke feiltyper. Så du kan (selvfølgelig) opprette strukturer som implementerer feilgrensesnittet og deretter gjøre sammenligning av typen i en brytererklæring.

Her er et eksempel på implementering av feil.

type ErrZeroDivision struct {
    meldingsstreng
}
func NewErrZeroDivision (meldingsstreng) * ErrZeroDivision {
    retur & ErrZeroDivision {
        melding: melding,
    }
}
func (e * ErrZeroDivision) Feil () streng {
    retur e.message
}

Nå kan denne feilen brukes slik.

func main () {
    resultat, feil: = dele (1.0, 0.0)
    hvis feil! = null {
        bytte feil (type) {
        sak * ErrZeroDivision:
            fmt.Println (err.Error ())
        misligholde:
            fmt.Println ("Hva h * skjedde nettopp?")
        }
    }
    fmt.Println (resultat)
}
func divide (a, b float64) (float64, error) {
    hvis b == 0,0 {
        return 0,0, NewErrZeroDivision ("Kan ikke dele med null")
    }
    returner a / b, null
}

Her er Go Play-lenken for det fulle eksemplet. Legg merke til bryteren err. (Type) mønster, som gjør det mulig å sjekke for forskjellige feiltyper i stedet for noe annet (som strengsammenligning eller noe lignende).

Bruke feilpakken og direkte sammenligning

Ovennevnte tilnærming kan alternativt håndteres ved å bruke feilpakken. Denne tilnærmingen anbefales for feilkontroller i pakken der du trenger en rask feilrepresentasjon.

var errNotFound = error.New ("Varen er ikke funnet")
func main () {
    err: = getItem (123) // Dette vil kaste errNotFound
    hvis feil! = null {
        bytte feil {
        sak errNotFound:
            log.Println ("Forespurt vare ikke funnet")
        misligholde:
            log.Println ("Ukjent feil oppstod")
        }
    }
}

Denne tilnærmingen er mindre god når du trenger mer komplekse feilobjekter med f.eks. feilkoder osv. I så fall bør du lage din egen type som implementerer feilgrensesnittet.

Umiddelbar feilhåndtering

Noen ganger kommer jeg over kode som nedenfor (men vanligvis med mer fluff rundt ..):

func eksempel1 () feil {
    feil: = ring1 ()
    retur feil
}

Poenget her er at feilen ikke blir håndtert umiddelbart. Dette er en skjør tilnærming siden noen kan sette inn kode mellom err: = call1 () og returfeilen, noe som vil bryte intensjonen, siden det kan skygge for den første feilen. To alternative tilnærminger:

// Skjul returen og feilen.
func example2 () feil {
    returner samtale1 ()
}
// Gjør eksplisitt feilhåndtering rett etter samtalen.
func example3 () feil {
    feil: = ring1 ()
    hvis feil! = null {
        retur feil
    }
    tilbake null
}

Begge de ovennevnte tilnærmingene går bra med meg. De oppnår det samme, som er; Hvis noen trenger å legge til noe etter samtale1 (), må de ta seg av feilhåndteringen.

Det var alt for i dag

Følg med på neste artikkel om Go Best Practices. Gå sterk :).

func main () {
    err: = readArticle ("Go Best Practices - Error handling")
    hvis feil! = null {
        ping ( "@ sebdah")
    }
}