Go语音开发中常见Error类型处理示例详解
bluesGavin 人气:0前言
上文我们了解了 Error
在 Go 中的设计理念。单从理念上来看, Error
的设计似乎有利于逻辑处理。但实际上,在开发过程中,我们还会遇到各种各样的困难。为了优化开发的流程,开发者们发明了很多处理 Error
的做法。今天我们就来看看目前 Go 开发中常见的几大 Error
类型。
透明错误处理策略
在最简单的 Go Error
设计中,开发者被期望通过判定函数返回的 err 值来确定调用是否正常。
value, err := doSomething() if err != nil { // 错误处理 } // 逻辑操作
这是 Go 语言中最常见的错误处理策略 ,80%以上的 Go 错误处理情形都可以归类到这种策略下。这样构造出的错误值代表的上下文信息,对错误处理方是透明的,因此这种策略称为 透明错误处理策略 。
这种策略最大的问题是,无法判定错误的原因和种类。它只是简单地把”是否出错“分为了两路逻辑。一旦我们的业务要求我们针对不同的错误原因,做不同的处理,这种情况就行不通了。
带来的问题
透明策略只解决了”是否出错“的问题,但开发者无法知道错误的原因。如果想要得到错误的内容,需要主动解析error值,这也引出了下面要讲的处理策略。
哨兵(Sentinel)错误处理策略
哨兵策略策略主要是解决透明错误处理策略精度不够的情况,它可以让开发者得到“是什么错误”,而不仅是”是否出错“。实际上,它就是通过预定义错误类型,在错误处理时,判断错误是否属于预定义的类别,从而做出处理。
因此我们可以得到它最简单的实现:
value, err := doSomething() if err!=nil { // 通过err.Error()获得错误信息 switch err.Error(){ case "bufio: negative count": //特点错误处理 return // 其他判断 default: // 未知错误原因 return } }
但这显然不是好的写法,会导致很严重的耦合问题。Go 1.13 之后,我们可以用 error.Is 方法很好地解决这个问题。
// 定义错误类型,一般变量用Err开头 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") ) func main(){ value, err := doSomething() if errors.Is(err,ErrBufferFull){ //判断是否为某类型错误 } }
带来的问题
虽然哨兵策略解决了我们不能获得错误原因的问题,但它本身也存在着设计缺陷。
1.对errors.Error()的依赖
==
的判断依赖着 errors.Error()
的输出。这是哨兵策略最大的问题。他导致了 error 值的扩展性很差,假如开发者想要为 error 带上上下文或者错误行号,文件位置等信息,都会破坏哨兵策略的判断。
从某种角度来看,errors.Error()
的输出更应该是用于开发者的调试使用,而不应该作为程序判断机制的一部分。
2.定义的错误类型会被公开
很多时候为了做错误类型判断,一些包内需要定义许多错误类型。而这些错误类型通常会被共用,这使得他们不得不被公开。在公共库中,被公开的错误类型必须有专门的文档说明,否则会对使用者造成困惑。这会额外增加开发者的工作量。
再者,有时为了判断某个特殊错误类型,使用者会引入对应的包。假如这种情况大量存在,会导致整个库生态极其混乱。
Error types
Error types 是实现了 errors 接口的自定义类型,目的是为了更加具体地描述错误。开发者可以在类型上带上需要的信息,如行号,文件位置等。可以参考下方:
type MyError struct { Msg string // 错误信息 Line int // 行号 FIle string // 文件位置 } // 实现 errors 接口 func (e *MyError) Error()string{ return fmt.Sprintf("%s:%d: %s",e.File,e.Line,e.Msg) }
上面提到,修改 error 值会导致哨兵策略的失效, Error types 就是例子之一。由于是自定义的类型,我们不能再用 ==
判断错误类型了,而是改用断言。
value, ok := x.(MyError) if ok { //判断是否为某类型错误 }
带来的问题
可以看到 Error Types 最大的优势是其丰富的错误描述能力。但它仍然没有解决哨兵策略下的问题。
不透明错误处理策略(Opaque errors)
不透明错误处理策略可以说是最灵活的处理方式,它要求代码与调用者之间的耦合最少。开发者可以知道”是否出错“,但无法得到错误的内部信息。
value, err := doSomething() if err != nil { // 错误处理 } // 逻辑操作
是的,在使用上不透明策略跟透明策略是很相似的。其中最大的不同是,透明策略可以通过 error.Error()
接口获得错误信息。而不透明策略中是不可以的。
那如果开发者需要判断错误类型怎么办呢?不透明策略更推荐的是断言 error 实现的行为,而不是特定的类型或值。可以参考下方代码:
type temporary interface { Temporary() bool } // 封装判断函数 func IsTemporary(err error) bool { // 断言 te, ok := err.(temporay) // 是否满足条件 return ok && te.Temporary() }
带来的问题
不透明策略跟透明策略一样,是很灵活的策略。他们的区别只在于错误的信息是否开放。因此,不透明策略依旧存在信息量缺乏的问题。
总结
今天我们了解了目前 Go 开发中常见的四种 Error 类型。这些类型各有优劣,需要开发者在实际开发者中深入思考,做出取舍。通常我们需要考虑,我们开发的是工具还是业务,我们需要错误为我们提供灵活度还是精确的信息。掌握本文的内容,可以让你在遇到错误处理时,有更多的处理思路。
当然,每种方式又会有问题,在接下来的文章里,我会分享一些常见的错误处理操作。
加载全部内容