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")
)
(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
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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")
......

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() }
(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
}

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
}

2. 避免无效判断

eliminate error handling by eliminate errors

避免无效的错误判断

func test() error { //不推荐
	err := funca()
	if err != nil {
		return err;
	}
	return nil;
}

func test() error { //推荐
	return funca()
}

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
	}
}

未使用错误收集方式

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
}

使用错误收集方式

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
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鼠晓

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值