首页 > 其他分享 >Go语言精进之路读书笔记第37条——了解错误处理的4种策略

Go语言精进之路读书笔记第37条——了解错误处理的4种策略

时间:2024-02-27 13:25:12浏览次数:22  
标签:errors err 错误 读书笔记 37 error Go 错误处理

C语言家族的经典错误机制:错误就是值。同时Go结合函数/方法的多返回值机制避免了像C语言那样在单一函数返回值种承载多重信息的问题。

37.1 构造错误值

错误处理的策略与构造错误值的方法是密切关联的。

错误是值,只是以error接口变量的形式统一呈现(按惯例,函数或方法通常将error类型返回值放在返回值列表的末尾)。

error接口是Go原生内置的类型,它的定义如下:

// $GOROOT/src/builtin/builtin.go
// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
    Error() string
}

任何实现了Error() string方法的类型的实例均可作为错误赋值给error接口变量。

在标准库中,Go提供了构造错误值的两种基本方法——errors.Newfmt.Errorf,示例如下:

err := errors.New("your first demo error")
errWithCtx = fmt.Errorf("inedx %d is out of bounds", i)
wrapErr = fmt.Errorf("wrap error: %w", err) // 仅Go 1.13及后续版本可用

Go 1.13版本之前,这两种方法实际上返回的是同一个实现了error接口的类型的实例,这个未导出的类型就是errors.errorString:

// $GOROOT/src/errors/errors.go
type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}

Go 1.13及后续版本中,当我们在格式化字符串中使用%w时,fmt.Errorf返回的错误值的底层类型为fmt.wrapError:

// $GOROOT/src/fmt/errors.go (Go 1.13及后续版本)

type wrapError struct {
    msg string
    err error
}

func (e *wrapError) Error() string {
    return e.msg
}

func (e *wrapError) Unwrap() error {
    return e.err
}

与errorString相比,wrapError多实现了Unwrap方法,这使得被wrapError类型包装的错误值在包装错误链中被检视(inspect)到:

var ErrFoo = errors.New("the underlying error")

err := fmt.Errorf("wrap err: %w", ErrFoo)
errors.Is(err, ErrFoo) // true (仅Go 1.13及后续版本可用)

标准库中的net包定义了一种携带额外错误上下文的错误类型

// $GOROOT/src/net/net.go
type OpError struct {
    Op string
    Net string
    Source string
    Addr Addr
    Err Error
}

扩展:判断是否实现了接口

// $GOROOT/src/errors/wrap.go:39
func Is(err, target error) bool {
    if target == nil {
        return err == target
    }

    isComparable := reflectlite.TypeOf(target).Comparable()
    for {
        if isComparable && err == target {
            return true
        }
        if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
            return true
        }
        // TODO: consider supporting target.Is(err). This would allow
        // user-definable predicates, but also may allow for coping with sloppy
        // APIs, thereby making it easier to get away with them.
        if err = Unwrap(err); err == nil {
            return false
        }
    }
}

37.2 透明错误处理策略

完成不关心返回错误值携带的具体上下文信息,只要发生错误就进入唯一的错误处理执行路径。

err := doSomething()
if err != nil {
    // 不关心err变量底层错误值所携带的具体上下文信息
    // 执行简单错误处理逻辑并返回
    ...
    return err
}

37.3 “哨兵”错误处理策略

// $GOROOT/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")
)
// 错误处理代码
data, err := b.Peek(1)
    switch err {
    case bufio.ErrNegativeCount:
        // ...
        return
    case bufio.ErrBufferFull:
        // ...
        return
    case bufio.ErrInvalidUnreadByte:
        // ...
        return
    default:
        // ...
        return
    }
}
// 或者
if err := doSomething(); err == bufio.ErrBufferFull {
    // 处理缓冲区满的错误情况
    ...
}

一般哨兵错误值变量以ErrXXX格式命名。暴露“哨兵”错误值意味着这些错误值和包的公共函数/方法一起成为API的一部分。

从Go 1.13版本开始,标准库errors提供了Is方法用于错误处理方对错误值进行检视。Is方法类似于将一个error类型变量与“哨兵”错误值的比较:

