zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Go 错误处理:如何通过 error、deferred、panic 等处理错误?

Go 如何 Error 通过 错误处理 deferred panic
2023-09-14 09:15:18 时间

错误


在 Go 语言中,错误是可以预期的,并且不是非常严重,不会影响程序的运行。对于这类问题,可以用返回错误给调用者的方法,让调用者自己决定如何处理。

 

error 接口


在 Go 语言中,错误是通过内置的 error 接口表示的。它非常简单,只有一个 Error 方法用来返回具体的错误信息,如下面的代码所示: 

type error interface {

   Error() string

}

在下面的代码中,我演示了一个字符串转整数的例子:

func main() {

   i,err:=strconv.Atoi("a")

   if err!=nil {

      fmt.Println(err)

   }else {

      fmt.Println(i)

   }

}

这里我故意使用了字符串 "a",尝试把它转为整数。我们知道 "a" 是无法转为数字的,所以运行这段程序,会打印出如下错误信息:

strconv.Atoi: parsing "a": invalid syntax

 这个错误信息就是通过接口 error 返回的。我们来看关于函数 strconv.Atoi 的定义,如下所示:

func Atoi(s string) (int, error)

一般而言,error 接口用于当方法或者函数执行遇到错误时进行返回,而且是第二个返回值。通过这种方式,可以让调用者自己根据错误信息决定如何进行下一步处理。

小提示:因为方法和函数基本上差不多,区别只在于有无接收者,所以以后当我称方法或函数,表达的是一个意思,不会把这两个名字都写出来。

Deferred 函数


在一个自定义函数中,你打开了一个文件,然后需要关闭它以释放资源。不管你的代码执行了多少分支,是否出现了错误,文件是一定要关闭的,这样才能保证资源的释放。

如果这个事情由开发人员来做,随着业务逻辑的复杂会变得非常麻烦,而且还有可能会忘记关闭。基于这种情况,Go 语言为我们提供了 defer 函数,可以保证文件关闭后一定会被执行,不管你自定义的函数出现异常还是错误。

下面的代码是 Go 语言标准包 ioutil 中的 ReadFile 函数,它需要打开一个文件,然后通过 defer 关键字确保在 ReadFile 函数执行结束后,f.Close() 方法被执行,这样文件的资源才一定会释放。

func ReadFile(filename string) ([]byte, error) {

   f, err := os.Open(filename)

   if err != nil {

      return nil, err

   }

   defer f.Close()

   //省略无关代码

   return readAll(f, n)

}

defer 关键字用于修饰一个函数或者方法,使得该函数或者方法在返回前才会执行,也就说被延迟,但又可以保证一定会执行。

以上面的 ReadFile 函数为例,被 defer 修饰的 f.Close 方法延迟执行,也就是说会先执行 readAll(f, n),然后在整个 ReadFile 函数 return 之前执行 f.Close 方法。

defer 语句常被用于成对的操作,如文件的打开和关闭,加锁和释放锁,连接的建立和断开等。不管多么复杂的操作,都可以保证资源被正确地释放。

Panic 异常


Go 语言是一门静态的强类型语言,很多问题都尽可能地在编译时捕获但是有一些只能在运行时检查比如数组越界访问、不相同的类型强制转换等,这类运行时的问题会引起 panic 异常

除了运行时可以产生 panic 外,我们自己也可以抛出 panic 异常。假设我需要连接 MySQL 数据库,可以写一个连接 MySQL 的函数connectMySQL,如下面的代码所示:

func connectMySQL(ip,username,password string){

   if ip =="" {

      panic("ip不能为空")

   }

   //省略其他代码

}

在 connectMySQL 函数中,如果 ip 为空会直接抛出 panic 异常。这种逻辑是正确的,因为数据库无法连接成功的话,整个程序运行起来也没有意义,所以就抛出 panic 终止程序的运行

panic 是 Go 语言内置的函数,可以接受 interface{} 类型的参数,也就是任何类型的值都可以传递给 panic 函数,如下所示:

func panic(v interface{})

小提示:interface{} 是空接口的意思,在 Go 语言中代表任意类型。

panic 异常是一种非常严重的情况,会让程序中断运行,使程序崩溃,所以如果是不影响程序运行的错误,不要使用 panic,使用普通错误 error 即可。 

Recover 捕获 Panic 异常


通常情况下,我们不对 panic 异常做任何处理,因为既然它是影响程序运行的异常,就让它直接崩溃即可。但是也的确有一些特例,比如在程序崩溃前做一些资源释放的处理,这时候就需要从 panic 异常中恢复,才能完成处理。

在 Go 语言中,可以通过内置的 recover 函数恢复 panic 异常因为在程序 panic 异常崩溃的时候,只有被 defer 修饰的函数才能被执行,所以 recover 函数要结合 defer 关键字使用才能生效。

下面的示例是通过 defer 关键字 + 匿名函数 + recover 函数从 panic 异常中恢复的方式。

func main() {

   defer func() {

      if p:=recover();p!=nil{

         fmt.Println(p)

      }

   }()

   connectMySQL("","root","123456")

}

运行这个代码,可以看到如下的打印输出,这证明 recover 函数成功捕获了 panic 异常。

ip 不能为空

通过这个输出的结果也可以发现,recover 函数返回的值就是通过 panic 函数传递的参数值

总结


这节课主要讲了 Go 语言的错误处理机制,包括 error、defer、panic 等。在 error、panic 这两种错误机制中,Go 语言更提倡 error 这种轻量错误,而不是 panic。