Gestionarea erorilor în Golang

Dincolo de metodele convenționale din alte limbaje de programare principale, cum ar fi JavaScript (care utilizează instrucțiunea try… catch) sau Python (cu blocul try… except), abordarea erorilor în Go necesită o abordare diferită. De ce? Pentru că funcțiile sale de tratare a erorilor sunt adesea aplicate greșit.

În această postare pe blog, vom analiza cele mai bune practici care ar putea fi folosite pentru a trata erorile într-o aplicație Go. O înțelegere de bază a modului în care funcționează Go este tot ceea ce este necesar pentru a digera acest articol – în cazul în care vă simțiți blocat la un moment dat, este în regulă să vă luați ceva timp și să cercetați concepte necunoscute.

Identificatorul gol

Identificatorul gol este un marcator anonim. Acesta poate fi utilizat ca orice alt identificator într-o declarație, dar nu introduce o legătură. Identificatorul blank oferă o modalitate de a ignora valorile stângace într-o atribuire și de a evita erorile compilatorului cu privire la importurile și variabilele neutilizate într-un program. Practica de a atribui erori la identificatorul blank în loc să le gestionați în mod corespunzător este nesigură, deoarece aceasta înseamnă că ați decis să ignorați în mod explicit valoarea funcției definite.

result, _ := iterate(x,y)if value > 0 { // ensure you check for errors before results.}

Motivul pentru care faceți probabil acest lucru este că nu vă așteptați la o eroare din partea funcției (sau orice eroare ar putea apărea), dar acest lucru ar putea crea efecte în cascadă în programul dumneavoastră. Cel mai bun lucru de făcut este să gestionați o eroare ori de câte ori puteți.

Manipularea erorilor prin valori de retur multiple

Un mod de a gestiona erorile este să profitați de faptul că funcțiile din Go suportă valori de retur multiple. Astfel, puteți trece o variabilă de eroare alături de rezultatul funcției pe care o definiți:

func iterate(x, y int) (int, error) {}

În exemplul de cod de mai sus, trebuie să returnăm variabila predefinită error dacă ne gândim că există o șansă ca funcția noastră să eșueze. error este un tip de interfață declarat în pachetul built-in din Go, iar valoarea sa zero este nil.

type error interface { Error() string }

De obicei, returnarea unei erori înseamnă că există o problemă, iar returnarea lui nil înseamnă că nu au existat erori:

result, err := iterate(x, y) if err != nil { // handle the error appropriately } else { // you're good to go }

Acum, ori de câte ori funcția iterate este apelată și err nu este egală cu nil, eroarea returnată trebuie tratată în mod corespunzător – o opțiune ar putea fi crearea unei instanțe a unui mecanism de reluare sau de curățare. Singurul dezavantaj al gestionării erorilor în acest mod este că nu există o aplicare din partea compilatorului Go, trebuie să decideți modul în care funcția pe care ați creat-o returnează eroarea. Puteți defini o structură de eroare și o puteți plasa în poziția valorilor returnate. O modalitate de a face acest lucru este folosind structura încorporată errorString (puteți găsi acest cod și în codul sursă Go):

package errors func New(text string) error { return &errorString { text } } type errorString struct { s string } func(e * errorString) Error() string { return e.s }

În exemplul de cod de mai sus, errorString încorporează un string care este returnat de metoda Error. Pentru a crea o eroare personalizată, va trebui să vă definiți structura de erori și să folosiți seturi de metode pentru a asocia o funcție la structura dvs.:

// 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" }}

Eroarea personalizată nou creată poate fi apoi restructurată pentru a utiliza structura încorporată error:

 import "errors"func CustomeErrorInstance() error { return errors.New("File type not supported")}

O singură limitare a structurii încorporate error este că aceasta nu vine cu urme de stivă. Acest lucru face ca localizarea locului unde s-a produs o eroare să fie foarte dificilă. Eroarea ar putea trece printr-un număr de funcții înainte de a fi tipărită. Pentru a gestiona acest lucru, ați putea instala pachetul pkg/errors, care oferă primitive de bază de tratare a erorilor, cum ar fi înregistrarea urmelor de stivă, înfășurarea, desfacerea și formatarea erorilor. Pentru a instala acest pachet, rulați această comandă în terminalul dvs.:

go get github.com/pkg/errors

Când aveți nevoie să adăugați la erorile dvs. urme de stivă sau orice alte informații care facilitează depanarea, utilizați funcțiile New sau Errorf pentru a furniza erori care înregistrează urma de stivă. Errorf implementează interfața fmt.Formatter care vă permite să formatați erorile utilizând runele pachetului fmt (%s, %v, %+v etc.):

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())}

