go的错误机制

  • golang没有异常机制
  • error类型实现了异常接口
type error interface {
  Error() string
}
  • 可以通过errors.New快速创建错误实例
errors.New("n must be ddxfsefsdf")

废话不多说, 来了解下go兼并python的优雅

import (
 "errors"
 "testing"
)

func GetFebnacci(n int) ([]int, error) {
 if n < 2 || n >= 100 {
  return nil, errors.New("n should be in [2, 100]")
 }

 fiblist := []int{1, 1}

 for i := 2; i < n; i++ {
  fiblist = append(fiblist, fiblist[i-2]+fiblist[i-1])
 }
 return fiblist, nil
}

func TestGetFebnacci(t *testing.T) {
 //t.Log(GetFebnacci(10))
 if v,err:=GetFebnacci(100);err!=nil{
  t.Error(err)
 }else {
  t.Log(v)
 }
}


最后输出:

=== RUN   TestGetFebnacci
    error_test.go:24: n should be in [2, 100]
--- FAIL: TestGetFebnacci (0.00s)

FAIL

注意下写法

自定义错误

我们为了开发时候能更清楚是哪里报错了,且封装一些信息出去,我们更倾向于自定义一些错误类型进行输出error msg 不多说: 上代码

var LessThanTwoError = errors.New("n should not less than 2")
var LargeThanHundredError = errors.New("n should not large than 100")

那么错误判断可以修改:

func GetFebnacci(n int) ([]int, error) {
 if n < 2 {
  return nil, LessThanTwoError
 }
 if n >= 100 {
  return nil, LargeThanHundredError
 }

 fiblist := []int{1, 1}

 for i := 2; i < n; i++ {
  fiblist = append(fiblist, fiblist[i-2]+fiblist[i-1])
 }
 return fiblist, nil
}
  • go倾向于快速报错,将容易出错的放在最前面 测试:
func TestGetFebnacci(t *testing.T) {
 //t.Log(GetFebnacci(10))
 if v, err := GetFebnacci(100); err != nil {
  if err == LessThanTwoError{
   fmt.Println("it is less")
  }
  t.Error(err)
 } else {
  t.Log(v)
 }
}

最佳实践

其实错误处理的最佳实践就是上面的,倾向于用自定义类型去处理,以便于判断错误类型

dosth return

panic和recover

panic

  • panic:用于不可恢复的错误
  • panic:退出前会执行defer指定的内容
os.Exit()
  • os比较牛逼,退出前是不会执行defer的内容的
  • os.Exit退出时候,不会输出当前调用栈信息

看代码:

func TestPanicVsExit(t *testing.T) {
 fmt.Println("start")
 os.Exit(-1)
}

这段代码输出:

=== RUN   TestPanicVsExit
start


Process finished with the exit code 1

panic接收一个空接口,通常我们会传递一个错误进去:

func TestPanicVsExit(t *testing.T) {
 fmt.Println("start")
 panic(errors.New("something wrong"))
}

输出:

=== RUN   TestPanicVsExit
start
--- FAIL: TestPanicVsExit (0.00s)
panic: something wrong [recovered]
 panic: something wrong
// 省略 go routine 的错误栈!!
  • 你运行下就知道,panic是会输出调用栈的信息的
  • os.Exit不会,啥都没

加上defer函数

func TestPanicVsExit(t *testing.T) {
 defer func() {
  fmt.Println("Finally!")
 }()
 fmt.Println("start")
 panic(errors.New("something wrong"))
}

看下输出:

=== RUN   TestPanicVsExit
start
Finally!
--- FAIL: TestPanicVsExit (0.00s)
panic: something wrong [recovered]
 panic: something wrong

goroutine 6 [running]:
// 省略调用栈的信息
  • 看到了吧~ defer函数是会在退出前执行的
try...catch...
defer func(){
  if err:=recover();err!=nil{
    // 恢复错误
  }
}()

上代码demo吧:

func TestPanicVsExit(t *testing.T) {

 defer func() {
  if err:=recover(); err != nil{
   fmt.Println("recover from:", err)
  }
 }()

 fmt.Println("start")
 //os.Exit(-1)
 panic(errors.New("something wrong"))
}

输出是什么呢?

=== RUN   TestPanicVsExit
start
recover from: something wrong
--- PASS: TestPanicVsExit (0.00s)
PASS

Process finished with the exit code 0

没有再报错了:pass 了 那我们可以说: panic被recover了(恐慌痛苦被恢复了? 怪怪的)

但是:方才不是说catch是不好?会出现什么问题?

  • 容易导致僵尸服务进程,导致heal check失效
  • let it crash ,让它报错,往往是我们恢复不确定性错误的最好方法