网站首页 > 基础教程 正文
最近校招又开始了,我也接到了一些面试工作,当我问「你觉得自己有什么优势」时,十个人里有八个的回答里会有一条「精力充沛,能加班」。
怪不得国家都给认证了:新生代农民工。合着我们这根本就不是什么脑力劳动者,而是靠出卖体力的苦劳力。
好了,废话不多说,肝文还确实需要体力。
这篇来说说 Go 的错误处理。
错误处理
错误处理相当重要,合理地抛出并记录错误能在排查问题时起到事半功倍的作用。
Go 中有关于错误处理的标准模式,即 error 接口,定义如下:
type error interface {
Error() string
}
大部分函数,如果需要返回错误的话,基本都会将 error 作为多个返回值的最后一个,举个例子:
package main
import "fmt"
func main() {
n, err := echo(10)
if err != nil {
fmt.Println("error: " + err.Error())
} else {
fmt.Println(n)
}
}
func echo(param int) (int, error) {
return param, nil
}
我们也可以使用自定义的 error 类型,比如调用标准库的 os.Stat 方法,返回的错误就是自定义类型:
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Error() string {
return e.Op + " " + e.Path + ": " + e.Err.Error()
}
暂时看不懂也没有关系,等学会了接口之后,再回过头来看这段代码,应该就豁然开朗了。
defer
延迟函数调用,defer 后边会接一个函数,但该函数不会立刻被执行,而是等到包含它的程序返回时(包含它的函数执行了 return 语句、运行到函数结尾自动返回、对应的 goroutine panic),defer 函数才会被执行。
通常用于资源释放、打印日志、异常捕获等。
func main() {
f, err := os.Open(filename)
if err != nil {
return err
}
/**
* 这里defer要写在err判断的后边而不是os.Open后边
* 如果资源没有获取成功,就没有必要对资源执行释放操作
* 如果err不为nil而执行资源执行释放操作,有可能导致panic
*/
defer f.Close()
}
defer 语句经常成对出现,比如打开和关闭,连接和断开,加锁和解锁。
defer 语句在 return 语句之后执行。
package main
import (
"fmt"
)
func main() {
fmt.Println(triple(4)) // 12
}
func double(x int) (result int) {
defer func() {
fmt.Printf("double(%d) = %d\n", x, result)
}()
return x + x
}
func triple(x int) (result int) {
defer func() {
result += x
}()
return double(x)
}
切勿在 for 循环中使用 defer 语句,因为 defer 语句不到函数的最后一刻是不会执行的,所以下面这段代码很可能会用尽所有文件描述符。
for _, filename := range filenames {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
}
一种解决办法是将循环体单独写一个函数,这样每次循环的时候都会调用关闭函数。
for _, filename := range filenames {
if err := doFile(filename); err != nil {
return err
}
}
func doFile(filename string) error {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
}
defer 语句的执行是按调用 defer 语句的倒序执行。
package main
import (
"fmt"
)
func main() {
defer func() {
fmt.Println("first")
}()
defer func() {
fmt.Println("second")
}()
fmt.Println("done")
}
输出:
done
second
first
panic 和 recover
一般情况下,在程序里记录错误日志,就可以帮助我们在碰到异常时快速定位问题。
但还有一些错误比较严重的,比如数组越界访问,程序会主动调用 panic 来抛出异常,然后程序退出。
如果不想程序退出的话,可以使用 recover 函数来捕获并恢复。
感觉挺不好理解的,但仔细想想其实和 try-catch 也没什么区别。
先来看看两个函数的定义:
func panic(interface{})
func recover() interface{}
panic 参数类型是 interface{},所以可以接收任意参数类型,比如:
panic(404)
panic("network broken")
panic(Error("file not exists"))
recover 需要在 defer 函数中执行,举个例子:
package main
import (
"fmt"
)
func main() {
G()
}
func G() {
defer func() {
fmt.Println("c")
}()
F()
fmt.Println("继续执行")
}
func F() {
defer func() {
if err := recover(); err != nil {
fmt.Println("捕获异常:", err)
}
fmt.Println("b")
}()
panic("a")
}
输出:
捕获异常: a
b
继续执行
c
F() 中抛出异常被捕获,G() 还可以正常继续执行。如果 F() 没有捕获的话,那么 panic 会向上传递,直接导致 G() 异常,然后程序直接退出。
还有一个场景就是我们自己在调试程序时,可以使用 panic 来中断程序,抛出异常,用于排查问题。
这个就不举例了,反正是我们自己调试,怎么爽怎么来就行了。
总结
错误处理在开发过程中至关重要,好的错误处理可以使程序更加健壮。而且将错误信息清晰地记录日志,在排查问题时非常有用。
Go 中使用 error 类型进行错误处理,还可以在此基础上自定义错误类型。
使用 defer 语句进行延迟调用,用来关闭或释放资源。
使用 panic 和 recover 来抛出错误和恢复。
使用 panic 一般有两种情况:
- 程序遇到无法执行的错误时,主动调用 panic 结束运行;
- 在调试程序时,主动调用 panic 结束运行,根据抛出的错误信息来定位问题。
为了程序的健壮性,可以使用 recover 捕获错误,恢复程序运行。
文章中的脑图和源码都上传到了 GitHub,有需要的同学可自行下载。
地址: https://github.com/yongxinz/gopher/tree/main/sc
Go 专栏文章列表:
- 开发环境搭建以及开发工具 VS Code 配置
- 变量和常量的声明与赋值
- 基础数据类型:整数、浮点数、复数、布尔值和字符串
- 复合数据类型:数组和切片 slice
- 复合数据类型:字典 map 和 结构体 struct
- 流程控制,一网打尽
- 函数那些事
猜你喜欢
- 2024-11-26 golang defer、panic、recover实践
- 2024-11-26 go-fastdfs文件上传
- 2024-11-26 OpenHarmony3.0在树莓派3B上的烧录与通讯
- 2024-11-26 go|bytes.buffer
- 2024-11-26 AOVX资产跟踪产品测试工具mPower1203如何使用python对接 (二)
- 2024-11-26 Python os.dup2() 方法是什么?os.du
- 2024-11-26 go中关于文件和json操作的知识点小结
- 2024-11-26 python3从零学习-5.8.4、mmap—内存映射文件支持
- 2024-11-26 29.Python 读取文件的六种方式
- 2024-11-26 macOS安装并设置五笔输入法
- 最近发表
- 标签列表
-
- gitpush (61)
- pythonif (68)
- location.href (57)
- tail-f (57)
- pythonifelse (59)
- deletesql (62)
- c++模板 (62)
- css3动画 (57)
- c#event (59)
- linuxgzip (68)
- 字符串连接 (73)
- nginx配置文件详解 (61)
- html标签 (69)
- c++初始化列表 (64)
- exec命令 (59)
- canvasfilltext (58)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- node教程 (59)
- console.table (62)
- c++time_t (58)
- phpcookie (58)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)