Go异常处理

  一、error接口

    Go 语言通过内置的错误接口提供了非常简单的错误处理机制

    error类型是一个接口,其定义如下:    

type error interface {
    Error() string
}

    我们可以在代码中通过实现error接口来生成错误信息。

    任意结构体,只要实现了 Error 方法,就可以认为是 error 错误类型。

    如,errors.New方法返回的就是一个实现了error接口的结构体实例,因为它是error类型:

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
}

    Go提供了两种创建error的方法:

      ①:errors.New

      ②:fmt.Errorf

    不过函数通常在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息:

import (
    "errors"
    "fmt"
)

func Hello(name string) (string, error) {
    // If no name was given, return an error with a message.
    if name == "" {
        // return "", fmt.Errorf("empty name")
        return "", errors.New("empty name")
    }

    // If a name was received, return a value that embeds the name
    message := fmt.Sprintf("Hi, %v. Welcome!", name)
    return message, nil
}

    我们会约定最后返回值为 error 类型,一般常见于第二个返回值,这是一个约定俗成的习惯。

    因此在调用返回error的方法的时候要注意判断error是否为nil,如果不为nil,说明异常,要做异常处理:

import (
    "fmt"
    "log"
)

func main() {
    message, err := Hello("")
    // If an error was returned, print it to the console and
    // exit the program.
    if err != nil {
        log.Fatal(err)
    }

    // If no error was returned, print the returned message
    // to the console.
    fmt.Println(message)
}

  

  二、panic

    panic是一个Go内置函数,它用来停止当前常规控制流并启动panicking(运行时恐慌)过程。

    panic的触发有两种:

      ①:在运行时遇到错误触发 panic,比如越界访问数组,不相同类型的变量强制类型转换等

      ②:通过显式直接调用 panic 函数触发 panic

        func panic(v interface{}) panic panic函数接收一个interface{}空接口类型的参数,也就是说,panic函数可以接收一个任意类型的参数。  

func test() {
    // 显式调用panic
    panic("panic error!")
}

    怎么处理panic呢?

      异常的使用场景简单描述:Go中可以抛出一个panic的异常,然后在defer中通过recover() 捕获这个异常,将 panic 错误写入日志文件,将程序恢复正常执行

      调用recover() 函数可以捕获panic,即通过recover() 获取panic。

      但是recover() 函数仅在defer函数内部使用。所以想要使用recover捕获panic,我们需要结合defer一起使用。而且,程序在触发panic异常后,不会继续往下执行代码,只能执行defer调用的函数。 

func a() int{
    i:=0
    return 10/i
}

func main() {
    // 捕获处理panic
    defer func(){
        if r := recover(); r != nil {
            fmt.Printf("panic recover:%s", r)
        }
    }()
    // 触发panic
    a()
    // 不会再执行
    fmt.Println("123")
}

      recover()是Go语言一个内置函数,可以重新获取对一个运行时恐慌的 goroutine 的控制。recover 仅在 defer 延迟函数内部使用。在正常执行程序中,调用 recover 函数,将返回 nil。如果当前 goroutine 处于恐慌状态,调用 recover 会捕获提供给 panic 的值并恢复正常执行。

      通常,我们不会去捕获运行时 panic,发生 panic 异常,直接让程序崩溃即可,及时根据 panic 提供的信息,修复异常。但是,一些情况下,我们还是需要捕获 panic,比如在程序发生 panic 异常时,释放资源。比如关闭文件或者释放锁

    

    注意:

      在Go语言中,recover只在defer调用的函数中有效,并且defer要在panic之前先注册,否则不能捕获异常。当panic被捕获到后,被注册的函数将获得程序控制权

    

    Go实现类似 try catch 的异常处理:

/*
实现类似try catch处理,后续代码可正常执行
fun() 可能触发panic的函数
hander() panic处理函数
*/
func Try(fun func(), handler func(interface{})) {
    defer func() {
        if err := recover(); err != nil {
            handler(err)
        }
    }()
    fun()
}

func main() {
    Try(func() {
        panic("test panic")
    }, func(err interface{}) {
        fmt.Println(err)
    })

    // 后续代码可继续执行
    fmt.Println("panic handled")
}

  error和panic区别:

    error一般是程序员可预知的,会进行合适的处理,例如检测输入是否合法等

    而panic是程序员无法预知的异常,例如空指针或数组越界等

  总结:panic 导致的后果非常严重,会导致程序崩溃,所以我们在处理一些不会影响程序正确运行的错误时,尽量使用 error 处理错误

  

  三、throw

    这个错误类型,在用户侧是没法主动调用的,均为 Go 底层自行调用的,像是大家常见的 map 并发读写,就是由此触发

    

END.