GO 从 0 到 1 系列:9 错误+延迟执行+panic&recover+单步调试

错误+延迟执行+panic&recover+单步调试

1 error 接口

Go 语言通过 error 接口实现错误处理的标准模式,通过使用函数返回值列表中的最后一个值返回错误信息,将错误的处理交由程序员主动进行处理

在程序中运行时错误错误一般分为两种:

  • 可恢复的错误(重试/或忽略)
  • 不可恢复的错误(程序退出)

范例代码:

package main

import (
    "fmt"
    "strconv"
)

func main() {
    // 需要给调用者返回错误信息
    // 通过函数最后一个返回值返回错误!
    // 调用者需要对错误进行检查,决定如何操作

    v, err := strconv.Atoi("123")
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(v)
    }
}
[14:35:31 root@go day1]#go run main.go 
123

1.2 error 接口初始化方法

下面两种效果其实是一样的,并没有区别

1.2.1 第一种初始化 error 方式

通过 fmt.Errorf 方法创建

定义函数实现 error 范例:

package main

import (
    "fmt"
)

// error 接口类型
func div(left, right int) (int, error) {
    if right == 0 {
        // 在 go 中通过, fmt.Errorf 就看返回错误
        return 0, fmt.Errorf("right num is zeror")
    }

    return left / right, nil
}

func main() {
    res, err := div(10, 0)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(res)
    }
}
[14:38:05 root@go day1]#go run main.go 
right num is zeror

1.2.2 第二种初始化 error 方法

通过 errors 包的 New 方法创建

范例代码:

package main

import (
    "errors"
    "fmt"
)

// error 接口类型
func div(left, right int) (int, error) {
    if right == 0 {
        // 在 go 中通过, errors.New 即可返回错误
        return 0, errors.New("参数传递错误")
    }

    return left / right, nil
}

func main() {
    res, err := div(10, 0)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(res)
    }
}
[14:50:42 root@go day1]#go run main.go 
参数传递错误

1.3 对 error 错误进行处理

看下面代码,如果说传递个 div 的参数是错误的话,我们可以输出提示错误信息,从而后续可以对代码进行调试

package main

import (
    "errors"
    "fmt"
)

// error 接口类型
func div(left, right int) (int, error) {
    if right == 0 {
        return 0, errors.New("right num is 0")
    }

    return left / right, nil
}

func main() {
    if res, err := div(10, 0); err != nil {
        fmt.Println("value is error :", err)
    } else {
        fmt.Println(res)
    }
}
[14:52:25 root@go day1]#go run main.go 
value is error : right num is 0

2 defer 延迟执行-压栈

defer 关键字用户声明函数,不论函数是否发生错误都在函数执行最后执行(return 之前),若使用 defer 声明多个函数,则按照声明的顺序,先声明后执行(堆)常用来做资源释放,记录日志关闭文件以及关闭链接等工作

我们可以看到通过下面代码,defer 的执行顺序是最后执行,也就是说当函数存在其他代码块的时候,优先执行其他代码块,最后在执行 defer ,而且当有多个 defer 的时候是先进后出

package main

import "fmt"

func main() {
    fmt.Println("start")

    defer func() {
        fmt.Println("defer")
    }()

    defer func() {
        fmt.Println("12")
    }()

    fmt.Println("END")
}
[14:57:43 root@go day1]#go run main.go 
start
END
12
defer

通过输出,我们会还想 fmt.Println("defer") 是先进,但是后出

所以 defer 的执行顺序是先进后出,谁先声明谁后执行

2.1 defer 细节1

我们可以通过下面代码发现, defer 并不受循环影响。

package main

import "fmt"

func main() {
    for i := 0; i < 2; i++ {
        fmt.Println("start")
        defer func() {

            // 这里的  i 寻找的是 for 循环的 i 
            fmt.Printf("defer %d\n", i)
        }()
        fmt.Println("stop")
    }
}
[15:00:21 root@go day1]#go run main.go 
start
stop
start
stop
defer 2
defer 2

输出结果看到,defer 2 ,为什么会是 2 呢,因为 defer 在使用的时候其实使用了外部的变量,所以最后 i 成 2 了,由于 for 循环完毕执行了 i++,而且在这个代码中只有一个匿名函数,我在上面说过 defer 最后执行,所以这里 defer 自然就被压到了最后执行,通过输出就是两次 defer 2 在最后,因为这里在 for 循环中 defer 循环了两次

