例如在代码中,经常会看到:
1 2 3 4
| xxx, err := fun() if err != nil { xxx }
|
自定义错误
返回的error
是一个接口
1 2 3
| type error interface { Error() string }
|
实现了这个接口即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| type myError struct { describe string }
func (m myError) Error() string { return m.describe }
func mayError(e bool) error { err := myError{} if e { err.describe = "get error" } return err }
|
但是在验证过程中需要注意,这样返回的interface
有类型,因此无论是否有报错,都无法与nil
做判断。
错误接口类型断言
通过类型断言,可以判断错误是否是自定义的错误
1 2 3 4 5 6
| switch err.(type) { case myError: fmt.Println("this is my Error") default: fmt.Println("unknown error") }
|
error接口
内置的errors
包中实现Error
接口
1 2 3 4 5 6 7 8 9 10 11
| func New(text string) error { return &errorString{text} }
type errorString struct { s string }
func (e *errorString) Error() string { return e.s }
|
例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| type errorString string
func (e errorString)Error()string { return string(e) }
func NewError(text string ) error { return errorString(text) }
var ErrNameType = NewError("EOF") var ErrStructType = errors.New("EOF")
func main() { if ErrNameType == NewError("EOF") { fmt.Println("Named Type Error") }
if ErrStructType == errors.New("EOF") { fmt.Println("Struct Type Error") } }
|
又或者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| type errorString struct { e string }
func (e errorString)Error()string { return e.e }
func NewError(text string ) error { return errorString{e:text} }
var ErrNameType = NewError("EOF") var ErrStructType = errors.New("EOF")
func main() { if ErrNameType == NewError("EOF") { fmt.Println("Named Type Error") }
if ErrStructType == errors.New("EOF") { fmt.Println("Struct Type Error") } }
|
errors
包中的方法
解包装
1
| func Unwrap(err error) error
|
例如
1 2 3 4
| err1 := errors.New("error1") err2 := fmt.Errorf("error2: [%w]", err1) fmt.Println(err2) fmt.Println(errors.Unwrap(err2))
|
输出
源码包的 src/bufio/bufio.go
中,可以看到使用errors
包
1 2 3 4 5 6
| var ( ErrInvalidUnreadByte = errors.New("bufio: invalid use of UnreadByte") ErrInvalidUnreadRune = errors.New("bufio: invalid use of UnreadRune") ErrBufferFull = errors.New("bufio: buffer full") ErrNegativeCount = errors.New("bufio: negative count") )
|
error的命名,建议是包名: 报错信息
判断这个是不是特定的某个error
1
| func Is(err error, target error) bool
|
例如
1 2
| fmt.Println(errors.Is(err1, err2)) // false fmt.Println(errors.Is(err2, err1)) // true
|
类型转换为特定的error
1
| func As(err error, target any) bool
|
例如
1 2 3 4 5 6 7 8
| if _, err := os.Open("non-existing"); err != nil { var pathError *fs.PathError if errors.As(err, &pathError) { fmt.Println("Failed at path:", pathError.Path) } else { fmt.Println(err) } }
|
输出
1
| Failed at path: non-existing
|
fs
包中的PathError
1 2 3 4 5 6 7
| type PathError struct { Op string Path string Err error }
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
|
第三方error包
golang
中error
的处理比较难以处理好,如果打印报错,为后续排查做定位,抛给上层,但是上层依然需要打印日志,此时日志打印就会出现一堆报错的情况。
另外,自定义错误在调用者判断时,由于需要判断是否一致,因此无法二次包装。
error
处理遵循:
- 能
handler
则不往上抛,不能handler
,则由上层处理,这样就可以避免函数处理错误,调用者再处理一遍
- 错误要被日志记录
- 能
handler
错误,则需要保证100%完整性
github.com/pkg/errors
提供了一种错误处理方案,可以封装error
,获取原生error
,以及打印error的调用栈。相对于原生errors包,增加了错误堆栈信息的打印,在这方面上,建议使用pkg/errors
。
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| package main
import ( "fmt" "github.com/pkg/errors" "io" "log" "os" )
var ( NoSuchFileError = errors.New("my no such file") )
func Write(w io.Writer, buf []byte) error { _, err := w.Write(buf) return errors.Wrap(err, "write failed") }
func WriteConfig(file string, buf []byte) error { f, err := os.Open(file) if err != nil { err = errors.Wrap(NoSuchFileError, "open file failed") return err } err = Write(f, buf) return nil }
func main() { conf := []byte("k1 = v1") err := WriteConfig("./config.ini", conf) if err != nil { log.Printf("root error: %v\n", err) if errors.Cause(err) == NoSuchFileError { fmt.Println("This is my error") } log.Printf("write config failed: %+v\n", err) } }
|
panic
error
指返回一般性错误,程序可以继续正常执行,panic
函数返回的让程序崩溃的错误,程序中断运行。例如数组越界、空指针引用,都会引起panic
异常。panic
函数接受任何参数。
内建函数
panic
时,会将堆栈和goroutine
信息输出到控制台
1 2 3 4 5
| goroutine 1 [running]: main.testA(...) xxx/panic/main.go:6 main.main() xxx/panic/main.go:15 +0x30
|
panic
之后,程序崩溃退出,后续代码就不会被执行。
结合defer
defer
语句在return
之前,panic
之后运行,可以通过recover
捕获到panic
的内容
内建函数:
例如:
1 2
| defer fmt.Println("test a") panic("this is a panic")
|
恢复机制recover
recover
函数只有在defer
调用函数中才有效
1 2 3 4 5
| defer func() { r := recover() fmt.Println(r) }() panic("panic")
|
没有recover
没有异常也会捕获,捕获为空,因此使用方式一般会判断捕获的信息
1 2 3 4 5
| defer func() { if err := recover(); err != nil { fmt.Println(err) } }()
|