TODO Error Handling
catch panics
Simple example how catch a panic
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import (
"fmt"
)
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("recover", r)
}
}()
f()
}
func f() {
fmt.Println("a")
panic("foo")
fmt.Println("b")
}
|
wrap errors
Using the %w
directive:
1
2
3
|
if err != nil {
return fmt.Errorf("bar failed: %w", err)
}
|
Using the %v
directive:
1
2
3
|
if err != nil {
return fmt.Errorf("bar failed: %v", err)
}
|
Code Snippet 1:
In this case the error is not wraped. It's transformed into another error, to add context. The original source error is no longer available.
check is an error is of a certain type (errors.As)
In summary, if we rely on Go 1.13 error wrapping, we must use errors.As to check whether
an error is a specific type. This way, regardless of whether the error is returned
directly by the function we call or wrapped inside an error, errors.As will be able to
recursively unwrap our main error and see if one of the errors is a specific type.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func handler(w http.ResponseWriter, r *http.Request) {
// Get transaction ID
amount, err := getTransactionAmount(transactionID)
if err != nil {
if errors.As(err, &transientError{}) {
http.Error(w, err.Error(),
http.StatusServiceUnavailable)
} else {
http.Error(w, err.Error(),
http.StatusBadRequest)
}
return
}
// Write response
}
|
errors.Is
In summary, if we use error wrapping in our application with the %w directive and
fmt.Errorf, checking an error against a specific value should be done using errors.Is
instead of ==. Thus, even if the sentinel error is wrapped, errors.Is can recursively
unwrap it and compare each error in the chain against the provided value.
1
2
3
4
5
6
7
8
9
10
11
12
|
import "errors"
var ErrFoo = errors.New("foo")
err := query()
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
// ...
} else {
// ...
}
}
|
handling defer errors
Some example (in this example the error in the defer function is completely ignored):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
func getBalance(db *sql.DB, clientID string) (
balance float32, err error) {
rows, err := db.Query(query, clientID)
if err != nil {
return 0, err
}
defer func() {
err = rows.Close()
}()
if rows.Next() {
err := rows.Scan(&balance)
if err != nil {
return 0, err
}
return balance, nil
}
// ...
}
|
Final version:
1
2
3
4
5
6
7
8
9
10
|
defer func() {
closeErr := rows.Close()
if err != nil {
if closeErr != nil {
log.Printf("failed to close rows: %v", err)
}
return
}
err = closeErr
}()
|