Go Vet 命令:超出預期的強大

前言

Go vet 命令在編寫代碼時非常有用。它可以幫助您檢測應用程序中任何可疑、異常或無用的代碼。該命令實際上由幾個子分析器組成,甚至可以與您的自定義分析器一起工作。讓我們首先回顧一下內置的分析器。

內置分析器

可以通過命令 go tool vet help 獲取 內置分析器[1] 列表。讓我們分析一些不太明顯的例子,以便更好地理解。

Atomic

這個分析器將防止原子函數的任何不正確使用

func main() {
   var a int32 = 0

   var wg sync.WaitGroup
   for i := 0; i < 500; i++ {
      wg.Add(1)
      go func() {
         a = atomic.AddInt32(&a, 1) // 改爲 atomic.AddInt32(&a, 1) 即可
         wg.Done()
      }()
   }
   wg.Wait()
}
main.go:15:4: direct assignment to atomic value

由於原子內存原語函數 addInt 是併發安全的,所以變量 a 會安全增加。但是,我們將結果分配給相同的變量,這不是一個併發安全的寫操作。atomic 分析儀將發現發現這個個粗心的錯誤。

copylocks

正如文檔中所描述的,永遠不應該複製鎖。實際上,它在內部管理鎖的當前狀態。一旦使用了鎖,此鎖的副本就會複製其內部狀態,使鎖的副本與原始狀態相同,而不是初始化的新狀態。

func main() {
   var lock sync.Mutex

   l := lock //直接使用 lock 即可
   l.Lock()
   l.Unlock()
}
// from vet: main.go:9:7: assignment copies lock value to l: sync.Mutex

使用鎖的結構體應該使用指針引用,以保持內部狀態一致:

type Foo struct {
   lock sync.Mutex
}

func (f Foo) Lock() { // 改爲:func (f *Foo) Lock()
   f.lock.Lock()
}

func main() {
   f := Foo{lock: sync.Mutex{}}
   f.Lock()
}
// from vet: main.go:9:9: Lock passes lock by value: command-line-arguments.Foo contains sync.Mutex

loopclosure

當您啓動一個新的 goroutine 時,主 goroutine 將繼續執行。在執行時,將進行評估 goroutine 及其變量的代碼將,當一個變量仍然被主 goroutine 更新時使用,這可能會導致一些常見的錯誤:

func main() {
   var wg sync.WaitGroup
   for _, v := range []int{0,1,2,3} { // 需引入臨時變量解決,或 通過傳值參數解決
      wg.Add(1)
      go func() {
         print(v)
         wg.Done()
      }()
   }
   wg.Wait()
}
// output:
// 3333
// from vet: main.go:10:12: loop variable v captured by func literal

lostcancel

從主上下文(main)創建一個可取消的上下文(cancellable context)將返回新上下文以及一個能夠取消該上下文的函數。此函數可在任何時候用於取消與此上下文關聯的所有操作,但應始終調用此函數,以避免泄漏任何上下文。

func Foo(ctx context.Context) {}

func main() {
   ctx, _ := context.WithCancel(context.Background())
   Foo(ctx)
}
// from vet: main.go:8:7: the cancel function returned by context.WithCancel should be called, not discarded, to avoid a context leak
// 需改爲:
    // ctx, cancleFunc := context.WithCancel(context.Background())
    // Foo(ctx)
    // cancleFunc()

如果需要了解關於 context 的更多細節、各種 context 的差異以及 cancel function 的功能,我建議您閱讀我關於上下文和通過傳播進行取消[2]的文章。

stdmethods

stdmethods 分析器將確保你已經從標準庫的接口來實現的方法是與標準庫兼容:

type Foo struct {}

func (f Foo) MarshalJSON() (string, error) {
   return `{a: 0}`, nil
}
// 需改爲:
// func (f Foo) MarshalJSON() ([]byte, error) {
//    return []byte(`{a: 0}`), nil
// }

func main() {
   f := Foo{}
   j, _ := json.Marshal(f)
   println(string(j))
}
// {}
// from vet: main.go:7:14: method MarshalJSON() (string, error) should have signature MarshalJSON() ([]byte, error)

structtag

標籤是結構中的字符串,應該遵循反射包中的約定[3]。隨意使用將使標籤無效,並可能很難調試沒有審查命令:一個多餘的空格都會使 tag 失效,如果沒有 vet 命令其將難以調試

type Foo struct {
   A int `json: "foo"`// 去除 `json: "foo" 中間多餘空格即可
}

func main() {
   f := Foo{}
   j, _ := json.Marshal(f)
   println(string(j))
}
// {"A":0}
// from vet: main.go:6:2: struct field tag `json: "foo"` not compatible with reflect.StructTag.Get: bad syntax for struct tag value

vet 命令還有更多可用的分析器[4],但這還不是這個命令的強大所在。它還允許我們自定義分析器。

自定義分析器

雖然內置分析器很有用,很強大,但是 Go 允許我們創建我們自己的分析器。

我將使用我構建的自定義分析器來檢測上下文包在函數參數中的使用情況,您可以在“構建自己的分析器[5]”一文中找到相關信息。

你的分析器一旦構建完成,就可通過 vet 命令直接使用。

go install github.com/blanchonvincent/ctxarg
go vet -vettool=$(which ctxarg)

您甚至可以構建自己的分析工具。

自定義分析命令

由於分析器與命令完全解耦,您可以使用您需要的分析程序創建您自己的命令。讓我們來看一個自定義命令的例子,它只使用我們需要的一些分析器:

基於分析器列表的自定義命令

package main

import (
    "golang.org/x/tools/go/analysis/multichecker"
    "golang.org/x/tools/go/analysis/passes/atomic"
    "golang.org/x/tools/go/analysis/passes/loopclosure"
    "github.com/blanchonvincent/ctxarg/analysis/passes/ctxarg"
)

func main() {
    multichecker.Main(
        atomic.Analyzer,
        loopclosure.Analyzer,
        ctxarg.Analyzer,
    )
}

構建並運行該命令將爲我們提供一個基於所選分析程序的工具:

./custom-vet help
custom-vet is a tool for static analysis of Go programs.

Registered analyzers:

    atomic       check for common mistakes using the sync/atomic package
    ctxarg       check for parameters order while receiving context as parameter
    loopclosure  check references to loop variables from within nested functions

您還可以創建您的自定義分析程序集,並將它們與您喜歡的內置分析程序合併,得到一個適合您自己的工作流和公司編碼標準的自定義命令。

參考資料

[1]
內置分析器: https://golang.org/cmd/vet/
[2]
上下文和通過傳播進行取消: https://medium.com/@blanchon.vincent/go-context-and-cancellation-by-propagation-7a808bbc889c
[3]
反射包中的約定: http://golang.org/pkg/reflect/#StructTag
[4]
更多可用的分析器: https://github.com/golang/tools/blob/release-branch.go1.12/go/analysis/cmd/vet/vet.go#L51-L73
[5]
構建自己的分析器: https://medium.com/@blanchon.vincent/go-how-to-build-your-own-analyzer-f6d83315586f
[6]
Vincent Blanchon: https://medium.com/@blanchon.vincent
[7]
TomatoAres: https://github.com/TomatoAres
[8]
DingdingZhou: https://github.com/DingdingZhou
[9]
GCTT: https://github.com/studygolang/GCTT
[10]
Go 中文網: https://studygolang.com/

[11]
https://mp.weixin.qq.com/s/EPfsrWxPWIO8OpbNncugbw

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章