// 类似 if err == ErrOutOfBounds{ ... }
if errors.Is(err, ErrOutOfBounds) {
    越界的错误处理
}

不同的是,Is方法会沿着错误链与链上所有被包装的错误进行比较,直到找到一个匹配的错误。

37.4 错误值类型检视策略

通过自定义错误类型的构造错误值的方式来提供更多的错误上下文信息,并且由于错误值均通过error接口变量统一呈现,要得到底层错误类型携带的错误上下文信息,错误处理方需要使用Go提供的类型断言机制(type assertion)或类型选择机制(type switch)。

// $GOROOT/src/encoding/json/decode.go
type UnmarshalTypeError struct {
    Value  string       // description of JSON value - "bool", "array", "number -5"
    Type   reflect.Type // type of Go value it could not be assigned to
    Offset int64        // error occurred after reading Offset bytes
    Struct string       // name of the struct type containing the field
    Field  string       // the full path from root node to the field
}

// $GOROOT/src/encoding/json/decode_test.go
func TestUnmarshalTypeError(t *testing.T) {
    for _, item := range decodeTypeErrorTests {
        err := Unmarshal([]byte(item.src), item.dest)
        if _, ok := err.(*UnmarshalTypeError); !ok {
            t.Errorf("expected type error for Unmarshal(%q, type %T): got %T",
                item.src, item.dest, err)
        }
    }
}

// $GOROOT/src/encoding/json/decode.go
// addErrorContext returns a new error enhanced with information from d.errorContext
func (d *decodeState) addErrorContext(err error) error {
    if d.errorContext.Struct != nil || len(d.errorContext.FieldStack) > 0 {
        switch err := err.(type) {
        case *UnmarshalTypeError:
            err.Struct = d.errorContext.Struct.Name()
            err.Field = strings.Join(d.errorContext.FieldStack, ".")
            return err
        }
    }
    return err
}

一般自定义导出的错误类型以XXXErr格式命名。与“哨兵”错误处理策略一样,这些错误类型和包的公共函数/方法一起成为API的一部分。

As方法可以检视某个错误值是不是某个自定义错误类型的实例。

As方法和Is方法一样,会沿着错误链与链上所有被包装的错误进行比较,直到找到一个匹配的错误。

37.5 错误行为特征检视策略

将某个包中的错误类型归类,统一提取出一些公共的错误行为特征(behavior),并将这些错误行为特征放入一个公开的接口类型中。

// $GOROOT/src/net/net.go
// An Error represents a network error.
type Error interface {
    error
    Timeout() bool   // Is the error a timeout?
    Temporary() bool // Is the error temporary?
}

// $GOROOT/src/net/http/server.go
func (srv *Server) Serve(l net.Listener) error {
    ...
    for {
        rw, err := l.Accept()
        if err != nil {
            select {
            case <-srv.getDoneChan():
                return ErrServerClosed
            default:
            }
            if ne, ok := err.(net.Error); ok && ne.Temporary() {
                // 这里对临时性错误进行处理
                ...
                time.Sleep(tempDelay)
                continue
            }
            return err
        }
        ...
    }
    ...
}

// $GOROOT/src/net/net.go
type OpError struct {
    ...
    // Err is the error that occurred during the operation.
    // The Error method panics if the error is nil.
    Err error
}

type temporary interface {
    Temporary() bool
}

func (e *OpError) Temporary() bool {
    // Treat ECONNRESET and ECONNABORTED as temporary errors when
    // they come from calling accept. See issue 6163.
    if e.Op == "accept" && isConnError(e.Err) {
        return true
    }

    if ne, ok := e.Err.(*os.SyscallError); ok {
        t, ok := ne.Err.(temporary)
        return ok && t.Temporary()
    }
    t, ok := e.Err.(temporary)
    return ok && t.Temporary()
}

参考

标签:errors,err,错误,读书笔记,37,error,Go,错误处理
From: https://www.cnblogs.com/brynchen/p/18036685

