闽公网安备 35020302035485号
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 类型,所以使用 == 无法判断出底层的 sqlError2.兼容自定义 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 =====