Loading

Go语言精进之路读书笔记第20条——在init函数中检查包级变量的初始状态

20.1 认识init函数

init函数的特点:

  • 运行时调用,Go程序中不能显式调用
  • 顺序执行,等待一个init函数执行完毕并返回后再执行下一个init函数
  • 在整个Go程序生命周期内仅会被执行一次
  • 先被传递给Go编译器的源文件中的init函数先被执行,同一个源文件中的多个init函数按声明顺序依次执行。但不要依赖init函数的执行次序

20.2 程序初始化顺序

init函数的执行顺位排在其所在包的包级变量之后

20.3 使用init函数检查包级变量的初始状态

1.重置包级变量值

// $GOROOT/src/flag/flag.go
// commandLineUsage使用了flag包的另一个导出包变量Usage
// 用户将自定义usage赋值给Usage后,就相当于改变了CommandLine变量的Usage
func init() {
    CommandLint.Usage = commandLineUsage
}

// $GOROOT/src/context/context.go

// closedchan是一个可重用的处于关闭状态的channel
var closedChan = make(chan struct{})
func init() {
    close(closedChan)
}

2.包级变量进行初始化,保证其后续可用

// $GOROOT/src/regexp/regexp.go
// Bitmap used by func special to check whether a character needs to be escaped.
var specialBytes [16]byte

// special reports whether byte b needs to be escaped by QuoteMeta.
func special(b byte) bool {
    return b < utf8.RuneSelf && specialBytes[b%16]&(1<<(b/16)) != 0
}
// 负责完成对内部特殊字节数组的初始化,这个特殊字节数组被包内的special函数使用,用于判断某个字符是否需要转义
func init() {
    for _, b := range []byte(`\.+*?()|[]{}^$`) {
        specialBytes[b%16] |= 1 << (b / 16)
    }
}

// $GOROOT/src/net/addrselect.go
// 对rfc6724policyTable这个未导出包级变量进行反转排序
func init() {
    sort.Sort(sort.Reverse(byMaskLength(rfc6724policyTable)))
}

// $GOROOT/src/net/http/h2_bundle.go
// 根据环境变量DEBUG的值对一些包级开关变量进行赋值
var (
    http2VerboseLogs    bool
    http2logFrameWrites bool
    http2logFrameReads  bool
    http2inTests        bool
)

func init() {
    e := os.Getenv("GODEBUG")
    if strings.Contains(e, "http2debug=1") {
        http2VerboseLogs = true
    }
    if strings.Contains(e, "http2debug=2") {
        http2VerboseLogs = true
        http2logFrameWrites = true
        http2logFrameReads = true
    }
}

3.init函数中的注册模式

lib/pq包访问PostgreSQL数据库。

import (
    "database/sql"
    _ "github.com/lib/pq"
)

func main() {
    db, err := sql.Open("postgres", "user=pqgotest dbname=pqgotest sslmode=verify-full")
    if err != nil {
        log.Fatal(err)
    }

    age := 21
    row, err := db.Query("SELECT name FROM users WHERE age = $1", age)
    ...
}

// github.com/lib/pq/conn.go
...
func init() {
    sql.Register("postgres", &Driver{})
}

空别名方式导入lib/pq的副作用就是Go运行时会将lib/pq作为main包的依赖包并会初始化pg包,于是pg包的init函数得以执行。

pg包将自己实现的SQL驱动(driver)注册到sql包中,当应用层代码在打开数据库的时候传入驱动的名字(这里是postgres),通过sql.Open函数返回的数据库实例句柄对应的就是pg这个驱动的相应实现。

这种注册模式实质是一种工厂设计模式的实现,sql.Open函数就是该模式中的工厂方法,它根据外部传入的驱动名称生产出不同类别的数据库实例句柄。

标准库image包获取各种格式的图片的宽和高。

4.init函数中检查失败的处理方法

init函数在检查包数据初始化状态时遇到失败或者错误的情况,建议直接调用panic或者log.Fatal等函数记录异常日志,让程序快速退出。

posted @ 2024-02-13 10:50  brynchen  阅读(4)  评论(0编辑  收藏  举报