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等函数记录异常日志,让程序快速退出。