NOTE
1.3 error
The distinction between exception and error in Go, how errors are used and handled, their implementation, and practical patterns.
This is a historical learning note and may contain outdated or incomplete understanding.
1. exception and error
Golang divides errors into exception and error.
- exception refers to bugs inside the program or unexpected situations
- error refers to expected situations that can be checked through a return value
2. Usage
2.1. exception
- Internal bugs are caught through recovery and converted into errors for handling
- Unexpected situations terminate the program through panic
2.2. error
- Construct an err through
fmt.ErrorOforerrs.New
func TestError1(t *testing.T) {
// The passed string should start with a lowercase letter and should not end with punctuation
fmt.Println(errors.New("network cannot reachable"))
fmt.Println(fmt.Errorf("value %s too long", "aaaaaaaaaaa"))
}
// output
network cannot reachable
value aaaaaaaaaaa too long
- How to handle an error
- Use
github.com/pkg/errors - Principles
- Do not handle it repeatedly. For example, the lower layer catches an error and logs it, then throws it upward again
- Do not only check the error. For example, when the lower layer returns an error upward, the upper layer should not return it unchanged; it should add information
- Do not determine a specific error from the error string. Although you can determine a concrete type through type conversion, defining error codes is more recommended
- Use
2.2.1. Example
- main.go
package main
import (
"database/sql"
"fmt"
"github.com/pkg/errors"
"log"
"test/errors/service"
)
func main() {
err := service.FindUser(1)
if errors.Cause(err) == sql.ErrNoRows {
fmt.Printf("data not found, %v\n", err)
return
}
if err != nil {
log.Fatalf("%+v\n", err)
return
}
log.Println("---------------------------")
}
// output
// when it is 0
data not found, service finduser failed: dao find user faile
d: sql: no rows in result set
// when it is 1
val cannot be 1
test/errors/dao.SelectOne
C:<USER_HOME>/code/go/test/errors/dao/dao.go:12
test/errors/service.FindUser
C:<USER_HOME>/code/go/test/errors/service/service.go:9
main.main
C:<USER_HOME>/code/go/test/errors/errors.go:32
runtime.main
C:/software/Go/src/runtime/proc.go:203
runtime.goexit
C:/software/Go/src/runtime/asm_amd64.s:1357
service finduser failed
// when it is 2
---------------------------
- service.go
package service
import (
"github.com/pkg/errors"
"test/errors/dao"
)
func FindUser(val int) error {
return errors.WithMessage(dao.SelectOne(val), "service finduser failed")
}
- dao.go
package dao
import (
"database/sql"
"github.com/pkg/errors"
)
func SelectOne(val int) error {
if val == 0 {// wrap the underlying error
return errors.Wrap(sql.ErrNoRows, "dao find user failed")
} else if val == 1 {// custom error
return errors.Errorf("val cannot be %d", val)
}
return nil
}
3. Implementation
3.1. Data Structure
type error interface {
Error() string
}
3.2. Methods
// src/errors/errors.go
func New(text string) error {
// Create an errorString object
return &errorString{text}
}
// Contains only the error message
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
func Errorf(format string, a ...interface{}) error {
// Format the string first
p := newPrinter()
p.wrapErrs = true
p.doPrintf(format, a)
s := string(p.buf)
var err error
if p.wrappedErr == nil {
// Then create it through errors.New
err = errors.New(s)
} else {
err = &wrapError{s, p.wrappedErr}
}
p.free()
return err
}
4. Best Practices
go get -u golang.org/x/xerrors
- main
func main() {
err := service.FindUser(1)
/*
2022/10/30 16:08:16 predefined error: query user failed:
test/errors/service.FindUser
C:<USER_HOME>/code/go/test/errors/service/service.go:11
- check val [0] failed:
test/errors/dao.checkVal
C:<USER_HOME>/code/go/test/errors/dao/dao.go:23
- invalid val
exit status 1
*/
if errors.Is(err, dao.ErrorInvalidVal) {
log.Fatalf("predefined error: %+v\n", err)
}
/*
2022/10/30 16:09:15 non-predefined error: query user failed:
test/errors/service.FindUser
C:<USER_HOME>/code/go/test/errors/service/service.go:11
- record not found for val [1]:
test/errors/dao.SelectOne
C:<USER_HOME>/code/go/test/errors/dao/dao.go:16
exit status 1
*/
if err != nil {
log.Fatalf("non-predefined error: %+v\n", err)
}
log.Println("-------------success--------------")
}
- service
func FindUser(val int) error {
if err := dao.SelectOne(val); err != nil {
return xerrors.Errorf("query user failed: %w", err)
}
return nil
}
- dao
var ErrorInvalidVal = errors.New("invalid val")
func SelectOne(val int) error {
if err := checkVal(val); err != nil {
return err
}
if val == 1 {
return xerrors.Errorf("record not found for val [%v]", val)
}
return nil
}
func checkVal(val int) error {
if val == 0 {
return xerrors.Errorf("check val [%d] failed: %w", val, ErrorInvalidVal)
}
return nil
}
Discussion
Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub