错误处理"/>
Go语言中的错误处理
目录
- Go语言中的错误处理与资源管理
- defer调用实现资源管理
- defer特点
- 何时使用defer调用
- defer和return结合
- 错误处理
- 错误类型
- 自定义错误
- 错误处理的方式
- Go语言宕机
- defer和panic
- 宕机恢复
- recover捕获宕机
- 函数调用过程中panic和recover
Go语言中的错误处理与资源管理
defer调用实现资源管理
defer关键字允许我们将某个语句或函数延迟到函数返回之前才发生,常用于释放某些已分配的资源。
defer特点
-
能够确保调用在函数结束时发生
-
参数在defer语句时计算
-
defer列表为后进先出,类似栈。
for i := 0; i <= 30; i++ {defer fmt.Println(i) } // 打印30到0
何时使用defer调用
- Open/Close
- Lock/Unlock
- PrintHeader/PrintFooter
defer和return结合
- defer与return同时存在时,要把return理解成两条执行指令的结合(不是原子指令),一个指令是给返回值赋值,另一个指令是跳出该函数。
- defer和return同时存在时,整体执行顺序如下
- 先给返回值赋值
- 然后执行defer
- 最后跳出函数
没有定义返回值接收变量,执行defer时返回值已经赋值
func f() int { // 没有定义返回值接收变量i := 0defer func() {i = i + 2}()return i
}func main() {fmt.Println(f()) // 0
}
声明接收返回值变量,执行defer语句时修改了返回值内容
func f() (i int) { // 没有定义返回值接收变量defer func() {i = i + 2}()return i
}func main() {fmt.Println(f()) // 2
}
错误处理
我们在编写程序时,为了加强程序的健壮性,往往会考虑到对程序中可能出现的错误和异常进行处理。
错误类型
-
Go语言中使用
builtin
包下error接口作为错误类型,error接口只包含了一个方法,返回值是string,表示错误信息。官方源码定义如下:// 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 }
-
Go语言设计者认为类似
try-catch-finally
的传统异常处理机制很容易造成开发者对异常机制的滥用,从而使代码结构变得很混乱。因此,Go语言中会使用多值返回来返回错误。 -
在Go语言标准库的errors包中提供了error接口的实现结构体
errorString
,还额外提供了快速创建错误的函数。package errors// New returns an error that formats as the given text. // Each call to New returns a distinct error value even if the text is identical. func New(text string) error {return &errorString{text} }// errorString is a trivial implementation of error. type errorString struct {s string }func (e *errorString) Error() string {return e.s }
-
如果错误信息由很多变量组成,可以借助
fmt.Errorf(format string, a ...interface{})
完成错误信息格式化,因为底层还是errors.New()
// Errorf formats according to a format specifier and returns the string // as a value that satisfies error. func Errorf(format string, a ...interface{}) error {return errors.New(Sprintf(format, a...)) }
自定义错误
-
使用errors包的New函数来创建自定义错误:
err := errors.New("this is an error")var err2 errorfmt.Println(err.Error())fmt.Println(err2) // error的类型变量在声明后其默认值为nil
执行结果:
this is an error
-
使用fmt包中的
Errorf
函数来设置错误内容的格式。if _, _, line, ok := runtime.Caller(0); ok { // 错误所在位置err := fmt.Errorf("***Line %d error***", line)fmt.Println(err)}
在程序中,我们通过调用runtime包的Caller方法获取当前代码所在行数,用来模拟错误的产生。
错误处理的方式
file, err := os.Open("abc.txt")if err != nil {if pathError, ok := err.(*os.PathError); ok {fmt.Println(pathError.Err)} else {fmt.Println("unknown error", err)}} else {fmt.Println(file)}
执行上面的函数,执行结果如下:
The system cannot find the file specified.
由于源代码所在文件下没有abc.txt文件,系统未找到指定的文件,因此使用os.Open
函数打开失败。
使用if处理错误,这就是Go语言编程中最常见的错误处理方式之一。
Go语言宕机
panic
方法是Go语言的一个内置函数。panic
有点类似其他编程语言的throw,抛出异常。当执行到panic后,停止当前函数的执行,一直向上返回,执行每一层的defer,如果没有遇见recover,程序退出,并打印错误栈信息。
panic的源码如下:
func panic(v interface{})
我们可以传入任意类型的值作为宕机内容。
一般而言,只有当程序发生不可逆的错误时,才会使用panic
方法来触发宕机。如果遇到以下情形,可以调用panic方法来退出程序:
- 程序处于失控状态且无法恢复,继续执行将影响其他正常程序,引发操作系统异常甚至是死机。
- 发生不可预知的错误。
defer和panic
在使用panic方法触发宕机后,且在退出函数前,会调用延迟执行语句defer。
func protect() edefer func() {fmt.Println("func protect exit")}()panic("Serious bug") // 触发宕机
}func main() {defer func() {fmt.Println("func main exit")}()protect()fmt.Println("Invalid code")
}
上面代码执行结果如下:
以上程序执行流程如下:
- protect函数内的panic触发宕机。
- 由于protect函数内的匿名函数通过defer语句延迟执行,在panic方法触发宕机后,且在退出protect函数前,会执行protect函数中的匿名函数,打印”func protect exit"。
- 由于main函数内的匿名函数通过defer语句延迟执行,在main函数退出前会执行main函数中的匿名函数,打印"func main exit"。
- 程序退出。
宕机恢复
recover捕获宕机
Go语言通过内置函数recover来捕获宕机,类似于其他编程语言中的try-catch
机制。recover源码如下:
func recover() interface{}
由于defer语句延迟执行的特性,我们可以通过“defer语句+匿名函数+recover方法”来完成对宕机的捕获。如果没有panic信息返回nil,如果有panic,recover会把panic状态取消,恢复程序的正常运行。
func protect() {defer func() {if err := recover(); err != nil {fmt.Println(err)}}()panic("Serious bug")
}func main() {protect()fmt.Println("Invalid code")
}
程序的执行结果如下:
函数调用过程中panic和recover
func demo1() {fmt.Println("demo1上半部分")demo2()fmt.Println("demo1下半部分")
}func demo2() {defer func() {recover() // 此处进行恢复}()fmt.Println("demo2上半部分")demo3()fmt.Println("demo2下半部分")
}func demo3() {fmt.Println("demo3上半部分")panic("在demo3出现了panic")fmt.Println("demo3下半部分")
}func main() {fmt.Println("程序开始")demo1()fmt.Println("程序结束")
}
以上程序的执行结果如下:
- recover()只能恢复当前函数级或当前函数调用函数中的panic(),恢复后当前级函数结束,但是调用此函数的函数可以继续执行。
- panic会停止当前函数的执行,然后一直向上返回,如果没有遇见recover,程序退出,并打印错误栈信息。
更多推荐
Go语言中的错误处理
发布评论