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

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

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

最终实现,使用标准包的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)

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

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

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)
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值