Go Vet

go vet用于检查代码中的错误,例如printf的参数错误等。

一、用法

go vet [-n] [-x] [-vettool prog] [build flags] [vet flags] [packages]

一般使用

go vet /path/to/file

二、Goland集成

可以使用Tools->Go Tools->Go Vet File

三、简介

3.1 Print-format错误

尽管go是强类型的,但是printf-format错误不会在编译阶段检测。C开发者可能使用默认激活的gcc-Wformat选项。如果参数不能匹配格式它可以给出一个很好的警告:

warning: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘int’ [-Wformat=]

不幸的是,在Go里面编译器没有任何输出。这是vet发挥作用的时候了。考虑下面的例子:

package mainimport "fmt"

func main() {
    str := "hello world!"
    fmt.Printf("%d\n", str)
}

这是一个典型的错误,一个坏的printf格式。因为str是一个字符串,所以format应该用%s,而不是%d

这个代码编译后运行,打印出%!d(string=hello world!),不够友好。现在,我们开始运行vet。

$ go tool vet ex1.go

ex1.go:7: arg str for printf verb %d of wrong type: string

当一个指针被使用时,vet也可以检测:

package main
import "fmt"

func main() {
    str := "hello world!"
    fmt.Printf("%s\n", &str)
}
$ go tool vet ex2.go

ex2.go:7: arg &str for printf verb %s of wrong type: *string

vet也可以找到所有的Printf()家族函数(Printf(), Sprintf(), Fprintf(), Errorf(), Fatalf(), Logf(), Panicf()等)格式错误。但是如果你要实现一个函数,接收和printf类似的参数,你可以使用-printfuncs选项使得vet来检测。

package main
import "fmt"

func customLogf(str string, args ...interface{}) {
    fmt.Printf(str, args...)
}
func main() {
    i := 42
    customLogf("the answer is %s\n", i)
}
$ go tool vet custom-printf-func.go
$ go tool vet -printfuncs customLogf custom-printf-func.go

custom-printf-func.go:11: arg i for printf verb %s of wrong type: int

你可以看到如果没有-printfuncs选项,vet没有任何输出。

3.2 Boolean错误

vet可以检查一直为truefalse或者冗余的表达式。

package main

import "fmt"

func main() {
    var i int

    // always true
    fmt.Println(i != 0 || i != 1)    // always false
    fmt.Println(i == 0 && i == 1)    // redundant check
    fmt.Println(i == 0 && i == 0)
}
$ go vet bool-expr.go
bool-expr.go:9: suspect or: i != 0 || i != 1
bool-expr.go:12: suspect and: i == 0 && i == 1
bool-expr.go:15: redundant and: i == 0 && i == 0

这种类型的警告常常是非常危险的,可以引起讨厌的bug。大多数情况下是由于排版错误引起的。

3.3 Range循环

当读取变量的时候,在range块内的go协程可能是有问题的。在这些场景下,vet可以检测到它们:

package main

import "fmt"

func main() {
    words := []string{"foo", "bar", "baz"}
    for _, word := range words {
        go func() {
            fmt.Println(word)
        }()
    }
}

注意,这个代码包含竞态,可能不输出任何东西。事实上,main函数可能在所有的协程执行前已经结束,这导致进程退出。

$ go tool vet range.go
range.go:10: range variable word captured by func literal

3.4 Unreachable的代码

下面的例子包含3个函数,带有不能到达的代码,每个函数使用了不同的方式。

package main

import "fmt"

func add(a int, b int) int {
    return a + b
    fmt.Println("unreachable")
    return 0
}

func div(a int, b int) int {
    if b == 0 {
        panic("division by 0")
    } else {
        return a / b
    }
    fmt.Println("unreachable")
    return 0
}

func fibonnaci(n int) int {
    switch n {
        case 0:        return 1
        case 1:        return 1
        default:       return fibonnaci(n-1) + fibonnaci(n-2)
    }
    fmt.Println("unreachable")
    return 0
}

func main() {
    fmt.Println(add(1, 2))
    fmt.Println(div(10, 2))
    fmt.Println(fibonnaci(5))
}
$ go vet unreachable.go

unreachable.go:8: unreachable code
unreachable.go:19: unreachable code
unreachable.go:33: unreachable code

3.5 混杂的错误

这里是一个代码段,包含了其他的几个vet可以检测的混杂的错误:

package main
import (
    "fmt"
    "log"
    "net/http"
)

func f() {}
func main() {    // Self assignment
    i := 42
    i = i    // a declared function cannot be nil
    fmt.Println(f == nil)    // shift too long
    fmt.Println(i >> 32)    // res used before checking err
    res, err := http.Get("https://www.spreadsheetdb.io/")
    defer res.Body.Close()
    if err != nil {
        log.Fatal(err)
    }
}
$ go tool vet misc.go

misc.go:14: self-assignment of i to i
misc.go:17: comparison of function f == nil is always false
misc.go:20: i might be too small for shift of 32
misc.go:24: using res before checking for errors

3.6 误报和漏报

有时,vet可能忽略了错误,并警告可疑代码,这些代码实际上是正确的。下面的例子:

package main

import "fmt"

func main() {
    rate := 42

    // this condition can never be true
    if rate > 60 && rate < 40 {
        fmt.Println("rate %:", rate)
    }
}
$ go tool vet false.go

false.go:10: possible formatting directive in Println call

这种情况很明显永远都不是true,但是并不会检测出来。然而,vet警告了一种可能的错误(使用Println()而不是Printf()),这里的Println()非常好用。

总的来说,使用go tool vet提示很少会有误报与漏报的情况。

3.7 性能

vet的README描述了,只是可能的错误是值得检测的。这种方式保证了vet不会变慢。

此时,Docker包含了23M的Go代码(包含依赖)。在Core i5机器上,vet花费了21.6秒来分析它,这是1MB/s的数量级。

Last updated

Was this helpful?