【go】异常处理

知识点:
1.异常处理 defer,panic,recover
2.error
3.自定义错误类型


异常处理:

在异常处理方面,Go语言不像其他语言,使用try..catch.. finall…, 而使用defer, panic, recover,将异常和控制流程区分开。即通过panic抛出异常,然后在defer中,通过recover捕获这个异常,最后处理。

但是更加推荐的错误处理方法:
Golang中我们通常会在函数或方法中返回error结构对象来判断是否有异常出现,并且可以更具需要自定义各种类型的error。如果返回的 error 值为 nil,则表示未遇到错误,否则 error 会返回一个字符串,用于说明遇到了什么错误。

type error interface {
    Error() string
}

golang 中内置的错误类型 error 是一个接口类型,自定义的错误类型必须实现 Error()方法

如何生成error?

方式一:New方法 原生
将字符串 text 包装成一个 error 对象返回

func New(text string) error {
    return &errorString{text}
}

//例如
var ErrShortWrite = errors.New("short write")

方式二:定义自己的错误

package main

import (
    "fmt"
    "time"
)

// MyError is an error implementation that includes a time and message.
type MyError struct {
    When time.Time
    What string
}

func (e MyError) Error() string {
    return fmt.Sprintf("%v: %v", e.When, e.What)
}

func oops() error {
    return MyError{
        time.Date(1989, 3, 15, 22, 30, 0, 0, time.UTC),
        "the file system has gone away",
    }
}

func main() {
    if err := oops(); err != nil {
        fmt.Println(err)
    }
}

defer:

defer是Go语言中的延迟执行语句,用来添加函数结束时执行的代码,常用于释放某些已分配的资源、关闭数据库连接、断开socket连接、解锁一个加锁的资源。

(1)defer执行顺序:

return xxx:赋值指令 + CALL defer指令 + RET指令

return xxx会被改写成:
返回值 = xxx
调用defer函数
空的return

整个return过程,没有defer之前,先在栈中写一个值,这个值会被当作返回值,然后再调用RET指令返回。return xxx语句汇编后是 1.先给返回值赋值,2.再做一个空的return,( 赋值指令 + RET指令)。defer的执行是被插入到return指令之前的,有了defer之后,就变成了(赋值指令 + CALL defer指令 + RET指令)。
而在CALL defer函数中,有可能将最终的返回值改写了…也有可能没改写。总之,return xxx不是一条原子指令,如果返回值改写了,那么看上去就像defer是在return xxx之后执行的~

example1

func f() (result int) {
    defer func() {
        result++
    }()
    return 0
}

返回值:1

example2

func f() (r int) {
     t := 5
     defer func() {
       t = t + 5
     }()
     return t
}

返回值:5

example3

func f() (r int) {
    defer func(r int) {
          r = r + 5
    }(r)
    return 1
}

返回值:1

example4

func f() (r int) {
    return 1
    defer func() {
        r++
        r++
    }()

    return 2
}

返回值:1
注意:如果去掉了return 2,会出现语法错误:Missing return at the end of function 

example5

func main() {
    for i:=0 ;i<5; i++ {
       defer func() {
           fmt.Println(i)
       }()
    }
    fmt.Println("test:")
}

输出:
test:
5
5
5
5
5

func main() {
    for i:=0 ;i<5; i++ {
       defer func(i int) {
           fmt.Println(i)
       }(i)
    }
    fmt.Println("test:")
}

输出:
test:
4
3
2
1
0

(2)defer栈

如果一个函数中有多个defer语句,它们会以LIFO(后进先出)的顺序执行。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执。
package main //必须

import "fmt"

func test(x int) {
    result := 100 / x
    fmt.Println("result = ", result)
    defer fmt.Println("three")
}

func main() {
    defer fmt.Println("one")
    defer fmt.Println("two")

    //调用一个函数,导致内存出问题,除数不能为0
    defer test(0)

    defer fmt.Println("four")
}

注:defer 需要放在 panic 之前定义,因为由panic引发异常以后,程序停止执行,即不再执行下面代码,放在后面调用不了defer

输出结果:
image.png


panic:

当程序遇到致命错误导致无法继续运行时就会触发panic,例如:数据越界,空指针等。我们可以通过主动调用 panic() 函数,抛出致命的错误。
通过使用recover()函数,我可以捕获上述的错误异常

golang 的错误处理流程:当一个函数在执行过程中出现了异常或遇到
panic(),正常语句就会立即终止,然后执行 defer 语句,再报告异
常信息,最后退出 goroutine。如果在 defer 中使用了 recover()
函数,则会捕获错误信息,使该错误信息终止报告。

panic会在调用它的函数中向本层和它的所有上层逐级抛出,若一直没有recover将其捕获,程序退出后会产生crash。

例如:

package main //必须

import "fmt"

func test(x int) {
    defer func() {
        if err:=recover(); err!=nil{
            fmt.Println(err)
        }
    }()
    result := 100 / x
    fmt.Println("result = ", result)
}

func main() {
    //调用一个函数,导致内存出问题,除数不能为0
     test(0)
    defer fmt.Println("end")
}

输出:
runtime error: integer divide by zero
end

注意:
1.defer要定义在panic之前
2.recover()的调用仅当它在defer函数被直接调用时才有效。在正常的执行过程中,调用 recover 会返回 nil 并且没有其他任何效果.
参考:http://www.coder55.com/articl…


发表评论

您的电子邮箱地址不会被公开。