Go 边看边练 -《Go 学习笔记》系列(七)

  |   2 评论   |   1,220 浏览

上一篇: [1438164538421]


ToC


3.5 延迟调用

关键字 defer 用于注册延迟调用。这些调用直到 return 前才被执行,通常⽤用于释放资源或错误处理。

func test() error {
    f, err := os.Create("test.txt")
    if err != nil { return err }

    defer f.Close() // 注册调用,而不是注册函数。必须提供参数,哪怕为空。

    f.WriteString("Hello, World!")        
    return nil
}

多个 defer 注册,按 FILO 次序执行。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行。

延迟调用参数在注册时求值或复制,可用指针或闭包 "延迟" 读取。

滥用 defer 可能会导致性能问题,尤其是在一个 "大循环" 里。

var lock sync.Mutex
      func test() {
      lock.Lock()
      lock.Unlock()
}

func testdefer() {
    lock.Lock()
    defer lock.Unlock()
}

func BenchmarkTest(b *testing.B) {
    for i := 0; i < b.N; i++ {
        test()
    }
}

func BenchmarkTestDefer(b *testing.B) {
    for i := 0; i < b.N; i++ {
        testdefer()
    }
}

输出:

BenchmarkTest" 50000000 43 ns/op
BenchmarkTestDefer 20000000 128 ns/op

3.6 错误处理

没有结构化异常,使用 panic 抛出错误,recover 捕获错误。

func test() {
    defer func() {
        if err := recover(); err != nil {
            println(err.(string)) // 将 interface{} 转型为具体类型。
        }
    }()

    panic("panic error!")
}

由于 panicrecover 参数类型为 interface{},因此可抛出任何类型对象。

func panic(v interface{})
func recover() interface{}

延迟调用中引发的错误,可被后续延迟调用捕获,但仅最后一个错误可被捕获。

func test() {
    defer func() {
        fmt.Println(recover())
    }()

    defer func() {
        panic("defer panic")
    }()

    panic("test panic")
}

func main() {
    test()
}

输出:

defer panic

捕获函数 recover 只有在延迟调用内直接调用才会终止错误,否则总是返回 nil。任何未捕获的错误都会沿调用堆栈向外传递。

func test() {
    defer recover() // 无效!
    defer fmt.Println(recover()) // 无效!
    defer func() {
        func() {
            println("defer inner")
            recover() // 无效!
        }()
    }()

    panic("test panic")
}

func main() {
    test()
}

输出:

defer inner
<nil>
panic: test panic

使用延迟匿名函数或下面这样都是有效的。

func except() {
    recover()
}

func test() {
    defer except()
    panic("test panic")
}

如果需要保护代码片段,可将代码块重构成匿名函数,如此可确保后续代码被执行。

func test(x, y int) {
    var z int

    func() {
        defer func() {
            if recover() != nil { z = 0 }
        }()

        z = x / y
        return
    }()

    println("x / y =", z)
}

除用 panic 引发中断性错误外,还可返回 error 类型错误对象来表示函数调用状态。

type error interface {
    Error() string
}

标准库 errors.Newfmt.Errorf 函数用于创建实现 error 接口的错误对象。通过判断错误对象实例来确定具体错误类型。

如何区别使用 panicerror 两种方式?惯例是:导致关键流程出现不可修复性错误的使用 panic,其他使用 error

下一篇: [1438311936449]



社区小贴士

  • 关注标签 [golang] 可以方便查看 Go 相关帖子
  • 关注标签 [Go学习笔记] 可以方便查看本系列
  • 关注作者后如有新帖将会收到通知

该文章同步自 黑客派

---- EOF ----
点击加入开源技术 Q 群 242561391,让学习和分享成为一种习惯!

评论

发表评论

validate