Go语言3小时光速入门04——错误处理

Go语言中的错误处理

在整个go语言中最让我感到恶心的就是对错误的处理方式。

在go中没有异常的概念当然就更不可能出现诸如try{}catch{}的处理方式。

关于go的错误处理方式,目前网上包括社区有很多争论。在1.6版本发布前官方就曾经公开过考虑在新版本中更加优雅的处理错误,并在社区开始讨论。但是在1.6发布之后无事发生。(事后官方表示原因之一是因为目前找不到较为贴切的替代方案(官方表示目前打死不添加新的关键字)...)

在Go以外的语言中,你需要将所有内容包装在相同的内容中try...catch。

Go中严格规定:可能失败的每个函数都应该返回一个error类型作为最后一个值,并且随后对其处理。由于Golang的零值概念,你通常可以在没有错误处理的情况下忽略错误处理。

即Go语言中处理错误的惯用法是:将错误作为函数最后一个返回值的形式将其返回,并在调用它的地方检查返回的错误值

在Go的错误处理中,你经常会发现变量error几乎不受控制的满天飞...通用的错误处理:

v, err := 会有错误返回的函数()

if err != nil {

// 处理错误

}

上面的模板代码相当于try{}catch(捕获到异常){处理异常}

在go语言中对于会返回error的函数建议尽早处理,尽早失败。

error类型

既然go语言中没有异常只有error,那么这个error类型究竟是什么呢?我们可以打开go的源码发现,error类型是一个简单的接口

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
    Error() string
}

error 有了一个签名为 Error() string 的函数。所有实现该接口的类型都可以当作一个错误类型。Error() 函数给出了错误的描述。

自定义错误

上面我们看到error其实就是一个接口(相当于Java的Throwable和Exception),实现该接口的类型都可以当作一个错误类型。所以我们自然也可以定义我们自己的错误类型。

type 自定义错误名 struct {
	//error接口
	error
	//其他自定义错误成员...
}

func (*自定义错误) Error()string{
//重写Error()函数....
}

创建返回错误

既然错误是一个类型,我们自然可以非常方便的创建一个值,然后返回。但是在go语言中为我们提供了更加便捷的创建错误的方式:errors.New("错误信息")用于Error()。

func TestRecover(t *testing.T){
	err := returnError()
	if err != nil{
		t.Error("出现错误!",err)
	}
}

func returnError() error {
	return errors.New("我返回了一个错误")
}

断言错误类型

有时我们会对自定义错误添加一些特殊的自定义处理函数,如连接超时后再次重新尝试等。这个时候外层仅仅拿到一个error是不行的,我们需要获取到特定类型的错误,这个时候可以用我们之前讲到的断言。error.(*自定义异常).自定义处理函数()如:

err_test.go:

type timeOutErr struct {
	error
	//是否是超时
	IsTimeOut bool
}

func (error *timeOutErr) timeOut(){
	if error.IsTimeOut{
		fmt.Println("超时重新尝试连接")
	}
}

func connect()error{
	//返回超时错误
	return timeOutErr{errors.New("连接错误"),true}
}

func TestTimeOut(t *testing.T){
	if err := connect(); err != nil{
		//断言错误类型
		timeOutError,ok := err.(timeOutErr);
		if ok{
			timeOutError.timeOut()
		}
	}
}

recover()函数

在go中提供了内置函数recover,可以捕获到错误。回想上一节中我们介绍到的defer函数。这样我们可以实现诸如类似于try...catch函数返回前的错误处理。在出现错误时,将会首先将错误交给revocer(但是个人建议这么做时一定要谨慎,因为go中的零值,defer只有在函数返回前执行,也就是说前面的代码很可能已经执行了)

defer func() {
	err := recover()
	if err != nil{
	//处理错误
	}
}()


err_test.go:

func TestRecover(t *testing.T){
	defer func() {
		err := recover()
		if err != nil{
			t.Error("出现错误!",err)
		}
	}()
	a := 123
	b := 0
	a = a / b
}

panic()函数

panic用于不可恢复的错误,其实在Go中也将错误分为了"异常"和"错误"对待,只不过他们都叫错误...如果说上面的recover相当于try...catch捕获异常进行处理,那么panic就相当于真正的需要终止的错误了(绝不能发生)。

对于“不可能发生”的情况,我们可以调用内置的panic()函数,该函数可以传入任何想要的值(例如,一个字符串用于解释为什么那些不变的东西被破坏了)。在其他语言如Java中我们可以使用assert进行断言,但在Go语言我们使用panic()。

*当内置的panic()函数被调用时,外围函数的执行会立即中止。然后,任何延迟执行的函数或者方法都会被调用,就像其外围函数正常返回一样。最后,调用返回到该外围函数的调用者,就像该外围调用函数或者方法调用了 panic()一样,因此该过程一直在调用栈中重复发生:函数停止执行,调用延迟执行函数等。当到达main()函数时不再有可以返回的调用者,因此这时程序会终止,并将包含传入原始panic()函数中的值的调用栈信息输出到os.Stderr。

简单的说就是哪怕退出panic也会把defer函数调完(这也是和os.Exit的主要区别)

painc(任意类型的值)

painc之后程序会执行defer然后退出

如:

err_test.go:

func TestPanic(t *testing.T){
	defer func() {
		fmt.Println("相当于finally的defer")
	}()
	fmt.Println("start")
	//相当于Java中的assert xxxx
	panic(errors.New("something wrong!"))
}

当然除了我们外部手动调用panic外,系统执行内部如果出现什么运行时错误也会内置panic(一个骚操作是,在defer里面recover恢复继续执行)

*err_test.go:

func TestPanic(t *testing.T){
	defer func() {
		err := recover()
		if err != nil{
			fmt.Println(err)
			fmt.Println("给劳资诈尸继续执行!")
			doSomeThing(0)
		}
		fmt.Println("相当于finally的defer")
	}()
	fmt.Println("start")
	doSomeThing(6)
}

func doSomeThing(a int){
	fmt.Println("干活干活")
	arr := [4]int{1,2,3,4}
	//下标越界
	fmt.Println(arr[3 + a])
}

在处理错误时,我们往往可以通过获取返回error然后马上处理,或者通过revocer处理。

建议:优先直接处理返回获取到的错误(尽早失败原则)防止对后面逻辑造成影响,其次选择recover和panic

更新时间:2020-02-21 14:09:57

本文由 寻非 创作,如果您觉得本文不错,请随意赞赏
采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
原文链接:https://www.zhouning.group/archives/go语言3小时光速入门04go语言的错误处理
最后更新:2020-02-21 14:09:57

评论

Your browser is out of date!

Update your browser to view this website correctly. Update my browser now

×