字符串类型的错误

// simple string-based error
err1 := errors.New("math: square root of negative number")

// with formatting
err2 := fmt.Errorf("math: square root of negative number %g", x)

自定义错误

自定义错误可携带额外信息,比如错误码等。

先定义一个错误码枚举:

// 定义枚举前需要先定义类型
type Status int

// 定义枚举
const (
	InvalidLogin = iota + 1
	NotFound
)

然后使用上面的枚举来实现自定义错误:

type StatusErr struct {
	Status  Status
	Message string
}

func (se StatusErr) Error() string {
	return se.Message
}

测试:

func foo() error {
	id := 1000
	return StatusErr{
		Status:  InvalidLogin,
		Message: fmt.Sprintf("status error with user id %d", id),
	}
}

func main() {
	err := foo()
	switch e := err.(type) {
	case StatusErr:
		fmt.Println("error with status code:", e.Status)
	default:
		fmt.Println(e)
	}
}

// error with status code: 1

上面示例代码中,注意两点:

error

哨兵错误

Err
ErrorFormat
func main() {
	data := []byte("blahblah...")
	notAZipFile := bytes.NewReader(data)
	_, err := zip.NewReader(notAZipFile, int64((len(data))))
	if err == zip.ErrFormat {
		fmt.Println("invalid file type!")
	}
}
error
// /xxx/go/1.16.5/libexec/src/archive/zip/reader.go

var (
	ErrFormat    = errors.New("zip: not a valid zip file")
	ErrAlgorithm = errors.New("zip: unsupported compression algorithm")
	ErrChecksum  = errors.New("zip: checksum error")
)

Error 的包装

接收到 error 后,可添加点信息在上面,再将其返回出去。这样可形成一条错误链。

fmt.Errorf%werror
func bar() error {
	err := foo()
	return fmt.Errorf("some more info on err, the source err is :%w", err)
}
%v
errors.Unwrap
func main() {
	err := bar()
	if err != nil {
		fmt.Println(err)
		if sourceErr := errors.Unwrap(err); sourceErr != nil {
			fmt.Println(sourceErr)
		}
	}
}

// some more info on err, the source err is :status error with user id 1000
// status error with user id 1000
Unsrap
type StatusError struct {
		Status Status
		Message string
+		err error
	}
	
+	func (se StatusError) Unwrap()error{
+		return se.err
+	}
IsAs
[errors.Is](http://errors.Is)[errors.As](http://errors.As)
func fileChecker(name string) error {
	f, err := os.Open(name)
	if err != nil {
		return fmt.Errorf("in fileChecker: %w", err)
	}
	defer f.Close()
	return nil
}

func main() {
	err := fileChecker("xxx.txt")
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			fmt.Println("file not exit")
		}
	}
}
errors.IsUnwrapIs
type MyErr struct {
	Status int
}

func (me MyErr)Error()string{
	return fmt.Sprintf("error code: %d",me.Status)
}

func (me MyErr) Is(target error)bool{
	if me2,ok:=target.(MyErr);ok{
		return  reflect.DeepEqual(me,me2)
	}
	return false
}
As
func main() {
	err:=AFuncThatReturnsAnErr()
	var myErr MyErr
	if errors.As(err,&myErr){
		fmt.Println(myErr.Code)
	}
}
err.(type)As
As

总结:

[errors.Is](http://errors.Is)[errors.As](http://errors.As)
defer

函数中如果有多个地方都需要进行同样的错误包装,比如像下面这样:

func foo(i int)(string,error)  {
	_,err:=f1(i)
	if err=nil{
		return "",fmt.Errorf("in foo:%w",err)
	}
	_,err:=f2(i)
	if err=nil{
		return "",fmt.Errorf("in foo:%w",err)
	}
	_,err:=f3(i)
	if err=nil{
		return "",fmt.Errorf("in foo:%w",err)
	}
	return "",nil
}
defer
func foo(i int)(_ string,err error)  {
	defer func() {
		if err!=nil{
			 err=fmt.Errorf("in foo:%w",err)
		}
	}()

	_,err:=f1(i)
	if err=nil{
		return "",err
	}
	_,err:=f2(i)
	if err=nil{
		return "",err
	}
	return f3(i)
}
deferdefer

发生 panic 及恢复

Go 程序中发生异常时会生成 panic,比如试图访问超出 slice 边界的元素,内存溢出等。

除了 runtime 会发生 panic,程序中也可根据需要来生成:

func genPanic(msg string) {
	panic(msg)
}

func main() {
	genPanic("blah")
}

运行结果:

$ go run main.go
panic: blah

goroutine 1 [running]:
main.genPanic(...)
        /xxx/main.go:4
main.main()
        /xxx/main.go:8 +0x50
exit status 2
make: *** [run] Error 1
recoverdefer
func div60(i int) {
	defer func() {
		if v := recover(); v != nil {
			fmt.Println(v)
		}
	}()
	fmt.Println(60 / i)
}

func main() {
	for _, val := range []int{1, 2, 0, 6} {
		div60(val)
	}
}

运行结果:

go run main.go
60
30
runtime error: integer divide by zero
10
os.Exit(1)
fmt.Printf%+v-trimpath