1.延迟调用

1.1 关键字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
}

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

package main

import \"fmt\"

func test(x int) {
	defer fmt.Println(\"a\")
	defer fmt.Println(\"b\")

	defer func() {
		fmt.Println(100 / x) // div0 异常未被捕获,逐步往外传递,最终终⽌止进程。
	}()

	defer fmt.Println(\"c\")
}

func main() {
	test(0)
}

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

package main

import \"fmt\"

func test() {
	x, y := 10, 20

	defer func(i int) {
		println(\"defer:\", i, y) // y 闭包引用
	}(x) // x 被复制

	x += 10
	y += 100
	fmt.Println(\"x =\", x, \"y =\", y)
}

func main() {
	test()
}

1.4 滥用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

2. 错误处理

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

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

    panic(\"panic error!\")
}

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

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

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

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

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

    panic(\"test panic\")
}

func main() {
    test()
}
//输出:
defer panic

2.4 捕获函数 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

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

func except() {
    recover()
}

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

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

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

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

        z = x / y
        return
    }()

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

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

type error interface {
    Error() string
}

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

package main

import (
	\"errors\"
	\"fmt\"
)

var ErrDivByZero = errors.New(\"division by zero\")

func div(x, y int) (int, error) {
	if y == 0 {
		return 0, ErrDivByZero
	}

	return x / y, nil
}

func main() {
	switch z, err := div(10, 0); err {
	case nil:
		fmt.Println(z)
	case ErrDivByZero:
		panic(err)
	}
}

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

收藏 打印