正确处理错误,可以让程序更加稳定。error是一个错误接口,一般来说,error是不会影响到整个程序运行的,我们可以自行处理。
而panic是会导致程序直接崩溃退出的,我们也可以用 go自带的recover()
来恢复panic,在程序崩溃前进行资源的释放工作。
错误
error接口
error接口只有一个Error方法,返回具体的错误信息(string)
type error interface {
Error() string
}
一个字符串转整数的例子:
func main(){
// try to conver alpha "a" to integer
i, err := strconv.Atoi("a")
if err != nil {
fmt.Println(err)
} else {
fmt.Println(i)
}
}
尝试将字符 a 转换成为 整数,显然是不成功的,因此返回了err
一般而言,error 接口用于当方法或者函数执行遇到错误时进行返回,而且是第二个返回值。通过这种方式,可以让调用者自己根据错误信息决定如何进行下一步处理。
error 工厂函数
除了可以使用其他函数,自己定义的函数也可以返回错误信息给调用者
自定义error
自定义error就是先自定义一个新的类型,然后实现error接口
// 自定义一个结构体,携带错误码和具体的错误信息
type commonError struct {
errorCode int // 错误码
errorMsg string // 错误信息
}
// 实现error接口
func (ce *commonError) Error() string {
return ce.errorMsg
}
error 断言
func add(a, b int) int, error {
if a < 0 || b < 0 {
return 0, &commonError{
errorCode: 1,
errorMsg: "a and b must >= 0"
}
}
return a + b, nil
}
func main(){
sum, err := add(-1, 2)
// 错误类型断言
if cm, ok := err.(*commonError); ok {
fmt.Printf("Error code = %d, error message = %s\n", cm.errorCode, cm.errorMsg)
} else {
fmt.Printf("Sum = %d\n", sum)
}
}
错误嵌套 Error Wrapping
需求,将一个错误嵌套到另一个错误中:
比如调用一个函数,返回了一个错误信息 error,在不想丢失这个 error 的情况下,又想添加一些额外信息返回新的 error。这时候,我们首先想到的应该是自定义一个 struct,如下面的代码所示:
type MyError struct {
err error // 指向原有的error
msg string // 当前error的错误信息
}
// 上述就是错误的嵌套
// MyError 实现error接口,返回当前错误信息,和原有错误信息
func (me *MyError) Error() string {
return fmt.Sprintf("Wrapped error: %s -> %s", me.err.Error(), me.msg)
}
func main(){
newError := MyError{err, "New error message."}
}
上述实现方式比较繁琐,从Go 1.13开始,go标准库新增了Error Wrapping功能,我们可以使用 fmt.Errorf 加 %w 的方式,实现错误的嵌套
e := errors.New("Original Error")
w := fmt.Errorf("Wrapped Error: %w", e)
fmt.Println(w)
errors.Unwrap 函数
通过 errors.Unwrap 解开嵌套的错误
errors.Unwrap(w) // w 是一个嵌套的错误,会返回嵌套前的原始错误
errors.Unwrap(e) // e 是一个原始的错误,会返回一个nil
errors.Is 函数
使用 Error Wrapping 之后,原来使用 ==
判断是否同一个错误的方式失效了
使用 errors.Is
:
func Is(err, target error) bool
- 如果err和target 是同一个,返回true
- 如果err是一个wrapping error,且target也包含在这个嵌套的error链中的话,也返回true
errors.As 函数
errors.As是嵌套error的断言
var cm *commonError
if errors.As(err,&cm){
fmt.Println("错误代码为:",cm.errorCode,",错误信息为:",cm.errorMsg)
} else {
fmt.Println(sum)
}
Deffered 函数
defer 函数用于保证打开的文件一定会被关闭,功能有点类似于 python 的 with
defer 的执行关系是先调用后执行
defer func() {fmt.Println("A")}()
defer func() {fmt.Println("B")}()
defer func() {fmt.Println("C")}()
// 最后输出C、B、A
defer 有一个调用栈,越早定义越靠近栈的底部,越晚定义月靠近栈的顶部,执行这些defer语句的时候,会先从栈顶弹出一个defer然后执行它。
Panic 异常
go语言是一门静态的强类型语言,很多问题都尽可能地在编译是捕获,但是有一些只能在运行时检查,比如数组越界访问、不相同的类型强制转换等,这类运行时问题都会引起panic异常。
panic异常直接将运行中的程序中断
panic可以接受 interface{}
类型的参数(空接口),任何类型的值都可以传到panic
func panic(v interface{})
panic 异常是一种非常严重的情况,会让程序中断运行,使程序崩溃,所以如果是不影响程序运行的错误,不要使用 panic,使用普通错误 error 即可
使用Recover捕获panic异常
通常情况下,不需要对panic进行处理,直接让其中断程序即可
但当我们需要在程序崩溃前做一些资源释放处理时,这时就得从panic恢复,然后进行处理
我们可以使用go语言内置的recover函数恢复panic异常,只有defer修饰的函数才能在程序崩溃时执行,所以:
recover函数要结合defer关键字使用才能生效
func connectMysql(ip, username, password string) {
if ip == "" {
panic("Invalid ip, expect not empty string.")
} else {
fmt.Printf("Connecting to ip[] with %s@%s\n", ip, username, password)
}
}
func main(){
defer func(){
if p := recover(); p != nil {
fmt.Printf("go panic: %v", p)
}
}()
connectMysql("", "root", "root")
}