目录
1、Go有类似于 try{}cache{} 一样的错误处理机制吗?
一、Go的错误处理机制
1、Go有类似于 try{}cache{} 一样的错误处理机制吗?
回答:没有。
这个Go的三位创始人是有关系的,他们坚持认为程序员对于异常的随意使用,包括try{}cache{}finally{} 的结构对于代码的影响会很大,他们更喜欢 C 的这种对于返回值的判断。
另一个理由就是 Go 支持了多返回值的结构,对于第二返回值的判断可以比较方便的处理错误。
2、与其它主要编程语言的差异
- 没有异常机制
- error 类型实现了 error interface
- 可以公共 errors.New() 来快速创建错误实例
3、error 接口
type error interface {
Error() string
}
4、错误处理示例
场景:我们以获取斐波拉西数列为例。
package error
import (
"errors"
"testing"
)
//获取斐波拉西数列
func GetFibonacci(num int) ([]int, error) {
//通过 errors.new() 方法创建错误并返回给调用者
if num < 2 {
return nil, errors.New("num should be greater than 2")
}
fibList := []int{1, 1}
for i := 2; i < num ; i++ {
fibList = append(fibList, fibList[i-2] + fibList[i-1])
}
return fibList, nil
}
//调用 GetFibonacci() 方法,并对错误进行处理
func TestFibonacci(t *testing.T) {
var (
err error
list []int
)
//及早失败,避免嵌套
//我们最好是先判断 err != nil,如果有错误尽早输出错误信息,然后return,
//这样做可以防止 if 语句嵌套的太深,增加代码的可读性
if list, err = GetFibonacci(-10); err != nil {
t.Log(err)
return
}
//只有当没有任何错误的时候,最后输出结果
t.Log(list)
}
5、预制错误
package error
import (
"errors"
"fmt"
"testing"
)
//在 package 里面定义两个预制的错误,方便做字符串的比较判断
var LessThanTwoError = errors.New("num should be greater than 2")
var GreaterThanOneHundredError = errors.New("num should be less than 100")
//获取斐波拉西数列
func GetFibonacci(num int) ([]int, error) {
//通过 errors.new() 方法创建错误并返回给调用者
if num < 2 {
return nil, LessThanTwoError
}
if num > 100 {
return nil, GreaterThanOneHundredError
}
fibList := []int{1, 1}
for i := 2; i < num ; i++ {
fibList = append(fibList, fibList[i-2] + fibList[i-1])
}
return fibList, nil
}
//调用 GetFibonacci() 方法,并对错误进行处理
func TestFibonacci(t *testing.T) {
var (
err error
list []int
)
//及早失败,避免嵌套
//我们最好是先判断 err != nil,如果有错误尽早输出错误信息,然后return,
//这样做可以防止 if 语句嵌套的太深,增加代码的可读性
if list, err = GetFibonacci(-10); err != nil {
//通过预制错误来进行判断,调用者在判断错误时,相对来说会比较简单
if err == LessThanTwoError {
fmt.Println("It's less than 2")
num := 3
t.Log(num)
}
return
}
//只有当没有任何错误的时候,最后输出结果
t.Log(list)
}
6、总结
- Go不支持异常机制,异常处理是通过 errors interface 来进行返回,然后让调用者进行判断;
- 通过 errors.new() 方法创建错误;
- 调用者处理异常时,要遵循及早失败,避免嵌套的原则;
- 调用者如果需要做错误判断,最好用预制错误的方式,比起用字符串判断,更加不易出错。
二、os.Exit、panic 和 recover
1、os.Exit
- os.Exit退出时不会调用 defer 指定的函数;
- os.Exit 退出时不输出当前调用栈信息。
//调试 os.Exit() 函数的效果
func TestOsExit(t *testing.T) {
defer func() {
fmt.Println("This is defer function")
}()
fmt.Println("start")
os.Exit(-1) // Process finished with exit code 1
fmt.Println("end")
}
运行程序,我们会发现 os.Exit() 不会调用 defer() 函数,也没有调用栈的信息,只是输出了一个错误码。
2、panic
- panic用于不可恢复的错误;
- panic退出前会执行 deffer 函数中的内容。
package panic_recover
import (
"errors"
"fmt"
"os"
"testing"
)
//调试 panic() 函数的效果
func TestPanic(t *testing.T) {
defer func() {
fmt.Println("This is defer function")
}()
fmt.Println("start")
panic(errors.New("something wrong"))
fmt.Println("end")
}
/*
start
This is defer function
--- FAIL: TestPanic (0.00s)
panic: something wrong [recovered]
panic: something wrong
goroutine 6 [running]:
testing.tRunner.func1.1(0x1120560, 0xc000052520)
/usr/local/go/src/testing/testing.go:1076 +0x30d
testing.tRunner.func1(0xc000001380)
*/
运行函数,我们发现 panic 会将调用栈也打印出来,而且会调用 defer 函数中的内容。
3、recover
package panic_recover
import (
"errors"
"fmt"
"testing"
)
//调试 recover() 函数的效果
func TestRecover(t *testing.T) {
defer func() {
//当抛出了 panic() 错误,我们又不希望程序中断和退出,
//可以通过 recover() 来修复错误。
if err := recover(); err != nil {
//修复错误
t.Log("通过 recover 来修复错误")
}
}()
fmt.Println("start")
panic(errors.New("something wrong"))
fmt.Println("end")
}
/*
start
panic_recover_test.go:19: 通过 recover 来修复错误
--- PASS: TestRecover (0.00s)
PASS
*/
注意 recover 的使用,最好不要在 recover() 中只记录一个日志或者忽略掉这个错误,然后什么都不做,比如如果是系统资源消耗完了,此时 recover 进行强制恢复,这样子会让我们的健康检查程序没办法检查出当前系统的问题,health check 只会检查当前的进程是在还是不在,这样就形成了僵尸服务进程。在这种情况下,我们最好使用一种可恢复的设计模式,例如 Let it crash,我们干脆让程序 crash 掉,然后让守护进程帮我们把程序重新启动起来,这往往是我们回复不确定错误的最好方法。
4、总结
- os.Exit退出时不会调用 defer() 指定的函数;
- os.Exit 退出时不输出当前调用栈信息。
- panic用于不可恢复的错误;
- panic退出前会执行 deffer() 函数中的内容。
- 当抛出了 panic() 错误,我们又不希望程序中断和退出,可以通过 recover() 来修复错误。
注:这篇博文是我学习中的总结,如有转载请注明出处:
https://blog.csdn.net/DaiChuanrong/article/details/118118967
上一篇:Go面向对象-拓展和多肽
下一篇:Go-package