Řešení chyb v jazyce Golang
Na rozdíl od běžných metod v jiných hlavních programovacích jazycích, jako je JavaScript (který používá příkaz try… catch
) nebo Python (s blokem try… except
), vyžaduje řešení chyb v jazyce Go jiný přístup. Proč? Protože jeho funkce pro ošetření chyb se často používají nesprávně.
V tomto příspěvku na blogu se podíváme na osvědčené postupy, které lze použít pro ošetření chyb v aplikaci Go. Ke strávení tohoto článku stačí základní znalosti o tom, jak Go funguje – pokud byste se v některém bodě cítili zaseknutí, není od věci věnovat trochu času a prozkoumat neznámé koncepty.
Prázdný identifikátor
Prázdný identifikátor je anonymní zástupný znak. Může být použit jako jakýkoli jiný identifikátor v deklaraci, ale nezavádí vazbu. Prázdný identifikátor poskytuje způsob, jak ignorovat levé hodnoty v přiřazení a vyhnout se chybám překladače o nepoužitých importech a proměnných v programu. Praxe přiřazování chyb prázdnému identifikátoru místo jejich správného ošetření je nebezpečná, protože to znamená, že jste se rozhodli explicitně ignorovat hodnotu definované funkce.
result, _ := iterate(x,y)if value > 0 { // ensure you check for errors before results.}
Důvodem, proč to pravděpodobně děláte, je to, že neočekáváte chybu funkce (nebo jakoukoli jinou chybu, která může nastat), ale to by mohlo způsobit kaskádové efekty ve vašem programu. Nejlepší je ošetřit chybu, kdykoli to jde.
Ošetření chyb pomocí více návratových hodnot
Jedním ze způsobů, jak ošetřit chyby, je využít toho, že funkce v Go podporují více návratových hodnot. Můžete tedy vedle výsledku definované funkce předat i chybovou proměnnou:
func iterate(x, y int) (int, error) {}
V ukázce kódu výše musíme vrátit předdefinovanou proměnnou error
, pokud si myslíme, že existuje možnost, že naše funkce selže. error
je typ rozhraní deklarovaný v balíčku Go built-in
a jeho nulová hodnota je nil
.
type error interface { Error() string }
Obvykle vrácení chyby znamená, že došlo k problému, a vrácení nil
znamená, že k žádné chybě nedošlo:
result, err := iterate(x, y) if err != nil { // handle the error appropriately } else { // you're good to go }
Kdykoli je tedy volána funkce iterate
a err
se nerovná nil
, měla by být vrácená chyba vhodně zpracována – možností by mohlo být vytvoření instance opakovaného pokusu nebo mechanismu čištění. Jedinou nevýhodou tohoto způsobu ošetření chyb je, že neexistuje žádné vynucení ze strany překladače Go, musíte se rozhodnout, jak vytvořená funkce chybu vrátí. Můžete definovat chybovou strukturu a umístit ji na pozici vrácených hodnot. Jedním ze způsobů, jak to udělat, je použít vestavěnou strukturu errorString
(tento kód najdete také ve zdrojovém kódu Go):
package errors func New(text string) error { return &errorString { text } } type errorString struct { s string } func(e * errorString) Error() string { return e.s }
V ukázce kódu výše errorString
vkládá string
, kterou vrací metoda Error
. Chcete-li vytvořit vlastní chybu, budete muset definovat vlastní strukturu chyby a pomocí sad metod přiřadit ke struktuře funkci:
// Define an error structtype CustomError struct { msg string}// Create a function Error() string and associate it to the struct.func(error * CustomError) Error() string { return error.msg}// Then create an error object using MyError struct.func CustomErrorInstance() error { return &CustomError { "File type not supported" }}
Nově vytvořenou vlastní chybu pak můžete restrukturalizovat a použít vestavěnou strukturu error
:
import "errors"func CustomeErrorInstance() error { return errors.New("File type not supported")}
Jedním omezením vestavěné struktury error
je, že neobsahuje stopy zásobníku. To velmi ztěžuje nalezení místa, kde došlo k chybě. Chyba může projít řadou funkcí, než se vypíše. Abyste si s tím poradili, můžete si nainstalovat balíček pkg/errors
, který poskytuje základní primitiva pro zpracování chyb, jako je záznam stop zásobníku, zabalení a rozbalení chyby a formátování. Chcete-li tento balíček nainstalovat, spusťte v terminálu tento příkaz:
go get github.com/pkg/errors
Pokud potřebujete k chybám přidat stopu zásobníku nebo jiné informace usnadňující ladění, použijte funkce New
nebo Errorf
, které poskytují chyby zaznamenávající stopu zásobníku. Funkce Errorf
implementuje rozhraní fmt.Formatter
, které umožňuje formátovat chyby pomocí run balíčku fmt
(%s
, %v
, %+v
atd.):
import( "github.com/pkg/errors" "fmt")func X() error { return errors.Errorf("Could not write to file")}func customError() { return X()}func main() { fmt.Printf("Error: %+v", customError())}
Chcete-li místo prosté chybové zprávy vypsat stopy zásobníku, musíte ve formátovacím vzoru místo %v
použít %+v
a stopy zásobníku budou vypadat podobně jako v následující ukázce kódu:
Error: Could not write to filemain.X /Users/raphaelugwu/Go/src/golangProject/error_handling.go:7main.customError /Users/raphaelugwu/Go/src/golangProject/error_handling.go:15main.main /Users/raphaelugwu/Go/src/golangProject/error_handling.go:19runtime.main /usr/local/opt/go/libexec/src/runtime/proc.go:192runtime.goexit /usr/local/opt/go/libexec/src/runtime/asm_amd64.s:2471
Odložení, panika a zotavení
Přestože Go nemá výjimky, má podobný druh mechanismu známý jako „odložení, panika a zotavení“. Ideologie jazyka Go spočívá v tom, že přidání výjimek, jako je příkaz try/catch/finally
v jazyce JavaScript, by vedlo ke složitému kódu a podněcovalo by programátory k označování příliš mnoha základních chyb, jako je například neotevření souboru, za výjimečné. Výjimku defer/panic/recover
byste neměli používat stejně jako throw/catch/finally
; pouze v případech neočekávaného, neopravitelného selhání.
Defer
je jazykový mechanismus, který umisťuje volání funkce do zásobníku. Každá odložená funkce se po skončení hostitelské funkce provede v opačném pořadí bez ohledu na to, zda je vyvolána panika, nebo ne. Mechanismus odložení je velmi užitečný pro čištění zdrojů:
package mainimport ( "fmt")func A() { defer fmt.Println("Keep calm!") B()}func B() { defer fmt.Println("Else...") C()}func C() { defer fmt.Println("Turn on the air conditioner...") D()}func D() { defer fmt.Println("If it's more than 30 degrees...")}func main() { A()}
Toto by se zkompilovalo jako:
If it's more than 30 degrees...Turn on the air conditioner...Else...Keep calm!
Panic
je vestavěná funkce, která zastaví normální tok provádění. Když v kódu zavoláte panic
, znamená to, že jste se rozhodli, že váš volající nemůže problém vyřešit. Proto by se panic
měla používat pouze ve vzácných případech, kdy není bezpečné, aby váš kód nebo kdokoli, kdo integruje váš kód, v tomto bodě pokračoval. Zde je ukázka kódu znázorňující, jak panic
funguje:
package mainimport ( "errors" "fmt")func A() { defer fmt.Println("Then we can't save the earth!") B()}func B() { defer fmt.Println("And if it keeps getting hotter...") C()}func C() { defer fmt.Println("Turn on the air conditioner...") Break()}func Break() { defer fmt.Println("If it's more than 30 degrees...") panic(errors.New("Global Warming!!!"))}func main() { A()}
Výše uvedená ukázka by se zkompilovala jako:
If it's more than 30 degrees...Turn on the air conditioner...And if it keeps getting hotter...Then we can't save the earth!panic: Global Warming!!!goroutine 1 :main.Break() /tmp/sandbox186240156/prog.go:22 +0xe0main.C() /tmp/sandbox186240156/prog.go:18 +0xa0main.B() /tmp/sandbox186240156/prog.go:14 +0xa0main.A() /tmp/sandbox186240156/prog.go:10 +0xa0main.main() /tmp/sandbox186240156/prog.go:26 +0x20Program exited: status 2.
Jak je uvedeno výše, při použití panic
a jeho neošetření se zastaví tok vykonávání, všechny odložené funkce se provedou v opačném pořadí a vypíší se stopy zásobníku.
Pro ošetření panic
a vrácení hodnot předávaných z panického volání můžete použít vestavěnou funkci recover
. recover
musí být vždy volána ve funkci defer
, jinak vrátí nil
:
package mainimport ( "errors" "fmt")func A() { defer fmt.Println("Then we can't save the earth!") defer func() { if x := recover(); x != nil { fmt.Printf("Panic: %+v\n", x) } }() B()}func B() { defer fmt.Println("And if it keeps getting hotter...") C()}func C() { defer fmt.Println("Turn on the air conditioner...") Break()}func Break() { defer fmt.Println("If it's more than 30 degrees...") panic(errors.New("Global Warming!!!"))}func main() { A()}
Jak je vidět v ukázce kódu výše, recover
zabrání zastavení celého toku vykonávání, protože jsme hodili funkci panic
a kompilátor by vrátil:
If it's more than 30 degrees...Turn on the air conditioner...And if it keeps getting hotter...Panic: Global Warming!!!Then we can't save the earth!Program exited.
Chcete-li ohlásit chybu jako návratovou hodnotu, musíte zavolat funkci recover
ve stejném goroutinu, v jakém je volána funkce panic
, získat z funkce recover
strukturu chyby a předat ji do proměnné:
package mainimport ( "errors" "fmt")func saveEarth() (err error) { defer func() { if r := recover(); r != nil { err = r.(error) } }() TooLate() return}func TooLate() { A() panic(errors.New("Then there's nothing we can do"))}func A() { defer fmt.Println("If it's more than 100 degrees...")}func main() { err := saveEarth() fmt.Println(err)}
Každá odložená funkce se provede po volání funkce, ale před příkazem return. Můžete tedy nastavit vracenou proměnnou dříve, než se vykoná příkaz return. Výše uvedená ukázka kódu by se zkompilovala jako:
If it's more than 100 degrees...Then there's nothing we can doProgram exited.
Obalování chyb
Dříve bylo obalování chyb v jazyce Go přístupné pouze pomocí balíčků, například pkg/errors
. V nejnovější verzi Go – 1.13 – je však podpora obalování chyb přítomna. Podle poznámek k vydání:
Chybu
e
lze obalit jinou chybouw
poskytnutím metodyUnwrap
, která vracíw
. Programy mají k dispozici jake
, takw
, což umožňujee
poskytnout další kontext kw
nebo jej reinterpretovat a zároveň umožňuje programům rozhodovat na základěw
.
Pro vytváření zabalených chyb má nyní fmt.Errorf
sloveso %w
a pro kontrolu a rozbalování chyb bylo do balíčku error
přidáno několik funkcí:
errors.Unwrap
: Tato funkce v podstatě kontroluje a odhaluje základní chyby v programu. Vrací výsledek volání metody Unwrap
na Err
. Pokud typ Err obsahuje metodu Unwrap
vracející chybu. V opačném případě Unwrap
vrátí nil
.
package errorstype Wrapper interface{ Unwrap() error}
Níže je uveden příklad implementace metody Unwrap
:
func(e*PathError)Unwrap()error{ return e.Err}
errors.Is
: Pomocí této funkce můžete porovnat chybovou hodnotu s hodnotou sentinelu. Od našich obvyklých kontrol chyb se tato funkce liší tím, že namísto porovnání sentinelové hodnoty s jednou chybou ji porovnává s každou chybou v řetězci chyb. Implementuje také metodu Is
na chybě, takže chyba se může vyslat jako sentinel, i když není sentinelovou hodnotou.
func Is(err, target error) bool
V základní implementaci výše Is
kontroluje a hlásí, zda se err
nebo některá z errors
v jejím řetězci rovná cíli (sentinelové hodnotě).
errors.As
: Tato funkce poskytuje způsob, jak provést cast na konkrétní typ chyby. Hledá první chybu v řetězci chyb, která odpovídá sentinelové hodnotě, a pokud ji najde, nastaví sentinelovou hodnotu na tuto chybovou hodnotu a vrátí true
:
package mainimport ( "errors" "fmt" "os")func main() { if _, err := os.Open("non-existing"); err != nil { var pathError *os.PathError if errors.As(err, &pathError) { fmt.Println("Failed at path:", pathError.Path) } else { fmt.Println(err) } }}
Tento kód najdete ve zdrojovém kódu Go.
Výsledek kompilace:
Failed at path: non-existingProgram exited.
Chybě odpovídá sentinelová hodnota, pokud je konkrétní hodnota chyby přiřaditelná hodnotě, na kterou ukazuje sentinelová hodnota. As
Vyvolá paniku, pokud sentinelová hodnota není non-nil ukazatel buď na typ, který implementuje chybu, nebo na některý typ rozhraní. As
vrátí false, pokud err
je nil
.
Shrnutí
Komunita Go v poslední době dělá impozantní pokroky s podporou různých programovacích konceptů a zavádí ještě stručnější a jednodušší způsoby zpracování chyb. Máte nějaké nápady, jak ošetřit nebo pracovat s chybami, které se mohou objevit ve vašem programu Go? Dejte mi vědět v komentářích níže.
Zdroje:
Specifikace programovacího jazyka Go o Type assertion
Přednáška Marcela van Lohuizena na dotGo 2019 – Hodnoty chyb Go 2 dnes
Poznámky k vydání Go 1.13
LogRocket: LogRocket je řešení pro monitorování frontendových aplikací, které vám umožní přehrát problémy, jako by se staly ve vašem vlastním prohlížeči. Namísto hádání, proč k chybám dochází, nebo žádání uživatelů o snímky obrazovky a výpisy protokolů vám LogRocket umožní přehrát relaci a rychle pochopit, co se pokazilo. Funguje perfektně s jakoukoli aplikací bez ohledu na framework a má pluginy pro záznam dalšího kontextu z Reduxu, Vuexu a @ngrx/store.
Kromě záznamu akcí a stavu Reduxu LogRocket zaznamenává konzolové logy, chyby JavaScriptu, stacktraces, síťové požadavky/odpovědi s hlavičkami + těly, metadata prohlížeče a vlastní logy. Také instrumentuje DOM a zaznamenává HTML a CSS na stránce, čímž znovu vytváří pixelově dokonalá videa i těch nejsložitějších jednostránkových aplikací.
Vyzkoušejte jej zdarma.
Využijte jej.