亲宝软件园·资讯

展开

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 类型。这些类型各有优劣,需要开发者在实际开发者中深入思考,做出取舍。通常我们需要考虑,我们开发的是工具还是业务,我们需要错误为我们提供灵活度还是精确的信息。掌握本文的内容,可以让你在遇到错误处理时,有更多的处理思路。

当然,每种方式又会有问题,在接下来的文章里,我会分享一些常见的错误处理操作。

加载全部内容

相关教程
猜你喜欢
用户评论