• Go语言错误处理最佳实践


    错误处理实践

    我们在go语言中设计error的处理体系时候, 一般都会去做下面两点

    直接使用errors.New()生成error接口的值
    扩展error接口, 并定义扩展error接口的实现类型

    error接口是什么?

    go语言的error是一个接口类型, 其源码如下:

    type error interface {
    	Error() string
    }
    
    • 1
    • 2
    • 3

    我们可以定义它的实现类型, 比如我们经常使用到的errors.New()方法, 返回值为一个error接口的实现类型*errorString的结构体字面量

    package errors
    
    func New(text string) error {
    	return &errorString{text}
    }
    
    type errorString struct {
    	s string
    }
    
    func (e *errorString) Error() string {
    	return e.s
    }
    
    //package main
    xxxerr := errors.New("xxx")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    所以我们可以直接调用errors.New()为我们生成一个error接口的值

    扩展的error接口

    我们为什么需要对error接口进行扩展呢?, 原因是error的实现类型范围太大了, 细粒度不够小, 所以我们需要实现更加精细的控制, 关于这种设计我们可以参考go语言标准库中的一些error处理代码, 比如下面的

    type Error interface {
    	//嵌入了error接口, 实现net.Error也会实现error
        error
    
        //扩展
        Timeout() bool
        Temporary() bool
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    然后我们又可以定义一个类型来实现这个扩展错误接口类型, 比如下面这个OpError:

    type OpError struct {
    	Op string
    	Net string
    	Source Addr
    	Addr Addr
    	Err error
    }
    
    //实现函数1
    func (e *OpError) Error() string {
    	return ""
    }
    
    //下面是实现函数2
    func (e *OpError) Timeout() bool {
    	//对应的处理逻辑
        return true
    }
    
    //下面是实现函数2
    func (e *OpError) Temporary() bool {}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    我们发现该结构体中存在一个名字叫做Err的类型为error的字段, 它代表了该错误的潜在错误, 有可能OpError类型的错误值还包含了AddrError这种错误
    通过这种类型建立起树形的错误体系, 用统一字段建立可追溯的链式错误关联, 我们就可以建立起来一套优秀的错误处理机制
    为了更好的表示, 我画了一张图

    在这里插入图片描述

    具体的错误

    因为Go语言的error是一个接口, 所以这个它的值的实际类型是非常复杂的, 于是我们就需要去判断它的值的一个实际类型

    如果错误值在某一个范围内, 我们可以使用类型断言表达式或者类型断言+switch语句进行判断
    对于已有相应变量且类型相同的一系列错误值, 一般直接使用判等操作 + switch语句
    没有相应变量且类型未知的一系列错误值, 只能使用其错误信息的字符串表示形式来判断

    下面我们分别来看上面的内容: 首先是第一点, 已知错误值的范围比如: {os.PathError|os.LinkError|os.SyscallError|exec.Error}, 是它们中的一个, 我们可以直接使用类型断言+switch, 然后返回潜在错误类型

    func underlyingError(err error) error {
      switch err := err.(type) {
      case *os.PathError:
        return err.Err
      case *os.LinkError:
        return err.Err
      case *os.SyscallError:
        return err.Err
      case *exec.Error:
        return err.Err
      }
      return err
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    当我已经知道某个错误是哪一个, 我们直接使用判等操作+switch,

    printError := func(i int, err error) {
        if err == nil {
            fmt.println("nil error")
            return
        }
    
        err = underlyingError(err)
    
        switch err {  
        case os.ErrClosed:    
            fmt.Printf("error(closed)[%d]: %s\n", i, err)  
        case os.ErrInvalid:    
            fmt.Printf("error(invalid)[%d]: %s\n", i, err)  
        case os.ErrPermission:    
            fmt.Printf("error(permission)[%d]: %s\n", i, err)  
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    通过上面这种直接判等操作, 我们就可以锁定具体的错误值了

    对于上面两种情况, 我们都会有比较明确的方法去解决, 但是我们对一个错误值可能代表的含义知道的很少, 那么就只能通过错误信息去判断了

  • 相关阅读:
    Docker(一) ----初始Docker
    【博学谷学习记录】超强总结,用心分享|架构师-前置知识-mongodb基本使用
    【深度学习相关知识】
    FFplay文档解读-44-视频过滤器十九
    ASP.NET Core WebApi返回结果统一包装实践
    内容 总结
    供应化学试剂BHQ-1 氨基|BHQ-1 amine|1308657-79-5
    软件测试面试题集锦--持续汇总更新
    鸿蒙Harmony应用开发—ArkTS声明式开发(触摸事件)
    API接口开发规范
  • 原文地址:https://blog.csdn.net/m0_73728511/article/details/133749918