type error interface { Error() string }与此同时,Go 的 errors 包实现了这个接口:调用 errors.New() 就会返回 error接口的实现类 errorString,通过源码我们看到 errorString 的底层就是一字符串,可真是 "省事" 啊。
func TestErrString(t *testing.T) { var error1 = errors.New("error") var error2 = errors.New("error") if error1 != error2 { log.Println("error1 != error2") } } ---------------------代码运行结果-------------------------- === RUN TestXXXX 2022/03/25 22:05:40 error1 != error2这种创建 error 的方式很常见,在 Go 源码以及三方包源码中大量出现。
var EOF = errors.New("EOF") var ErrUnexpectedEOF = errors.New("unexpected EOF") var ErrNoProgress = errors.New("multiple Read calls return no data or error")然而很可惜的是,Go 的 error 设计并不能满足所有场景。
type PathError struct { Op string Path string Err error }封装 error 堆栈信息
SERVICE ERROR 2022-03-25T16:32:10.687+0800!!! Error 1406: Data too long for column 'content' at row 1我们可以使用 github.com/pkg/error包解决这个问题,这个包提供了 errors.withStack()方法将堆栈信息封装进 error:
注意:目前我们只讨论在调用 Go 内置库和第三方库时产生的 error 的最佳处理实践,业务层面的错误处理是一个单独的话题,以后单独写一篇聊。
// CountLines() 实现了"读取内容的行数"功能 func CountLines(r io.Reader) (int, error) { var ( br = bufio.NewReader(r) lines int err error ) for { _, err := br.ReadString('\n') lines++ if err != nil { break } } if err != io.EOF { return 0, nilsadwawa } return lines, nil } // 堆代码 duidaima.com // 利用 bufio.scan() 简化 error 的处理: func CountLinesGracefulErr(r io.Reader) (int, error) { sc := bufio.NewScanner(r) lines := 0 for sc.Scan() { lines++ } return lines, sc.Err() }源码解读:bufio.NewScanner() 返回一个 Scanner 对象,结构体内部包含了 error 类型,调用 Err() 方法即可返回封装好的 error。
type Scanner struct { r io.Reader // The reader provided by the client. split SplitFunc // The function to split the tokens. maxTokenSize int // Maximum size of a token; modified by tests. token []byte // Last token returned by split. buf []byte // Buffer used as argument to split. start int // First non-processed byte in buf. end int // End of data in buf. err error // Sticky error. empties int // Count of successive empty tokens. scanCalled bool // Scan has been called; buffer is in use. done bool // Scan has finished. } func (s *Scanner) Err() error { if s.err == io.EOF { return nil } return s.err }利用上面学到的思路,我们可以自己实现一个 errWriter 对象,简化对 error 的处理:
type Header struct { Key, Value string } type Status struct { Code int Reason string } // WriteResponse()函数实现了"构建HttpResponse"功能 func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error { _, err := fmt.Fprintf(w, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason) if err != nil { return err } for _, h := range headers { _, err := fmt.Fprintf(w, "%s: %s\r\n", h.Key, h.Value) if err != nil { return err } } if _, err := fmt.Fprintf(w, "\r\n"); err != nil { return err } _, err = io.Copy(w, body) return err } // 优化错误处理 type errWriter struct { io.Writer err error } func (e *errWriter) Write(buf []byte) (n int, err error) { if e.err != nil { return 0, e.err } n, e.err = e.Writer.Write(buf) return n, nil } func WriteResponseGracefulErr(w io.Writer, st Status, headers []Header, body io.Reader) error { ew := &errWriter{w, nil} fmt.Fprintf(ew, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason) for _, h := range headers { fmt.Fprintf(ew, "%s: %s\r\n", h.Key, h.Value) } fmt.Fprintf(w, "\r\n") io.Copy(ew, body) return ew.err }
// 与errors.Wrap()行为相反 // 获取err链中的底层err func Unwrap(err error) error { u, ok := err.(interface { Unwrap() error }) if !ok { return nil } return u.Unwrap() }
errors.Is()
在 1.13 版本之前,我们可以用 err == targetErr 判断 err 类型。
errors.Is() 是其增强版:error 链上的任一err == targetErr,即 return true,我们写个 demo 跑一下:
var errNoRows = errors.New("no rows") // 模仿sql库返回一个errNoRows func sqlExec() error { return errNoRows } func service() error { err := sqlExec() if err != nil { return errors.WithStack(err) // 包装errNoRows } return nil } func TestErrIs(t *testing.T) { err := service() // errors.Is递归调用errors.UnWrap,命中err链上的任意err即返回true if errors.Is(err, errNoRows) { log.Println("===== errors.Is() succeeded =====") } //err经errors.WithStack包装,不能通过 == 判断err类型 if err == errNoRows { log.Println("err == errNoRows") } } -------------------------------代码运行结果---------------------------------- === RUN TestErrIs 2023/10/25 18:35:00 ===== errors.Is() succeeded =====例子解读:因为使用 errors.WithStack 包装了 sqlError,sqlError 位于 error 链的底层,上层的 error 已经不再是 sqlError 类型,所以使用 == 无法判断出底层的 sqlError
2.兼容自定义 error 类型。
func Is(err, target error) bool { if target == nil { return err == target } isComparable := reflectlite.TypeOf(target).Comparable() for { if isComparable && err == target { return true } // 自定义的 error 可以实现`Is接口`自定义 error 类型判断逻辑 if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) { return true } if err = Unwrap(err); err == nil { return false } } }下面我们尝试使用 erros.Is() 识别自定义 error 类型:
type errNoRows struct { Desc string } func (e errNoRows) Unwrap() error { return e } func (e errNoRows) Error() string { return e.Desc } func (e errNoRows) Is(err error) bool { return reflect.TypeOf(err).Name() == reflect.TypeOf(e).Name() } // 模仿sql库返回一个errNoRows func sqlExec() error { return &errNoRows{"Kaolengmian NB"} } func service() error { err := sqlExec() if err != nil { return errors.WithStack(err) } return nil } func serviceNoErrWrap() error { err := sqlExec() if err != nil { return fmt.Errorf("sqlExec failed.Err:%v", err) } return nil } func TestErrIs(t *testing.T) { err := service() if errors.Is(err, errNoRows{}) { log.Println("===== errors.Is() succeeded =====") } } -------------------------------代码运行结果---------------------------------- === RUN TestErrIs 2023/10/25 18:35:00 ===== errors.Is() succeeded =====
// errors.WithStack 包装了 sqlError // sqlError 位于 error 链的底层,上层的 error 已经不再是 sqlError 类型 // 使用类型断言无法判断出底层的 sqlError,而使用 errors.As() 函数可以判断出底层的 sqlError type sqlError struct { error } func (e *sqlError) IsNoRows() bool { t, ok := e.error.(ErrNoRows) return ok && t.IsNoRows() } type ErrNoRows interface { IsNoRows() bool } // 返回一个sqlError func sqlExec() error { return sqlError{} } // errors.WithStack包装sqlError func service() error { err := sqlExec() if err != nil { return errors.WithStack(err) } return nil } func TestErrAs(t *testing.T) { err := service() // 递归使用errors.UnWrap,只要Err链上有一种Err满足类型断言,即返回true sr := &sqlError{} if errors.As(err, sr) { log.Println("===== errors.As() succeeded =====") } // 经errors.WithStack包装后,不能通过类型断言将当前Err转换成底层Err if _, ok := err.(sqlError); ok { log.Println("===== type assert succeeded =====") } } ----------------------------------代码运行结果-------------------------------------------- === RUN TestErrAs 2023/10/25 18:09:02 ===== errors.As() succeeded =====