前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站https://www.captainbed.cn/kitie。
前言
作为开发者来说,我们没办法保证程序在运行过程中永远不会出现异常,对于异常,在很多编程语言中,可以用 try-catch
语句来捕获,而Go语言的开发者显然觉得 try-catch
被滥用了,因此 Go
不支持使用 try-catch
语句捕获异常处理。
那么,Go语言是如何定义和处理程序的异常呢?
Go语言的将程序运行出现的问题分为两种:错误(error
)和异常(exception
),错误是程序(比如一个函数)运行的预期结果之一,当你调用一个函数时,就应该处理出现错误的情况,而异常是不可预期的(当然也可以手动触发)且会导致程序中断运行的严重错误。
目录
Go错误处理策略
编写Go语言程序,一般推荐通过函数的最后一个返回值告诉调用者函数是否执行成功,可以分两种情况来讨论:
第一种情况,如果函数的执行结果只有正确与失败,那么返回值可以是 boolean
类型:
Go
func exists(key string) bool {
//to check if the key is exists
return true
}
if ok := exists("test"); ok {
// do something
}
第二种情况,如果要让调用者得到更详细的错误信息,显然只返回一个布尔值是不够的,这时候可以返回一个 error
类型,error
类型是一个接口,只有一个 Error
方法的接口:
Go
type error interface {
Error() string
}
通过 error
类型的 Error
方法,可以获得更详细的错误信息,方便调用者做进一步的处理,因此在 Go
标准库的方法和函数中,一般都会将 error
类型作为最后一个返回值,比如我们以前文章中讲到的 os
包下的 Open
和 Create
函数:
Go
package os
func Open(name string) (*File, error) {
return OpenFile(name, O_RDONLY, 0)
}
func Create(name string) (*File, error) {
return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}
当函数最后一个返回值 error
不为 nil
时,表示执行不成功,此时其他的返回值就应该忽略:
Go
file,err := os.Open("./my.dat")
if err != nil{
return err
}
defer file.Close()
上面代码中,如果 error
不为 nil
,那么变量 file
则为 nil
,表示无法获得一个文件句柄,这时候直接返回错误,因为如果再继续往下执行,可能会引发程序崩溃。
当然并不是所有情况下一遇到返回的 error
不为 nil
,就要抛弃其他返回值,比如使用 Read()
方法读取文件时:
Go
Read(p []byte) (n int, err error)
在读取文件到结尾时 Read
方法会返回一个 io.EOF
的错误:
Go
var EOF = errors.New("EOF")
此时虽然 error
不为 nil
,但仍然应该把读取到的字节数组保存,而不是抛弃掉。
创建error的几种方式
当我们开发自己的函数时,也可以创建自己的错误类型,有以下几种方式:
errors包
errors
包下的 New()
函数可以创建一个只有文本信息的 error
类型:
Go
package main
func main() {
var s = []int{1, 2, 3}
s[3] = 10
}
fmt包
fmt
包下的 Errorf
函数可以将文本格式后作为error类型的错误信息,并返回一个error类型,因此其作用与errors.New函数类似
Go
func getFile(name string)(*os.file,error){
if name == ""{
return nil,fmt.Errorf("file name could not be empty")
}
}
fmt.Errorf
函数还能封装其他 error
类型,再返回一个新的 error
类型,以此形成一条完整的错误链条:
Go
doc, err := html.Parse(resp.Body)
resp.Body.Close()
if err != nil {
return nil, fmt.Errorf("parsing %s as HTML: %v", url,err)
}
自定义错误类型
其实,上面两种创建错误类型的方式,本质上都是实现 error
接口,我们也可以创建一个拥有 Error
方法的类型来实现 error
接口:
Go
type Result struct {
Code int
Message string
Data interface{}
}
func (r Result) Error() string {
return r.Message
}
如何处理错误
当调用的函数或方法的返回值有 error
类型时,最简单的当然可以选择直接忽略错误,不过更恰当的方式是处理对应的错误,有以下几种处理策略:
直接返回错误
对于函数来说,如果在执行时遇到错误,可以直接返回给上层调用者:
Go
func SendMessage(url string) error {
if url == ""{
return errors.New("url can't not be empty")
}
_, err := http.Get(url)
if err != nil {
return err
}
return nil
}
记录日志并继续运行
当调用函数时遇到返回的错误,如果不影响程序运行,也可以选择记录错误日志并往下执行:
Go
if err := SendMessage("https://xxx/sendMessage");err != nil{
log.Printf("the message sending been broken by : %v\n", err)
}
记录日志并结束运行
如果错误影响程序的执行,也可以记录日志后,退出程序执行:
Go
if err := SendMessage("https://xxx/sendMessage");err != nil{
log.Printf("the message sending been broken by : %v\n", err)
os.Exit(1)
}
记录日志并退出执行,直接用 log
包的 Fatalf
函数就可以做到,因此上面代码的更简洁做法是:
Go
if err := SendMessage("https://xxx/sendMessage");err != nil{
log.Fatalf("the message sending been broken by : %v\n", err)
}
Go异常处理机制
在Go语言中,异常是指会引发程序崩溃无法继续运行的错误,比如数组越界或者空指针引用等情况,这时候会触发 panic
异常:
Go
package main
func main() {
var s = []int{1, 2, 3}
s[3] = 10
}
上面程序运行结果如下,可以看出触发了数组越界的异常:
Go
panic: runtime error: index out of range [3] with length 3
无论是在主协程还是子协程中,一旦触发 panic
异常,整个程序都会终止运行。
panic函数
除了数组越界等不可预测的异常会自动触发 panic
,也可以手动调用 panic
函数触发异常来终止程序的运行:
Go
func loadConfig(path string){
panic("can't load the config file on path " + path)
}
一个良好的程序最好不要主动调用 panic
函数,尤其是开发类库的时候,最好通过返回 error
类型来告诉调用者发生了什么错误,而不是触发 panic
导致程序终止运行。
recover函数
当发生 panic
异常时,如果不捕获得异常,那么程序就是终止运行,在Go语言中,可以用 defer
语句和 recover
函数的模式来捕获 panic
异常:
Go
package main
import (
"fmt"
"log"
)
func main() {
n1 := FindElementByIndex(1)
fmt.Println(n1)
n2 := FindElementByIndex(4)
fmt.Println(n2)
}
func FindElementByIndex(index int) int {
defer func() {
if e := recover(); e != nil {
log.Fatal(e)
}
}()
s := []int{1, 2, 3, 4}
return s[index]
}
总结
本文深入探讨了Go语言的错误策略与异常机制。主要介绍了错误处理的重要性,以及Go语言中的错误类型和处理函数。此外还讨论了Go语言的异常机制,包括panic和recover函数的使用。通过合理的错误处理和异常处理,我们可以提高代码的可维护性和可靠性,减少潜在的bug和故障。希望本文对您有帮助,感谢阅读~