• go error处理


    一、 Error vs Exception

    exception是抛出给调用者,由调用者处理;
    panic直接程序挂掉,代码不能再继续运行。

    error可以不隐藏控制流,简单方便,默认每个操作都是原子。

    二、 Error Type

    • 预定义的特定错误称为sentinel error,比如io.EOF甚至systemcall.ENOENT,直接用等号判断。

    • error types自己实现自定义类型,使用类型断言和类型switch,但还是强耦合。

    • opaque errors, 不暴露error的具体错误,如果需要更多信息去判断error的行为而不是类型

    三、 Handling Error
    使用一些技巧,简化代码中if err != nil 的语句

    type Header struct {
    	Key, Value string
    }
    
    type Status struct {
    	Code int
    	Reason string
    }
    
    // WriteResponse 没有简化err处理的程序
    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
    }
    
    
    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, e.err
    }
    // WriteResponseNew 简化处理err
    func WriteResponseNew(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
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    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, err
    	}
    	return lines, nil
    }
    
    func CountLinesNew(r io.Reader) (int, error) {
    	sc := bufio.NewScanner(r)
    	lines := 0
    	for sc.Scan() {
    		lines++
    	}
    	return lines, sc.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
    • 25
    • 26
    • 27
    • Wrap errors
      要么直接向上抛出,会难以定位;要么打印后再向上抛出,则打印的错误位置混乱。

    使用github.com/pkg/errors第三方库
    errors.New 或者 errors.Errorf , 会记录堆栈
    errors.Wrap或者Warpf将标准库或第三方库进行包装,会保存堆栈信息
    在顶部进行errors.Cause获取root error,使用%v+进行堆栈打印

    • go1.13错误处理
      errors.Is(err, ErrNotFound) 逐层展开
      erross.As(err, &e)

    常规的错误检查方式:
    1 简单的错误检查:
    if err != nil {

    }
    2 对sentinal err进行检查:
    var ErrNotFound = errors.New(“not found”)
    if err == ErrNotFound {
    }
    3 实现err interface的自定义error struct,进行断言获取更丰富的上下文
    type NotFoundError struct {
    Name string
    }
    func (e *NotFoundError) Error() string { return e.Name + “: not found”}

    if e, ok := err.(*NotFoundError); ok {

    }

    在常规的使用fmt.Errorf()方法对err进行上下文包装时,会修改err的sentinal信息,则
    依赖此error的相等判断会失效,go1.13引入fmt.Errorf的%w形式,对err进行warp封装:

    // 常规
    if err != nil {
    	return fmt.Errorf("decompress %v: %v", name, err)
    }
    // 使用%w的新谓词
    if err != nil {
    	return fmt.Errorf("decompress %v: %w", name, err)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    使用%w包装错误可用于errors.Is和errors.As:

    err := fmt.Errorf("access denied: %w", ErrPermission)
    if errors.Is(err, ErrPermission)
    
    • 1
    • 2

    最终实现,使用标准包的Is和As方法,但由于标准包的fmt.Errorf不包含堆栈信息,
    则仍使用pkg/errors的Wrap方法

    sentinel := os.ErrPermission
    
    // 不使用%w格式,丢失sentinel
    err1 := fmt.Errorf("add prefix: %v", sentinel)
    fmt.Printf("err1: %v\n", err1)
    if err1 == sentinel {
    	fmt.Println("err1 == sentinel")
    }
    
    // 使用fmt.Errorf的%w格式,或者xerrors.Wrap方法,都可以后续进行Is判断
    //
    //err = fmt.Errorf("add prefix: %w", sentinel)
    err2 := xerrors.Wrap(sentinel, "add suffix")
    fmt.Printf("err2: %v\n", err2)
    if errors.Is(err2, sentinel) {
    	fmt.Println("err2 is sentinel")
    }
    fmt.Printf("err2 trace:\n%+v\n", err2)
    
    fmt.Println("------------------------")
    
    // 这里err2已经有堆栈信息,如果还使用Warp则err3最后的打印会包含两个堆栈
    err3 := xerrors.WithMessage(err2, "err3 prefix")
    fmt.Printf("err3: %v\n", err3)
    if errors.Is(err3, sentinel) {
    	fmt.Println("err3 is sentinel")
    }
    fmt.Printf("err3 trace:\n%+v\n", err3)
    
    • 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

    对错误进行层层封装的写法:

    func ReadFile(path string) ([]byte, error) {
    	f, err := os.Open(path)
    	if err != nil {
    		// 对sentinel err进行封装
    		return nil, xerrors.Wrap(err, "Open FAILED")
    	}
    	defer f.Close()
    
    	// 直接返回 or 再封装?
    	buf, err := io.ReadAll(f)
    	if err != nil {
    		return nil, xerrors.Wrap(err, "Read FAILED")
    	}
    	return buf, nil
    
    }
    
    func ReadConfig() ([]byte, error) {
    	home := os.Getenv("Home")
    	config, err := ReadFile(filepath.Join(home, ".setting.xml"))
    	return config, xerrors.WithMessage(err, "COULD NOT READ CONFIG")
    }
    
    func main()  {
    	_, err := ReadConfig()
    	if err != nil {
    		// errors.Cause()返回sentinel error
    		fmt.Printf("original error: %T -- %v\n", xerrors.Cause(err), xerrors.Cause(err))
    		fmt.Printf("stack trace:\n%+v\n", err) // 打印错误堆栈
    		os.Exit(1)
    	}
    }
    
    • 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

    errors.As样例:

    type CloseError struct {
    	Code int
    	Reason string
    }
    
    func (ce *CloseError) Error() string {
    	return fmt.Sprintf("status = %v and reason = %q", ce.Code, ce.Reason)
    }
    
    func main() {
    	sentinel := &CloseError{
    		Code: 1,
    		Reason: "Normal",
    	}
    
    	// 使用fmt.Errorf的%w格式,或者xerrors.Wrap方法,都可以后续进行As判断
    	err := fmt.Errorf("add_prefix: %w", sentinel)
    	//err := xerrors.Wrap(sentinel, "add prefix")
    	var ce = &CloseError{}
    	if errors.As(err, &ce) {
    		fmt.Println("err As ce")
    		fmt.Println(ce)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
  • 相关阅读:
    防止砍单、封号:亚马逊、沃尔玛测评方案优化建议
    移动百事通BesTV_R3300-L_S905L_8189_线刷固件包
    怎样给黑白照片上色?这篇文章来教你
    LeetCode【第2575题】
    使用WebSocket实现答题积分排名实时更新的功能
    以前端视角,漫谈「云端」
    SQL Server 临时对象缓存
    [MySQL] MySQL表的约束
    力扣记录:剑指offer(7)——JZ59-68
    深度学习进阶(二十七)现代 LLM 的核心架构设计其二:SwiGLU
  • 原文地址:https://blog.csdn.net/Zerore/article/details/125560563