可以通过下面代码解决上面问题

每次执行完了之后我们将 i 传递到 defer func() 中去,因为这里将 i 变量传递到了 defer 匿名函数中,所以这里的 i 不再是最后的那个 i++

package main

import "fmt"

func main() {
    for i := 0; i < 2; i++ {
        fmt.Println("start")
        defer func(i int) {
            fmt.Printf("defer %d\n", i)
        }(i)
        fmt.Println("stop")
    }
}
[15:06:01 root@go day1]#go run main.go 
start
stop
start
stop
defer 1
defer 0

延迟执行或者闭包的时候,参数传递的时候一定要注意

2.2 defer 细节2

在延迟执行中尽量不要修改返回值,因为 defer 开启匿名函数然后我们的值就会在匿名函数中被修改

package main

import "fmt"

func test() (i int) {
    // 在延迟执行中尽量不要修改返回值

    i = 1
    defer func() {
        fmt.Println("defer")

        // 在 defer 中将 i 修改为 2
        i = 2
    }()
    return i
}

func main() {
    fmt.Println(test())
}
[15:07:42 root@go day1]#go run main.go 
defer
2

3 panic & recover

go 语言提供 panic 和 recover 函数用于处理运行时错误,当调用 panic 抛出错误,中断原有的控制流程,常用于不可修复性错误。recover 函数用于终止错误处理流程,仅在 defer语句的函数中有效,用于截取错误处理流程,recover 只能捕获到最后一个错误

初识 panic

panic 其实是在程序员执行程序的时候用来抛出错误

package main

func main() {
    panic("我是错误")
}
[15:10:12 root@go day1]#go run main.go 
panic: 我是错误

goroutine 1 [running]:
main.main()
        /root/project/2022/day1/main.go:4 +0x39
exit status 2

当出现错误,一般要用延迟执行加 recover

package main

import "fmt"

func main() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("出错了,进行恢复", err)
        }
    }()
    panic("我是错误")
}
[15:12:30 root@go day1]#go run main.go 
出错了,进行恢复 我是错误

假如我们把 panic 注释掉就不抛出错误,因为 recover 只能够捕获 panic

package main

import "fmt"

func main() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("出错了,进行恢复", err)
        }
    }()
//  panic("我是错误")
}

通过函数调用实现控制 recover 和 panic

package main

import "fmt"

func test(b bool) {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("出现错误:", err)
        }
    }()

    if b {
        fmt.Println("允许...")
    } else {
        panic("panic err")
    }

}

func main() {
    test(true)      // b 为 true 就执行允许
    test(false)     // b 为 false 执行 panic err
}

可以通过这种方式来控制程序的执行逻辑

[23:47:31 root@go day1]#go run main.go 
允许...
出现错误: panic err

recover 还能够捕获其他函数中的 painc

package main

import "fmt"

func callback(b bool) {
    // 如果 b 为 true 就执行 running,否则就是 panic 报错
    if b {
        fmt.Println("running...")
    } else {
        fmt.Println("callback panic")
    }
}

func test(b bool) {
    // defer 在函数中最后执行, 所以这里会先执行 callback 函数用来判断 b 是否为 true 或者 false
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("recover err", err)
        }
    }()
// 调用 callback 函数
    callback(b)

}

func main() {
    test(true)
    test(false)
}

通过输出发现 recover 也能捕获到其他函数的错误

[23:50:53 root@go day1]#go run main.go 
running...
callback panic

3.1 recover 的处理

recover 的处理,一般会用 recover 捕获到错误,然后进行返回

package main

import "fmt"

func callback(b bool) {
    if b {
        panic("callback err panic") // b 为 true 返回 panic
    } 
    fmt.Println("running")
}

// 定义返回值为 error 类型
func test(b bool) (err error) {
    defer func() {
        if msg := recover(); msg != nil { // 定义 msg 接收 recover 的返回值,捕获panic
            err = fmt.Errorf("%s", msg)   // 通过 fmt.Errorf 接受到有 callback 传递的 panic 错误值并传递给 err 
        }
    }()
    callback(b)

    // 返回命名返回值,可以看到 return 的时候其实是一个空因为他会自动返回 recover 在 callback 函数中捕获到 panic 的值
    return
}

func main() {
    fmt.Println(test(true))
    fmt.Println(test(false))
    fmt.Println(test(false))

}
[16:17:23 root@go day1]#go run main.go 
callback err panic
running
<nil>
running
<nil>
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