• Go Error 错误处理总结


    Go Error

    一. 设计理念

    • 简单
    • 考虑成功而不是只有成功
    • 没有隐藏的控制流
    • 完全交给调用者控制 error
    • Error are values(Rob Pike)

    二. 错误与异常

    go 中的错误处理主要使用到errorpanic,其中 error 使用居多

    error

    error 主要有以下几个特点

    • 不会造成程序运行结束
    • error 仅仅是一个返回值
    • 将问题抛给调用者处理,可以不处理,但建议处理
    • 明确告诉调用者需要处理错误,一定程度上确保调用者会处理(强烈建议)

    panic

    panic 主要有以下几个特点

    • 调用者未处理时可能会造成整个程序运行结束
    • 隐式的,调用者不知道被调用方法是否会触发 panic,所以无法确保调用者一定会处理
    • 通常用作较大错误,程序无法执行(fatal error)时抛出(慎用)

    三. 几种错误实践方案

    1. Sentinel Error(预定义错误)

    (1. 标准库中使用到预定义错误的例子

    尽可能提前定义好所有需要的错误类型及错误代码,方便业务中使用及判断

    // go/1.17.5/libexec/src/bufio/bufio.go
    
    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")
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    (2. 使用 error 对象进行 == 判断,而非 error 的内容

    error 的内容是为了方便调试或者日志记录,而非方便程序控制

    仅使用错误内容判断错误,而进行错误逻辑处理可能造成隐患,因为可能存在相同错误内容,但不同错误对象的情况。errors.New() 时也会返回当前对象的指针,以确保每个新建一个对象都是唯一的。

    package errors
    
    // New returns an error that formats as the given text.
    // Each call to New returns a distinct error value even if the text is identical.
    func New(text string) error {
    	return &errorString{text}
    }
    
    // errorString is a trivial implementation of error.
    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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wbfuhipu-1655983351596)(/Users/zsl/Library/Application Support/typora-user-images/image-20220623190257837.png)]

    (3. 预定义 error,需要放置在公共包中,且需要有文档描述

    Sentinel Error 通常被用作底层工具开发使用,第三方包如果大量使用可能会导致循环导入问题(import loop)

    需要放置在公共包中,且需要有文档描述。(注意:阅读源码发现方法的返回值仍然是 error

    // ErrShortWrite means that a write accepted fewer bytes than requested (写入的字节数少于请求的)
    // but failed to return an explicit error. 
    var ErrShortWrite = errors.New("short write")
    
    // errInvalidWrite means that a write returned an impossible count. (写入的返回是无法计数的)
    var errInvalidWrite = errors.New("invalid write result")
    
    // ErrShortBuffer means that a read required a longer buffer than was provided.(读取的字节数大于提供的)
    var ErrShortBuffer = errors.New("short buffer")
    ......
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2. Error Types(错误类型)

    (1. 使用自己封装的 type 作为 error 使用

    // PathError records an error and the operation and file path that caused it.
    type PathError struct {
    	Op   string
    	Path string
    	Err  error
    }
    
    func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    (2. 判断错误时可使用断言的方式做类型转换

    // underlyingError returns the underlying error for known os error types.
    func underlyingError(err error) error {
    	switch err := err.(type) {
    	case *PathError:
    		return err.Err
    	case *LinkError:
    		return err.Err
    	case *SyscallError:
    		return err.Err
    	}
    	return err
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3. Opaque error(模糊错误)

    (1. 仅仅判断 error 是否为 nil,而不关心具体是什么 error

    (2. assert errors for behaviour, no type(可暴露行为,而不暴露类型)

    4. Warp error(包装错误)

    将错误向上抛,在某个地方统一处理

    • 仅处理错误一次
    • 需要使用预定义错误、类型断言、行为判定时不能使用 fmt.Errorf() 做转换
    • 通过日志记录错误是需要记录详细信息(即仅凭日志就可以粗略看出具体问题,位置信息、参数信息、错误信息等)

    使用github.com/pkg/errors 做错误包装,

    • 程序中出现错误使用 errors.New() 或者 errors.Errorf() 返回错误
    • 调用其他包,返回错误时可直接返回
    • 调用其他库报错时,使用error.Warp()或者error.Warpf()保存堆栈信息(适用于标准库)
    • 程序顶部使用%+v方式打印错误堆栈信息
    • errors.Cause 打印原始错误,可以使用预定义错误、类型断言、行为判定等配合使用
    • 业务库中可以使用,封装库避免使用 Wrap error ,否则可能出现两层堆栈信息

    四. 错误处理写法优化策略

    1. 错误流缩进

    indented flow is for errors

    无错误的代码正常执行,而不是缩进的代码(推荐判断 err != nil不推荐err == nil

    func test() {
    	a, err := funca()
    	if err != nil { //推荐
    		//do error handling
    	}
    	//do something
    
    	if err == nil { //不推荐
    		//do something
    	}
    	// do error handling
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2. 避免无效判断

    eliminate error handling by eliminate errors

    避免无效的错误判断

    func test() error { //不推荐
    	err := funca()
    	if err != nil {
    		return err;
    	}
    	return nil;
    }
    
    func test() error { //推荐
    	return funca()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3. 收集错误

    可以通过错误记录或者收集的方式减少错误判断

    //go/1.17.5/libexec/src/bufio/scan.go
    // advance consumes n bytes of the buffer. It reports whether the advance was legal.
    func (s *Scanner) advance(n int) bool {
    	if n < 0 {
    		s.setErr(ErrNegativeAdvance)
    		return false
    	}
    	if n > s.end-s.start {
    		s.setErr(ErrAdvanceTooFar)
    		return false
    	}
    	s.start += n
    	return true
    }
    
    // setErr records the first error encountered.
    func (s *Scanner) setErr(err error) {
    	if s.err == nil || s.err == io.EOF {
    		s.err = err
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    未使用错误收集方式

    type Header struct {
    	Key, Value string
    }
    type Status struct {
    	Code   int
    	Reason string
    }
    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.Fprint(w, "\r\n"); err != nil {
    		return err
    	}
    	_, err = io.Copy(w, body)
    	return err
    }
    
    • 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 errWriter struct {
    	io.Writer
    	err error
    }
    
    func (e *errWriter) Write(buf []byte) (int, error) {
    	if e.err != nil {
    		return 0, e.err
    	}
    	var n int
    	n, e.err = e.Writer.Write(buf)
    	return n, nil
    }
    
    func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {
    	ew := &errWriter{Writer: w}
    	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.Fprint(ew, "\r\n")
    	io.Copy(ew, body)
    	return ew.err
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
  • 相关阅读:
    java-net-php-python-SSM公共租赁住房信息管理系统录像计算机毕业设计程序
    Java基础——重写toString()方法
    “在 GitHub 用十年攒的 54k+ Star,一个误操全没了”
    代码库制作与使用
    ElasticsearchRestTemplate 和ElasticsearchRepository 的使用
    【Docker】非root用户加入docker用户组省去sudo (三)
    Multisim14 逻辑分析仪的使用教程(打开&关闭+详细具体)
    实现Ant Design Vue的modal可拖拽
    设备管理团队如何做好停机维护工作_基于PreMaint设备数字化平台
    ChatGpt介绍和国产ChatGpt对比
  • 原文地址:https://blog.csdn.net/Small_Mouse0/article/details/125433393