Pentru a imprima urme de stivă în loc de un simplu mesaj de eroare, trebuie să utilizați %+v în loc de %v în modelul de format, iar urmele de stivă vor arăta similar cu exemplul de cod de mai jos:

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

Defer, panic, and recover

Deși Go nu are excepții, are un tip similar de mecanism cunoscut sub numele de „Defer, panic, and recover”. Ideologia Go este că adăugarea de excepții, cum ar fi declarația try/catch/finally din JavaScript, ar duce la un cod complex și ar încuraja programatorii să eticheteze prea multe erori de bază, cum ar fi eșecul de a deschide un fișier, ca fiind excepționale. Nu ar trebui să folosiți defer/panic/recover așa cum ați folosi throw/catch/finally; numai în cazuri de eșec neașteptat și irecuperabil.

Defer este un mecanism al limbajului care pune apelul de funcție într-o stivă. Fiecare funcție amânată este executată în ordine inversă atunci când funcția gazdă se termină, indiferent dacă se apelează sau nu o panică. Mecanismul de amânare este foarte util pentru curățarea resurselor:

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()}

Aceasta s-ar compila ca:

If it's more than 30 degrees...Turn on the air conditioner...Else...Keep calm!

Panic este o funcție încorporată care oprește fluxul normal de execuție. Atunci când apelați panic în cod, înseamnă că ați decis că apelantul dvs. nu poate rezolva problema. Astfel, panic ar trebui să fie utilizată numai în cazuri rare în care nu este sigur pentru codul dvs. sau pentru oricine integrează codul dvs. să continue în acel punct. Iată un exemplu de cod care descrie modul în care funcționează panic:

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()}

Eșantionul de mai sus ar fi compilat ca:

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.

După cum se arată mai sus, atunci când panic este utilizat și nu este gestionat, fluxul de execuție se oprește, toate funcțiile amânate sunt executate în ordine inversă și sunt tipărite urme de stivă.

Puteți utiliza funcția încorporată recover pentru a gestiona panic și a returna valorile care trec de la un apel de panică. recover trebuie să fie întotdeauna apelată într-o funcție defer, altfel va returna 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()}

După cum se poate observa în exemplul de cod de mai sus, recover previne ca întregul flux de execuție să se oprească pentru că am aruncat o funcție panic și compilatorul ar fi returnat:

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.

Pentru a raporta o eroare ca valoare de retur, trebuie să apelați funcția recover în aceeași goroutine în care este apelată funcția panic, să preluați o structură de eroare din funcția recover și să o treceți într-o variabilă:

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)}

Toată funcția amânată va fi executată după un apel de funcție, dar înainte de o instrucțiune de returnare. Astfel, puteți seta o variabilă returnată înainte ca o instrucțiune return să fie executată. Exemplul de cod de mai sus s-ar compila sub forma:

If it's more than 100 degrees...Then there's nothing we can doProgram exited.

Error wrapping

Anterior, error wrapping în Go era accesibil doar prin utilizarea unor pachete precum pkg/errors. Cu toate acestea, odată cu cea mai recentă versiune a Go – versiunea 1.13, există suport pentru error wrapping. Conform notelor de lansare:

