优雅的处理Go错误

错误处理属于编程中非主要但不可或缺的部分,优雅的错误处理不仅能让业务逻辑更清晰,还提高身心健康。

错误处理

Go支持多返回值,因此Go对错误的处理模式是将错误作为返回值返回。刚接触Go时,你一定会对return nil, err这样的代码记忆尤新,同时也会对大量的if err != nil感到力不从心。

将错误在返回值中返回的好处是你必须显示处理所有的错误,或者使用_显示忽略。缺点是需要写大量的if err != nil,这一步无法省略,但是可以减少类似的代码,当然,前提是业务逻辑是类似的。

我们以一个序列化Rect结构体的例子来看如何优雅的处理错误。

type Rect struct {
	X int32
	Y int32
	W int32
	H int32
}

func Marshal_v1(r Rect) ([]byte, error) {
	var buf bytes.Buffer
	if err := binary.Write(&buf, binary.BigEndian, r.X); err != nil {
		return nil, err
	}
	if err := binary.Write(&buf, binary.BigEndian, r.Y); err != nil {
		return nil, err
	}
	if err := binary.Write(&buf, binary.BigEndian, r.W); err != nil {
		return nil, err
	}
	if err := binary.Write(&buf, binary.BigEndian, r.H); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}
  • 闭包

将业务逻辑提取到闭包中,然后在一个地方统一处理错误。

func Marshal_v2(r Rect) (bs []byte, err error) {
	var buf bytes.Buffer
	marshal := func(i int32) {
		if err == nil {
			err = binary.Write(&buf, binary.BigEndian, i)
		}
	}
	marshal(r.X)
	marshal(r.Y)
	marshal(r.W)
	marshal(r.H)

	bs = buf.Bytes()
	return
}
  • 结构体
type marsher struct {
	buf bytes.Buffer
	err error
}

func (m *marsher) write(i int32) {
	if m.err == nil {
		m.err = binary.Write(&m.buf, binary.BigEndian, i)
	}
}

func Marshal_v3(r Rect) ([]byte, error) {
	m := marsher{}
	m.write(r.X)
	m.write(r.Y)
	m.write(r.W)
	m.write(r.H)
	if m.err != nil {
		return nil, m.err
	}
	return m.buf.Bytes(), nil
}

通过闭包和结构体封装两种方式都能减少if err != nil代码。仔细看就能发现,这两种方式都是将相似的业务逻辑抽象成函数,区别在于是闭包还是结构体的方法,其本质还是在复用

然而这样的优化也不是无脑的,如果真的要写例子中的程序,下面的写法难道不更香吗?

func Marshal_v4(r Rect)(bs []byte) {
	temp := make([]byte, 4)
	binary.BigEndian.PutUint32(temp, uint32(r.X))
	bs = append(bs, temp...)
	binary.BigEndian.PutUint32(temp, uint32(r.Y))
	bs = append(bs, temp...)
	binary.BigEndian.PutUint32(temp, uint32(r.W))
	bs = append(bs, temp...)
	binary.BigEndian.PutUint32(temp, uint32(r.H))
	bs = append(bs, temp...)
	return bs
}
// 或者
func Marshal_v5(r Rect) (bs []byte, err error) {
	var buf bytes.Buffer
	err = binary.Write(&buf, binary.BigEndian, r)
	if err == nil {
		bs = buf.Bytes()
	}
	return
}

错误包装

似乎生产中很少会返回原生的错误类型,比如errors.New。此外,我们习惯上会对错误进行包装然后返回,据说是为了方便查问题。包装错误也有3种方式。

  • fmt.Errorf

fmt.Errorffmt包提供的一个比较特殊的格式化函数,它的参数只能是error类型。

func main() {
	err := errors.New("inner")
	err = fmt.Errorf("outter:%v", err)
	fmt.Println(err)
}
  • 结构体
type MyErr struct {
	cause string
	err error
}

func(e MyErr) Error() string {
	return fmt.Sprintf("%s: %v", e.cause, e.err)
}

func main() {
	err := errors.New("inner")
	err = MyErr{"unknown", err}
	fmt.Println(err)
}
  • github.com/pkg/errors
func main() {
	err := errors.New("inner")
	err = errors.Wrap(err, "outer")
	fmt.Println(err)
}

标准库的errors包有4个方法:

  • errors.As
  • errors.Is
  • errors.New
  • errors.Unwrap

github.com/pkg/errors包也有类似的函数,不知道你对他们是否感到好奇。他们的作用是什么,又是如何实现的。限于篇幅,我们下期见。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值