error interfészA Go-ban a hibakezelés nem kivételekkel történik.
A hibák ehelyett egyszerű értékek, amelyeknek a típusa megvalósítja a beépített error interfészt.
Az error interfész nagyon egyszerű.
Egyetlen metódust tartalmaz, az Error()-t, amely stringként adja vissza a hibaüzenetet.
type error interface {
Error() string
}
Minden alkalommal, amikor olyan függvényt definiálsz, amelynek a futása során hiba léphet fel, és ennek el kell jutnia a hívóhoz, a visszatérési típusok közé fel kell venned az error-t.
Ha a függvénynek több visszatérési értéke van, a konvenció szerint az error mindig az utolsó.
func DoSomething() (int, error) {
// ...
}
Nem kell mindig magadnak megvalósítanod az error interfészt.
Egyszerű hiba létrehozásához használhatod az errors.New() függvényt, amely a szabványos könyvtár errors csomagjának a része.
Egyedül a hibaüzenetet kell stringként átadnod, és az errors.New() gondoskodik róla, hogy létrejöjjön egy érték, amely tartalmazza az üzenetedet, és megvalósítja az error interfészt.
Ha a függvény hibát ad vissza, jó gyakorlat, hogy az összes többi visszatérési értékhez a nullaértéket add vissza:
func DoSomething() (SomeStruct, int, error) {
// ...
return SomeStruct{}, 0, errors.New("failed to calculate result")
}
Ne feltételezd, hogy minden függvény nullaértéket ad vissza a többi visszatérési értékre, ha hiba történt. A legjobb gyakorlat az, hogy feltételezed: hiba visszaadása esetén a többi visszatérési érték egyike sem használható biztonságosan.
Példaként képzelj el egy függvényt, amely megpróbál egy fájlból olvasni, és egy félig kitöltött bájtszeletet meg egy hibát ad vissza. Ha újra felhasználnád ezt a visszaadott bájtszeletet a kódodban, abban a hitben, hogy hiba miatt úgyis mindig üres, nehezen észrevehető hibákba futhatsz.
Az egyetlen kivétel az, amikor a függvény dokumentációja egyértelműen kimondja, hogy hiba esetén a többi visszatérési érték is hasznos lehet.
Nézz meg egy példát a [buffer Write metódusának dokumentációjában][buffer-write].
Ügyelj rá, hogy a megadott hibaüzenet a lehető legpontosabb legyen, mert a hibák alapértelmezés szerint nem tartalmaznak stack trace-t. A konvenció szerint a hibaüzenet kisbetűvel kezdődjön, és ne ponttal végződjön.
Ha egy ilyen hibát több helyen is használni szeretnél (vagy elérhetővé szeretnéd tenni a csomagod felhasználói számára), akkor a hiba számára változót érdemes deklarálnod ahelyett, hogy soron belül az errors.New-t hívnád.
A konvenció szerint a változó neve Err-rel vagy err-rel kezdődjön (attól függően, hogy exportált-e vagy sem).
Az ilyen hibaváltozókat gyakran sentinel error-nak nevezik.
import "errors"
var ErrNotFound = errors.New("resource was not found")
func DoSomething() error {
// ...
return ErrNotFound
}
A hiba visszatéréseként adj nil-t, jelezve ezzel, hogy a függvény futása során nem történt hiba:
func Foo() (int, error) {
return 10, nil
}
Ha néhány változóból összetettebb hibaüzenetet kell összeállítanod, használhatod a string formázást.
A Go-nak erre a célra van egy különleges fmt.Errorf függvénye.
Ez összeállítja a hibaüzenet stringet, és visszaad egy hibát.
input := 123
action := "UPDATE"
err := fmt.Errorf("invalid input %d for action %s", input, action)
err.Error()
// => "invalid input 123 for action UPDATE"
Ha olyan függvényt hívsz meg, amely hibát ad vissza, gyakori, hogy a hiba értékét egy err nevű változóban tárolod.
Mielőtt felhasználod a függvény tényleges eredményét, ellenőrizned kell, hogy nem történt-e hiba.
Az == és a != operátorral összehasonlíthatjuk a hibát a nil-lel, és tudjuk, hogy hiba történt, ha az err nem nil.
Hogy elkerüld a kódod „happy path” részének beágyazását, először a hibás eseteket kell kezelned, általában korai visszatéréssel. További részletekért nézd meg Mat Ryer híres cikkét.
// Do this:
func myFunc() error {
file, err := os.Open("./users.csv")
if err != nil {
return err
}
// do something with file
}
// Not this:
func myFunc() error {
file, err := os.Open("./users.csv")
if err != nil {
// handle err
} else {
// do something with file
}
}
// Also not this:
func myFunc() error {
file, err := os.Open("./users.csv")
if err == nil {
// do something with file
}
// handle err
}
A legtöbbször a hiba felkerül a függvényhívási láncon, ahogy a fenti első példában is látható. A hiba kezelésének egy másik módja lehet, hogy naplózzuk, és folytatjuk valamilyen más művelettel. Jó gyakorlat vagy visszaadni, vagy naplózni a hibát, soha nem mindkettőt.
Mivel a Go-ban a legtöbb függvény visszatérési értékei között szerepel egy hiba, az if err != nil mintával mindenfelé találkozni fogsz a Go kódokban.
Ha azt szeretnéd, hogy a hibád a hibaüzenet stringen kívül több információt is tartalmazzon, létrehozhatsz egyedi hibatípust.
Ahogy korábban említettük, a Go-ban minden hiba lehet, ami megvalósítja az error interfészt (azaz van egy Error() string metódusa).
Az egyedi hibatípus létrehozásához általában structot használunk.
A konvenció szerint az egyedi hibatípusok neve Error-ral végződjön.
Érdemes továbbá az Error() string metódust pointer receiverrel létrehozni; az okokat ebben a Stackoverflow hozzászólásban olvashatod.
Vedd figyelembe, hogy ez azt jelenti, hogy pointert kell visszaadnod az egyedi hibádra, különben nem számít error-nak, mert a nem pointer típusú érték nem biztosítja az Error() string metódust.
type MyCustomError struct {
message string
details string
}
func (e *MyCustomError) Error() string {
return fmt.Sprintf("%s, details: %s", e.message, e.details)
}
func someFunction() error {
// ...
return &MyCustomError{
message: "...",
details: "...",
}
}