《Go语言精进之路》读书笔记 | 在init函数中检查包级变量的初始状态

Posted COCOgsta

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Go语言精进之路》读书笔记 | 在init函数中检查包级变量的初始状态相关的知识,希望对你有一定的参考价值。

书籍来源:《Go语言精进之路:从新手到高手的编程思想、方法和技巧》

一边学习一边整理读书笔记,并与大家分享,侵权即删,谢谢支持!

附上汇总贴:《Go语言精进之路》读书笔记 | 汇总_COCOgsta的博客-CSDN博客


在Go包这一基本单元中分布着常量、包级变量、函数、类型和类型方法、接口等,要保证包内部的这些元素在被使用之前处于合理有效的初始状态,尤其是包级变量,一般通过包的init函数来完成这一工作。

20.1 认识init函数

init函数是一个无参数、无返回值的函数:

func init() 
    ...

复制代码

如果一个包定义了init函数,Go运行时会负责在该包初始化时调用它的init函数。

一个Go包可以拥有多个init函数,每个组成Go包的Go源文件中可以定义多个init函数。init函数极其适合做一些包级数据的初始化及初始状态的检查工作。

一般来说,先被传递给Go编译器的源文件中的init函数先被执行,同一个源文件中的多个init函数按声明顺序依次执行。

20.2 程序初始化顺序

Go程序由一组包组合而成,每个包还包含有常量、变量、init函数等(其中main包有main函数),这些元素在程序初始化过程中的初始化顺序是什么样的呢?

图20-1 Go程序初始化顺序

init函数适合做包级数据的初始化及初始状态检查工作的前提条件是,init函数的执行顺位排在其所在包的包级变量之后。

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

init函数就好比Go包真正投入使用之前的唯一“质检员”,负责对包内部以及暴露到外部的包级数据(主要是包级变量)的初始状态进行检查。

  1. 重置包级变量值

标准库flag包的init函数:

// $GOROOT/src/flag/flag.go

func init() 
    CommandLine.Usage = commandLineUsage

复制代码

flag包在init函数中,将ComandLine的Usage字段设置为一个包内未导出函数commandLineUsage,后者则直接使用了flag包的另一个导出包变量Usage。这样就通过init函数将CommandLine与包变量Usage关联在一起了。在用户将自定义usage赋值给Usage后,就相当于改变了CommandLine变量的Usage。

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

标准库regexp包的init函数就负责完成对内部特殊字节数组的初始化,这个特殊字节数组被包内的special函数使用,用于判断某个字符是否需要转义:

//  $GOROOT/src/regexp/regexp.go

var specialBytes [16]byte

func special(b byte) bool 
    return b < utf8.RuneSelf && specialBytes[b%16]&(1<<(b/16)) != 0

func init() 
    for _, b := range []byte(`.+*?()|[]^$`) 
        specialBytes[b%16] |= 1 << (b / 16)
    

复制代码
  1. 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
    rows, err := db.Query("SELECT name FROM users WHERE age = $1", age)
    ...

复制代码

在以空别名方式导入lib/pq包后,main函数中似乎并没有使用pq的任何变量、函数或方法。这段代码的奥秘全在pq包的init函数中:

// github.com/lib/pq/conn.go
...

func init() 
    sql.Register("postgres", &Driver)

...
复制代码

Go运行时会将lib/pq作为main包的依赖包并会初始化pq包,在pq包的init函数中,pq包将自己实现的SQL驱动(driver)注册到sql包中。这样,只要应用层代码在打开数据库的时候传入驱动的名字(这里是postgres),通过sql.Open函数返回的数据库实例句柄对应的就是pq这个驱动的相应实现。

这种在init函数中注册自己的实现的模式降低了Go包对外的直接暴露,避免了外部通过包级变量对包状态的改动。

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

init函数是一个无参数、无返回值的函数,它的主要目的是保证其所在包在被正式使用之前的初始状态是有效的。一旦init函数在检查包数据初始状态时遇到失败或错误的情况,快速失败是最佳选择。

以上是关于《Go语言精进之路》读书笔记 | 在init函数中检查包级变量的初始状态的主要内容,如果未能解决你的问题,请参考以下文章

《Go语言精进之路》读书笔记 | 使用defer让函数更简洁更健壮

《Go语言精进之路》读书笔记 | 使用无类型常量简化代码

《Go语言精进之路》读书笔记 | 汇总

《Go语言精进之路》读书笔记 | 了解string实现原理并高效使用

《Go语言精进之路》读书笔记 | 理解Go语言的包导入

《Go语言精进之路》读书笔记 | 尽量定义零值可用的类型