相关文章

  • 《系统科学方法概论》第5章读书笔记
    无论自然界还是人类社会,抑或人类思维都存在着自发组织起来的现象。自发组织系统理论的提出不是偶然的,他是19世纪中期以来热学力学同生物学矛盾发展的结果。一个系统由始态变化到中态,其内能的减少量的e等于该系统对外做的功a与该系统传递给环境的热量q之和,这就是热力学第一定律,用公......
  • 《系统科学方法概论》第4章读书笔记
    20世纪以来控制发展论有三个阶段,第一阶段是经典控制论阶段是20世纪4050年代。第2个是现代控制论阶段,20世纪6070年代第3个大系统理论阶段是20世纪70年代。控制论也有好多种分支情况。一工程控制论二生物控制论三社会控制论四人工智能。控制的定义。所谓控制就是指在一定的环境中,一......
  • [AGC037B] RGB Balls
    题意有\(n\)个人,\(3\timesn\)个球,球有三种颜色,每种颜色恰好\(n\)个。给每个人每种颜色的球各一个,按照在原序列的顺序分别设为\(p1,p2,p3\)。试求使得\(\sump_3-p_1\)最小的方案数。Sol其实直接考虑就行了,没必要想那么复杂。假设当前的球的颜色为\(R\),之前......
  • 《系统科学方法概论》第五章读书笔记
    首先,组建现代管理系统必须遵循远离平衡态原则。这也就是说,构成管理系统的人员必须具有各不相同的能力和水平,尤其是作为管理系统的第一把手应具备把握全局的能力和权力,而其他成员则不必具备把握全局的能力和权力,或只需具备把握某-方面全局的能力和权力即可。如果-一个管理系统的所......
  • 《系统科学方法概论》第二章读书笔记
    系统工程不是无缘无故地提出的,而是为了解决目前实践或科研中所遇到的问题而搞起来的。所以,在设计或研制一项系统工程前,应先把所遇到的问题搞清楚。问题是什么?就是现实状况和主观需要之间的矛盾。在阐述问题时,要注意三个方面:-是注意问题的过去、现在和将来的发展趋势,也就是要考......
  • 《系统科学方法概论》第三章读书笔记
    信息论最初是作为一种通信理论而被建立起来的,它也主要用于通信领域。但是经过30多年的发展,到了20世纪70年代,信息论已不仅仅是一种通信理论,而是越过了通信领域,广泛渗人其他学科了。例如在物理学研究中,一些科学家就把信息与熵1]联系起来,以说明系统的有序和无序的相互转化。在生物学......
  • 《系统科学方法概论》第一章读书笔记
    系统思想的发展史即人们对物质世界系统性认识的历史。这个历史经历了古代、近代、现代三个发展时期。任何系统都处于--定的环境中,并与环境发生着物质、能量或信息交换关系,脱离一定环境的系统是不存在的。什么是环境?所谓环境是指系统整体存在和发展的全部条件的总和。环境是构成......
  • 《构建之法》读书笔记一
    个人的成功不是天生的,而是慢慢积累的。当然,一个优秀的程序员也是慢慢学成的;正所谓:千里之行始于足下,我们必须从最基础的开始,不仅要学会写代码,更要学会看代码,看别人的代码,发表自己的意见;并且还要学会将代码规范化,代码看了要简洁明了,让别人看了就很舒服;当代码完成后,我们在为团队成员......
  • 《程序是怎样跑起来的》第十一章读书笔记
    Window控制硬件时借助的是输入输出指令。其中具有代表性的两个输入输出指令就是IN和OUT。这些指令也是汇编语言的助记符。I/O是loput/Output的缩写。显示器、键盘等外围设备都有各自专用的I/O控制器。I/0控制器中有用于临时保存输人输出数据的内存。这个内存就是端口。端口(port)......
  • 《程序是怎样跑起来的》第十二章读书笔记
    C语言的rund(函数中,也肯定通过某些公式生成了伪随机数。假如使用的是线性同余法的话,就需要提前设定Ri、a、b、c的数值,为此就要用到代码清单12-1及代码清单12-2中的srand(time(NULL));。srand(函数中的参数time(NULL),是用来获取当前时间的参数。以time(NULL)的值为基础,来设定Ri、a......