空白标识符

未匹配的标注

我们在 for-range 循环映射中提过几次空白标识符。 空白标识符可被赋予或声明为任何类型的任何值,而其值会被无害地丢弃。它有点像Unix中的 /dev/null 文件:它表示只写的值,在需要变量但不需要实际值的地方用作占位符。 我们在前面已经见过它的用法了。

多个参数赋值中的空白标识符

for range 循环中对空表标识符的用法是一种具体情况,更一般的情况即为多个参数赋值。

若某次赋值需要匹配多个左值,但其中某个变量不会被程序使用, 那么用空白标识符来代替该变量可避免创建无用的变量,并能清楚地表明该值将被丢弃。 例如,当调用某个函数时,它会返回一个值和一个错误,但只有错误很重要, 那么可使用空白标识符来丢弃无关的值。

if _, err := os.Stat(path); os.IsNotExist(err) {
    fmt.Printf("%s does not exist\n", path)
}

你偶尔会看见为忽略错误而丢弃错误值的代码,这是种糟糕的实践。请务必检查错误返回, 它们会提供错误的理由。

// 很糟糕的代码!若路径不存在,它就会崩溃。
fi, _ := os.Stat(path)
if fi.IsDir() {
    fmt.Printf("%s is a directory\n", path)
}

未使用的导入和变量

若导入某个包或声明某个变量而不使用它就会产生错误。未使用的包会让程序膨胀并拖慢编译速度, 而已初始化但未使用的变量不仅会浪费计算能力,还有可能暗藏着更大的Bug。 然而在程序开发过程中,经常会产生未使用的导入和变量。虽然以后会用到它们, 但为了完成编译又不得不删除它们才行,这很让人烦恼。空白标识符就能提供一个工作空间。

这个写了一半的程序有两个未使用的导入(fmtio)以及一个未使用的变量(fd),因此它不能编译, 但若到目前为止代码还是正确的,我们还是很乐意看到它们的。

package main

import (
    "fmt"
    "io"
    "log"
    "os"
)

func main() {
    fd, err := os.Open("test.go")
    if err != nil {
        log.Fatal(err)
    }
    // TODO: use fd.
}

要让编译器停止关于未使用导入的包,需要空白标识符来引用已导入包中的符号。 同样,将未使用的变量 fd 赋予空白标识符也能关闭未使用变量错误。 该程序的以下版本可以编译。

package main

import (
    "fmt"
    "io"
    "log"
    "os"
)

var _ = fmt.Printf  // 用于调试,结束时删除。
var _ io.Reader    // 用于调试,结束时删除。

func main() {
    fd, err := os.Open("test.go")
    if err != nil {
        log.Fatal(err)
    }
    // TODO: use fd.
    _ = fd
}

按照惯例,我们应在导入并加以注释后,再使全局声明导入错误静默,这样可以让它们更易找到, 并作为以后清理它的提醒。

为辅助作用而导入

像前例中 fmtio 这种未使用的导入总应在最后被使用或移除: 空白赋值会将代码标识为工作正在进行中。但有时导入某个包只是为了其辅助作用, 而没有任何明确的使用。例如,在 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.Marshaler)

若只需要判断某个类型是否是实现了某个接口,而不需要实际使用接口本身 (可能是错误检查部分),就使用空白标识符来忽略类型断言的值:

if _, ok := val.(json.Marshaler); ok {
    fmt.Printf("value %v of type %T implements json.Marshaler\n", val, val)
}

当需要确保某个包中实现的类型一定满足该接口时,就会遇到这种情况。 若某个类型(例如json.RawMessage) 需要一种自定义的JSON表现时,它应当实现 json.Marshaler, 不过现在没有静态转换可以让编译器去自动验证它。若该类型通过忽略转换失败来满足该接口, 那么JSON编码器仍可工作,但它却不会使用自定义的实现。为确保其实现正确, 可在该包中用空白标识符声明一个全局变量:

var _ json.Marshaler = (*RawMessage)(nil)

在此声明中,我们调用了一个 *RawMessage 转换并将其赋予了 Marshaler,以此来要求 *RawMessage 实现 Marshaler,这时其属性就会在编译时被检测。 若 json.Marshaler 接口被更改,此包将无法通过编译, 而我们则会注意到它需要更新。

在这种结构中出现空白标识符,即表示该声明的存在只是为了类型检查。 不过请不要为满足接口就将它用于任何类型。作为约定, 只有当代码中不存在静态类型转换时才能使用这种声明,毕竟这是种非常罕见的情况。

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/effective-go/20...

译文地址:https://learnku.com/docs/effective-go/20...

上一篇 下一篇
贡献者:7
讨论数量: 0
发起讨论 只看当前版本


暂无话题~