O eroare e poate înveli o altă eroare w prin furnizarea unei metode Unwrap care returnează w. Atât e, cât și w sunt disponibile pentru programe, permițând ca e să ofere un context suplimentar pentru w sau să o reinterpreteze, permițând în același timp programelor să ia decizii bazate pe w.

Pentru a crea erori înfășurate, fmt.Errorf are acum un verb %w, iar pentru inspectarea și desfacerea erorilor, au fost adăugate câteva funcții la pachetul error:

errors.Unwrap: Această funcție inspectează și expune, practic, erorile care stau la baza unui program. Ea returnează rezultatul apelării metodei Unwrap pe Err. Dacă tipul lui Err conține o metodă Unwrap care returnează o eroare. În caz contrar, Unwrap returnează nil.

package errorstype Wrapper interface{ Unwrap() error}

Mai jos este un exemplu de implementare a metodei Unwrap:

func(e*PathError)Unwrap()error{ return e.Err}

errors.Is: Cu această funcție, puteți compara o valoare de eroare cu valoarea santinelă. Ceea ce face ca această funcție să fie diferită de verificările noastre obișnuite ale erorilor este că, în loc să compare valoarea santinelă cu o singură eroare, o compară cu fiecare eroare din lanțul de erori. De asemenea, implementează o metodă Is pe o eroare, astfel încât o eroare se poate afișa ca fiind o valoare santinelă, chiar dacă nu este o valoare santinelă.

func Is(err, target error) bool

În implementarea de bază de mai sus, Is verifică și raportează dacă err sau oricare dintre errors din lanțul său sunt egale cu ținta (valoarea santinelă).

errors.As: Această funcție oferă o modalitate de a face cast la un anumit tip de eroare. Ea caută prima eroare din lanțul de erori care se potrivește cu valoarea santinelă și, dacă este găsită, stabilește valoarea santinelă la acea valoare a erorii și returnează 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) } }}

Puteți găsi acest cod în codul sursă Go.

Rezultat compilator:

Failed at path: non-existingProgram exited.

O eroare se potrivește cu valoarea santinelă dacă valoarea concretă a erorii poate fi atribuită la valoarea indicată de valoarea santinelă. As va intra în panică dacă valoarea santinelă nu este un pointer non-nil fie la un tip care implementează eroarea, fie la orice tip de interfață. As returnează false dacă err este nil.

Rezumat

Comunitatea Go a făcut pași impresionanți în ultima vreme cu suportul pentru diverse concepte de programare și introducerea unor modalități și mai concise și mai ușoare de a gestiona erorile. Aveți idei despre cum să gestionați sau să lucrați cu erorile care pot apărea în programul dumneavoastră Go? Anunțați-mă în comentariile de mai jos.

Resurse:
Specificația limbajului de programare Go privind Type assertion
Conferința lui Marcel van Lohuizen la dotGo 2019 – Valori de eroare Go 2 astăzi
Notele de lansare Go 1.13

LogRocket: Vizibilitate completă în aplicațiile dvs. web

LogRocket este o soluție de monitorizare a aplicațiilor frontend care vă permite să redați problemele ca și cum s-ar fi întâmplat în propriul browser. În loc să ghiciți de ce se întâmplă erorile sau să cereți utilizatorilor capturi de ecran și descărcări de jurnal, LogRocket vă permite să redați sesiunea pentru a înțelege rapid ce a mers prost. Funcționează perfect cu orice aplicație, indiferent de framework, și are plugin-uri pentru a înregistra contextul suplimentar de la Redux, Vuex și @ngrx/store.

În plus față de înregistrarea acțiunilor și stării Redux, LogRocket înregistrează jurnalele de consolă, erorile JavaScript, stacktraces, cererile/răspunsurile de rețea cu anteturi + corpuri, metadatele browserului și jurnalele personalizate. De asemenea, instrumentează DOM pentru a înregistra HTML și CSS din pagină, recreând videoclipuri pixel-perfect chiar și pentru cele mai complexe aplicații cu o singură pagină.

Încercați-l gratuit.

Lasă un răspuns

Adresa ta de email nu va fi publicată.