Go
Obsługa błędów
Szukaj…
Wprowadzenie
W Go nieoczekiwane sytuacje są obsługiwane przy użyciu błędów , a nie wyjątków. To podejście jest bardziej podobne do podejścia C przy użyciu errno niż do języka Java lub innych języków zorientowanych obiektowo, z ich blokami try / catch. Jednak błąd nie jest liczbą całkowitą, ale interfejsem.
Funkcja, która może zawieść, zwykle zwraca błąd jako ostatnią zwracaną wartość. Jeśli ten błąd nie jest zerowy , coś poszło nie tak i wywołujący funkcję powinien podjąć odpowiednie działanie.
Uwagi
Zwróć uwagę, że w Go nie zgłaszasz błędu. Zamiast tego zwracasz błąd w przypadku awarii.
Jeśli funkcja może zawieść, ostatnia zwracana wartość jest zazwyczaj typem error
.
// This method doesn't fail
func DoSomethingSafe() {
}
// This method can fail
func DoSomething() (error) {
}
// This method can fail and, when it succeeds,
// it returns a string.
func DoAndReturnSomething() (string, error) {
}
Tworzenie wartości błędu
Najprostszym sposobem na utworzenie błędu jest użycie pakietu errors
.
errors.New("this is an error")
Jeśli chcesz dodać dodatkowe informacje do błędu, pakiet fmt
zapewnia również przydatną metodę tworzenia błędów:
var f float64
fmt.Errorf("error with some additional information: %g", f)
Oto pełny przykład, w którym błąd jest zwracany z funkcji:
package main
import (
"errors"
"fmt"
)
var ErrThreeNotFound = errors.New("error 3 is not found")
func main() {
fmt.Println(DoSomething(1)) // succeeds! returns nil
fmt.Println(DoSomething(2)) // returns a specific error message
fmt.Println(DoSomething(3)) // returns an error variable
fmt.Println(DoSomething(4)) // returns a simple error message
}
func DoSomething(someID int) error {
switch someID {
case 3:
return ErrThreeNotFound
case 2:
return fmt.Errorf("this is an error with extra info: %d", someID)
case 1:
return nil
}
return errors.New("this is an error")
}
Tworzenie niestandardowego typu błędu
W Go błąd jest reprezentowany przez dowolną wartość, która może opisywać się jako ciąg znaków. Każdy typ, który implementuje wbudowany interfejs error
, jest błędem.
// The error interface is represented by a single
// Error() method, that returns a string representation of the error
type error interface {
Error() string
}
Poniższy przykład pokazuje, jak zdefiniować nowy typ błędu za pomocą literału złożonego ciągu.
// Define AuthorizationError as composite literal
type AuthorizationError string
// Implement the error interface
// In this case, I simply return the underlying string
func (e AuthorizationError) Error() string {
return string(e)
}
Teraz mogę użyć mojego niestandardowego typu błędu jako błędu:
package main
import (
"fmt"
)
// Define AuthorizationError as composite literal
type AuthorizationError string
// Implement the error interface
// In this case, I simply return the underlying string
func (e AuthorizationError) Error() string {
return string(e)
}
func main() {
fmt.Println(DoSomething(1)) // succeeds! returns nil
fmt.Println(DoSomething(2)) // returns an error message
}
func DoSomething(someID int) error {
if someID != 1 {
return AuthorizationError("Action not allowed!")
}
// do something here
// return a nil error if the execution succeeded
return nil
}
Zwracanie błędu
W Go nie zgłaszasz błędu. Zamiast tego zwracasz error
w przypadku awarii.
// This method can fail
func DoSomething() error {
// functionThatReportsOK is a side-effecting function that reports its
// state as a boolean. NOTE: this is not a good practice, so this example
// turns the boolean value into an error. Normally, you'd rewrite this
// function if it is under your control.
if ok := functionThatReportsOK(); !ok {
return errors.New("functionThatReportsSuccess returned a non-ok state")
}
// The method succeeded. You still have to return an error
// to properly obey to the method signature.
// But in this case you return a nil error.
return nil
}
Jeśli metoda zwraca wiele wartości (a wykonanie może się nie powieść), standardową konwencją jest zwrócenie błędu jako ostatniego argumentu.
// This method can fail and, when it succeeds,
// it returns a string.
func DoAndReturnSomething() (string, error) {
if os.Getenv("ERROR") == "1" {
return "", errors.New("The method failed")
}
s := "Success!"
// The method succeeded.
return s, nil
}
result, err := DoAndReturnSomething()
if err != nil {
panic(err)
}
Obsługa błędu
Błędy w Go mogą być zwracane z wywołania funkcji. Konwencja jest taka, że jeśli metoda może zawieść, ostatni zwrócony argument jest error
.
func DoAndReturnSomething() (string, error) {
if os.Getenv("ERROR") == "1" {
return "", errors.New("The method failed")
}
// The method succeeded.
return "Success!", nil
}
Używasz wielu przypisań zmiennych, aby sprawdzić, czy metoda się nie powiodła.
result, err := DoAndReturnSomething()
if err != nil {
panic(err)
}
// This is executed only if the method didn't return an error
fmt.Println(result)
Jeśli nie jesteś zainteresowany błędem, możesz go po prostu zignorować, przypisując go do _
.
result, _ := DoAndReturnSomething()
fmt.Println(result)
Oczywiście ignorowanie błędu może mieć poważne konsekwencje. Dlatego generalnie nie jest to zalecane.
Jeśli masz wiele wywołań metod, a jedna lub więcej metod w łańcuchu może zwrócić błąd, należy propagować błąd na pierwszy poziom, który może go obsłużyć.
func Foo() error {
return errors.New("I failed!")
}
func Bar() (string, error) {
err := Foo()
if err != nil {
return "", err
}
return "I succeeded", nil
}
func Baz() (string, string, error) {
res, err := Bar()
if err != nil {
return "", "", err
}
return "Foo", "Bar", nil
}
Odzyskiwanie z paniki
Częstym błędem jest deklarowanie wycinka i rozpoczęcie od niego żądania indeksów bez inicjowania go, co prowadzi do paniki „indeks poza zakresem”. Poniższy kod wyjaśnia, jak odzyskać panikę bez wychodzenia z programu, co jest normalnym zachowaniem w przypadku paniki. W większości sytuacji zwracanie błędu w ten sposób zamiast wychodzenia z programu w panice jest przydatne tylko do celów programistycznych lub testowych.
type Foo struct {
Is []int
}
func main() {
fp := &Foo{}
if err := fp.Panic(); err != nil {
fmt.Printf("Error: %v", err)
}
fmt.Println("ok")
}
func (fp *Foo) Panic() (err error) {
defer PanicRecovery(&err)
fp.Is[0] = 5
return nil
}
func PanicRecovery(err *error) {
if r := recover(); r != nil {
if _, ok := r.(runtime.Error); ok {
//fmt.Println("Panicing")
//panic(r)
*err = r.(error)
} else {
*err = r.(error)
}
}
}
Zastosowanie oddzielnej funkcji (zamiast zamknięcia) pozwala na ponowne użycie tej samej funkcji w innych funkcjach podatnych na panikę.