Parts of Effective Go
空白标识符
空白标识符可以被赋予或声明为任何类型的任何值,其值会被无害的丢弃
类似于Unix
中的/dev/null
,表示只写的值,在需要变量但不需要实际值的地方用作占位符
可使用空白标识符来丢弃无关的值
1 | if _, err := os.Stat(path); os.IsNotExist(err) { |
1 | // 错误的方式 |
未使用的导入和变量
若导入某个包或声明某个变量而不使用它就会产生错误
未使用的包会让程序膨胀并拖慢编译速度
已初始化但未使用的变量不仅会浪费计算能力,还可能暗藏更大的Bug
在开发过程中,经常会产生未使用的导入和变量
空白标识符可以忽略编译报错
1 | package main |
为副作用而导入
前例中fmt
和io
这种未使用的导入总应在最后被使用或者移除
但有时导入某个包只是为了其副作用,而没有任何明确的使用
例如,在net/http/pprof
包的init
函数中记录了HTTP
处理程序的调试信息,它有个可导出的API
,但大部分客户端只需要该处理程序的记录和通过Web
页面访问数据
只为了其副作用来导入该包,只需将包重命名为空白标识符import _ "net/http/pprof"
这中导入格式能明确表示该包是为其副作用而导入的,没有其它使用该包的可能,在这个源文件中,它没有名字(如果它有名字却没有使用,编译器就会拒绝该程序)
接口检查
一个类型无需显式地声明它实现了某些接口
该类型只要实现了某个接口的方法,其实就实现了该接口
大部分接口转换都是静态的,会在编译时检测
例如,将一个*os.File
传入一个预期的io.Reader
函数将不会被编译,除非*os.File
实现了io.Reader
接口
部分接口检查会在运行时进行encoding/json
包中有一个实例定义了一个Marshaler
接口,当JSON
编码器接收到一个实现了该接口的值,该编码器就会调用该值的编组方法,将其转换为JSON
,而不是进行标准的类型转换
编码器在运行是通过类型断言检查其属性m, ok := val.(json.Marshsler)
若只需要判断某个类型是否实现了某个接口,而不需要实际使用接口本身(可能是错误检查部分),就使用空白标识符来忽略类型断言的值
1 | if _, ok := val.(json.Marshaler); ok { |
当明确需要确保某个包中实现的类型一定满足该接口时,例如 类型json.RawMessage
需要一种定制的JSON
表现时,它应当实现json.Marshaler
,不过现在没有静态转换可以让编译器去自动验证它,若该类型通过忽略转换失败来满足该接口,那么JSON
编码器仍可工作,但它却不会使用定制的实现,为保证其实现正确,可在该包中用空白标识符声明一个全局变量var _ json.Marshaler = (*RawMessage)(nil)
在此声明中,调用了一个*RawMessage
转换并将其赋予了Marshaler
,以此来要求*RawMessage
实现Marshaler
,这时其属性就会在编译时被检测
若json.Marshaler
接口被更改,此包将无法通过编译,从而提示开发者注意到它需要更新
在这种结构中出现空白标识符,即表示该声明的存在只是为了类型检查
不要为满足接口就将它用于任何类型,作为约定,仅当代码中不存在静态类型转换时才能用这种声明,毕竟这是种罕见的情况
内嵌
Go
并不提供典型的、类型驱动的子类化概念,但通过将类型内嵌到结构体或接口中,它就能“借鉴”部分实现
1 | // io.Reader |
ReadWriter
能够做任何Reader
和Writer
可以做到的事情,它的内嵌接口的联合体(它们必须是不相交的方法集)
只有接口能被嵌入到接口中
同样的思考方法可以应用在结构体中,但其意义更加深远bufio
包中有bufio.Reader
和bufio.Writer
这两个结构体类型,它们每一个都实现了与io
包中相同意义的接口
此外,bufio
还通过结合reader/writer
并将其内嵌到结构体中,实现了带缓冲的reader/writer
1 | // ReadWriter 存储了指向 Reader 和 Writer 的指针 |
内嵌类型的方法可以直接引用
这意味着bufio.ReadWriter
不仅包括bufio.Reader
和bufio.Writer
的方法,还同时满足下列三个接口io.Reader io.Writer io.ReadWriter
当内嵌一个类型时,该类型的方法会成为外部类型的方法,但当它们被调用时,该方法的接收者是内部类型,而不是外部类型
当bufio.ReadWriter
的Read
方法被调用时,接收者是ReadWriter
的reader
字段,而不是ReadWriter
本身
1 | type Job struct { |
内嵌类型会引入命名冲突的问题
字段或方法X
会隐藏该类型中更深层嵌套的其它项X
若log.Logger
包含一个名为Command
的字段或方法,Job
的Command
字段会覆盖它
若相同的嵌套层级上出现同名冲突,通常会产生一个错误
若Job
结构体中包含为Logger
的字段或方法,再将log.Logger
内嵌到其中的话就会产生错误
若重名永远不会在该类型定义之外的程序中使用,那就不会出错
这种限定能够在外部嵌套类型发生修改时提供保护
因此,就算添加的字段与相同嵌套层级上的字段相冲突,只要这两个相同的字段永远不会被使用就没问题