Gopls:分析器

Gopls 包含一個用於可插拔、模組化靜態 分析器 的驅動程式,例如 go vet 使用的分析器。

大多數分析器會報告程式碼中的錯誤;有些則建議可以直接在編輯器中應用的“快速修復”。每次編輯程式碼時,gopls 都會重新執行其分析器。分析器診斷有助於您更早地檢測到 bug,在執行測試之前,甚至在儲存檔案之前。

本文件描述了 gopls 中提供的分析器套件,該套件聚合了來自各種來源的分析器

  • 來自 go vet 套件的所有常用 bug 查詢分析器(例如 printf;請參閱 go tool vet help 獲取完整列表);
  • 一些具有更實質性依賴項的分析器,這些依賴項阻止它們在 go vet 中使用(例如 nilness);
  • 透過建議對常見錯誤的快速修復來增強編譯錯誤的分析器(例如 fillreturns);以及
  • 少量建議可能的樣式改進的分析器(例如 simplifyrange)。

要啟用或停用分析器,請使用 analyses 設定。

此外,gopls 還包含 staticcheck 套件。當 staticcheck 布林選項未設定時,預設啟用其中略超過一半的分析器;此子集已根據精度和效率進行選擇。將 staticcheck 設定為 true 以啟用完整集合,或設定為 false 以停用完整集合。

Staticcheck 分析器與所有其他分析器一樣,可以使用 analyzers 配置設定顯式啟用或停用;此設定優先於 staticcheck 設定,因此,無論您使用 staticcheck 的哪個值(true/false/unset),都可以根據您偏好的分析器集合進行調整。

QF1001:應用德摩根定律

自 2021.1 版本起可用

預設:關閉。透過設定 "analyses": {"QF1001": true} 來啟用。

軟體包文件:QF1001

QF1002:將未標記的 switch 轉換為已標記的 switch

一個比較單個變數與一系列值的未標記 switch 可以替換為已標記的 switch。

之前

switch {
case x == 1 || x == 2, x == 3:
    ...
case x == 4:
    ...
default:
    ...
}

之後

switch x {
case 1, 2, 3:
    ...
case 4:
    ...
default:
    ...
}

自 2021.1 版本起可用

預設:開啟。

軟體包文件:QF1002

QF1003:將 if/else-if 鏈轉換為已標記的 switch

一系列 if/else-if 檢查,比較同一個變數與值,可以替換為已標記的 switch。

之前

if x == 1 || x == 2 {
    ...
} else if x == 3 {
    ...
} else {
    ...
}

之後

switch x {
case 1, 2:
    ...
case 3:
    ...
default:
    ...
}

自 2021.1 版本起可用

預設:開啟。

軟體包文件:QF1003

QF1004:使用 strings.ReplaceAll 代替 strings.Replace (n == -1)

自 2021.1 版本起可用

預設:開啟。

軟體包文件:QF1004

QF1005:展開 math.Pow 呼叫

某些 math.Pow 的用法可以簡化為基本乘法。

之前

math.Pow(x, 2)

之後

x * x

自 2021.1 版本起可用

預設:關閉。透過設定 "analyses": {"QF1005": true} 來啟用。

軟體包文件:QF1005

QF1006:將 if+break 提升到迴圈條件

之前

for {
    if done {
        break
    }
    ...
}

之後

for !done {
    ...
}

自 2021.1 版本起可用

預設:關閉。透過設定 "analyses": {"QF1006": true} 來啟用。

軟體包文件:QF1006

QF1007:將條件賦值合併到變數宣告

之前

x := false
if someCondition {
    x = true
}

之後

x := someCondition

自 2021.1 版本起可用

預設:關閉。透過設定 "analyses": {"QF1007": true} 來啟用。

軟體包文件:QF1007

QF1008:從選擇器表示式中省略嵌入的欄位

自 2021.1 版本起可用

預設:關閉。透過設定 "analyses": {"QF1008": true} 來啟用。

軟體包文件:QF1008

QF1009:使用 time.Time.Equal 代替 == 運算子

自 2021.1 版本起可用

預設:開啟。

軟體包文件:QF1009

QF1010:在列印位元組切片時將其轉換為字串

自 2021.1 版本起可用

預設:開啟。

軟體包文件:QF1010

QF1011:省略變數宣告中的冗餘型別

自 2021.1 版本起可用

預設:關閉。透過設定 "analyses": {"QF1011": true} 來啟用。

軟體包文件:QF1011

QF1012:使用 fmt.Fprintf(x, …) 代替 x.Write(fmt.Sprintf(…))

自 2022.1 版本起可用

預設:開啟。

軟體包文件:QF1012

S1000:使用普通的 channel 傳送或接收,而不是單 case 的 select

只有單個 case 的 select 語句可以替換為簡單的傳送或接收。

之前

select {
case x := <-ch:
    fmt.Println(x)
}

之後

x := <-ch
fmt.Println(x)

自 2017.1 版本起可用

預設:開啟。

軟體包文件:S1000

S1001:用 copy 呼叫替換 for 迴圈

使用 copy() 將元素從一個 slice 複製到另一個 slice。對於大小相同的陣列,可以使用簡單的賦值。

之前

for i, x := range src {
    dst[i] = x
}

之後

copy(dst, src)

自 2017.1 版本起可用

預設:開啟。

軟體包文件:S1001

S1002:省略與布林常量的比較

之前

if x == true {}

之後

if x {}

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"S1002": true} 來啟用。

軟體包文件:S1002

S1003:用 strings.Contains 替換 strings.Index 呼叫

之前

if strings.Index(x, y) != -1 {}

之後

if strings.Contains(x, y) {}

自 2017.1 版本起可用

預設:開啟。

軟體包文件:S1003

S1004:用 bytes.Equal 替換 bytes.Compare 呼叫

之前

if bytes.Compare(x, y) == 0 {}

之後

if bytes.Equal(x, y) {}

自 2017.1 版本起可用

預設:開啟。

軟體包文件:S1004

S1005:刪除空白識別符號的不必要使用

在許多情況下,賦值給空白識別符號是不必要的。

之前

for _ = range s {}
x, _ = someMap[key]
_ = <-ch

之後

for range s{}
x = someMap[key]
<-ch

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"S1005": true} 來啟用。

軟體包文件:S1005

S1006:使用 'for { … }' 進行無限迴圈

對於無限迴圈,使用 for { … } 是最符合慣用語的選擇。

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"S1006": true} 來啟用。

軟體包文件:S1006

S1007:透過使用原始字串字面量簡化正則表示式

原始字串字面量使用反引號而不是雙引號,並且不支援任何轉義序列。這意味著反斜槓可以自由使用,而無需轉義。

由於正則表示式有自己的轉義序列,原始字串可以提高其可讀性。

之前

regexp.Compile("\\A(\\w+) profile: total \\d+\\n\\z")

之後

regexp.Compile(`\A(\w+) profile: total \d+\n\z`)

自 2017.1 版本起可用

預設:開啟。

軟體包文件:S1007

S1008:簡化返回布林表示式

之前

if <expr> {
    return true
}
return false

之後

return <expr>

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"S1008": true} 來啟用。

軟體包文件:S1008

S1009:省略對 slice、map 和 channel 的冗餘 nil 檢查

len 函式適用於所有 slice、map 和 channel,即使是 nil 的,它們的長度也為零。在檢查長度不為零之前,無需檢查 nil。

之前

if x != nil && len(x) != 0 {}

之後

if len(x) != 0 {}

自 2017.1 版本起可用

預設:開啟。

軟體包文件:S1009

S1010:省略預設 slice 索引

切片時,第二個索引預設為值的長度,這使得 s[n:len(s)] 和 s[n:] 等效。

自 2017.1 版本起可用

預設:開啟。

軟體包文件:S1010

S1011:使用單個 append 來連線兩個 slice

之前

for _, e := range y {
    x = append(x, e)
}

for i := range y {
    x = append(x, y[i])
}

for i := range y {
    v := y[i]
    x = append(x, v)
}

之後

x = append(x, y...)
x = append(x, y...)
x = append(x, y...)

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"S1011": true} 來啟用。

軟體包文件:S1011

S1012:用 time.Since(x) 替換 time.Now().Sub(x) 呼叫

time.Since 助手函式的效果與使用 time.Now().Sub(x) 相同,但更易於閱讀。

之前

time.Now().Sub(x)

之後

time.Since(x)

自 2017.1 版本起可用

預設:開啟。

軟體包文件:S1012

S1016:使用型別轉換而不是手動複製結構欄位

具有相同欄位的兩種結構型別可以相互轉換。在舊版本的 Go 中,欄位必須具有相同的結構標籤。然而,自 Go 1.8 以來,結構標籤在轉換時被忽略。因此,不必逐個手動複製每個欄位。

之前

var x T1
y := T2{
    Field1: x.Field1,
    Field2: x.Field2,
}

之後

var x T1
y := T2(x)

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"S1016": true} 來啟用。

軟體包文件:S1016

S1017:用 strings.TrimPrefix 替換手動修剪

與其使用 strings.HasPrefix 和手動切片,不如使用 strings.TrimPrefix 函式。如果字串不以字首開頭,則返回原始字串。使用 strings.TrimPrefix 可以降低複雜性,並避免常見的 bug,例如偏移量錯誤。

之前

if strings.HasPrefix(str, prefix) {
    str = str[len(prefix):]
}

之後

str = strings.TrimPrefix(str, prefix)

自 2017.1 版本起可用

預設:開啟。

軟體包文件:S1017

S1018:使用 'copy' 來滑動元素

copy() 允許使用相同的源和目標 slice,即使範圍重疊。這使其成為滑動 slice 中元素的理想選擇。

之前

for i := 0; i < n; i++ {
    bs[i] = bs[offset+i]
}

之後

copy(bs[:n], bs[offset:])

自 2017.1 版本起可用

預設:開啟。

軟體包文件:S1018

S1019:透過省略冗餘引數來簡化 'make' 呼叫

'make' 函式的 length 和 capacity 引數有預設值。對於 channel,length 預設為零,對於 slice,capacity 預設為 length。

自 2017.1 版本起可用

預設:開啟。

軟體包文件:S1019

S1020:省略型別斷言中的冗餘 nil 檢查

之前

if _, ok := i.(T); ok && i != nil {}

之後

if _, ok := i.(T); ok {}

自 2017.1 版本起可用

預設:開啟。

軟體包文件:S1020

S1021:合併變數宣告和賦值

之前

var x uint
x = 1

之後

var x uint = 1

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"S1021": true} 來啟用。

軟體包文件:S1021

S1023:省略冗餘控制流

沒有返回值的函式不需要 return 語句作為函式的最後一條語句。

Go 中的 switch 語句不像 C 語言那樣具有自動的 fallthrough。因此,不必在 case 塊的最後一條語句中使用 break。

自 2017.1 版本起可用

預設:開啟。

軟體包文件:S1023

S1024:用 time.Until(x) 替換 x.Sub(time.Now())

time.Until 助手函式的效果與使用 x.Sub(time.Now()) 相同,但更易於閱讀。

之前

x.Sub(time.Now())

之後

time.Until(x)

自 2017.1 版本起可用

預設:開啟。

軟體包文件:S1024

S1025:不必要地使用 fmt.Sprintf("%s", x)

在許多情況下,有更簡單、更有效的方法可以獲得值的字串表示。當值的底層型別已經是字串,或者型別具有 String 方法時,應直接使用它們。

鑑於以下共享定義

type T1 string
type T2 int

func (T2) String() string { return "Hello, world" }

var x string
var y T1
var z T2

我們可以簡化

fmt.Sprintf("%s", x)
fmt.Sprintf("%s", y)
fmt.Sprintf("%s", z)

轉換為

x
string(y)
z.String()

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"S1025": true} 來啟用。

軟體包文件:S1025

S1028:使用 fmt.Errorf 簡化錯誤構造

之前

errors.New(fmt.Sprintf(...))

之後

fmt.Errorf(...)

自 2017.1 版本起可用

預設:開啟。

軟體包文件:S1028

S1029:直接 range 字串

對字串進行 range 操作會產生位元組偏移量和 rune。如果未在偏移量中使用,這在功能上等同於將字串轉換為 rune slice 並對其進行 range 操作。然而,直接對字串進行 range 操作會更高效,因為它避免了分配一個取決於字串長度的新 slice。

之前

for _, r := range []rune(s) {}

之後

for _, r := range s {}

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"S1029": true} 來啟用。

軟體包文件:S1029

S1030:使用 bytes.Buffer.String 或 bytes.Buffer.Bytes

bytes.Buffer 同時具有 String 和 Bytes 方法。使用 string(buf.Bytes()) 或 []byte(buf.String()) 幾乎總是不必要的——只需使用另一種方法即可。

唯一的例外是 map 查詢。由於編譯器最佳化,m[string(buf.Bytes())] 比 m[buf.String()] 更高效。

自 2017.1 版本起可用

預設:開啟。

軟體包文件:S1030

S1031:省略迴圈周圍的冗餘 nil 檢查

您可以使用 range 在 nil slice 和 map 上,迴圈將根本不會執行。這使得迴圈周圍的額外 nil 檢查是不必要的。

之前

if s != nil {
    for _, x := range s {
        ...
    }
}

之後

for _, x := range s {
    ...
}

自 2017.1 版本起可用

預設:開啟。

軟體包文件:S1031

S1032:使用 sort.Ints(x)、sort.Float64s(x) 和 sort.Strings(x)

sort.Ints、sort.Float64s 和 sort.Strings 函式比 sort.Sort(sort.IntSlice(x))、sort.Sort(sort.Float64Slice(x)) 和 sort.Sort(sort.StringSlice(x)) 更易於閱讀。

之前

sort.Sort(sort.StringSlice(x))

之後

sort.Strings(x)

自 2019.1 版本起可用

預設:開啟。

軟體包文件:S1032

S1033:對 'delete' 的呼叫周圍有不必要的保護

在 nil map 上呼叫 delete 是一個 no-op。

自 2019.2 版本起可用

預設:開啟。

軟體包文件:S1033

S1034:使用型別斷言的結果來簡化 case

自 2019.2 版本起可用

預設:開啟。

軟體包文件:S1034

S1035:在 net/http.Header 上呼叫方法時,net/http.CanonicalHeaderKey 呼叫是多餘的

net/http.Header 的方法,即 Add、Del、Get 和 Set,在操作 map 之前已經對給定的 header 名稱進行了規範化。

自 2020.1 版本起可用

預設:開啟。

軟體包文件:S1035

S1036:不必要的 map 訪問保護

當訪問一個尚不存在的 map 鍵時,會得到一個零值。通常,零值是一個合適的值,例如在使用 append 或進行整數數學運算時。

以下

if _, ok := m["foo"]; ok {
    m["foo"] = append(m["foo"], "bar")
} else {
    m["foo"] = []string{"bar"}
}

可以簡化為

m["foo"] = append(m["foo"], "bar")

if _, ok := m2["k"]; ok {
    m2["k"] += 4
} else {
    m2["k"] = 4
}

可以簡化為

m["k"] += 4

自 2020.1 版本起可用

預設:開啟。

軟體包文件:S1036

S1037:不必要的睡眠方式

使用帶有接收來自 time.After 結果的單個 case 的 select 語句是一種非常複雜的睡眠方式,可以透過簡單的 time.Sleep 呼叫更簡單地表達。

自 2020.1 版本起可用

預設:開啟。

軟體包文件:S1037

S1038:列印格式化字串的方式過於複雜

與其使用 fmt.Print(fmt.Sprintf(…)),不如使用 fmt.Printf(…)。

自 2020.1 版本起可用

預設:開啟。

軟體包文件:S1038

S1039:不必要的 fmt.Sprint 使用

用單個字串引數呼叫 fmt.Sprint 是不必要的,並且等同於直接使用字串。

自 2020.1 版本起可用

預設:開啟。

軟體包文件:S1039

S1040:型別斷言到當前型別

型別斷言 x.(SomeInterface),當 x 的型別已經是 SomeInterface 時,只有當 x 為 nil 時才會失敗。通常,這是 x 具有不同型別時的遺留程式碼,您可以安全地刪除型別斷言。如果您想檢查 x 是否不為 nil,請考慮明確使用實際的 if x == nil 比較,而不是依賴型別斷言的 panic。

自 2021.1 版本起可用

預設:開啟。

軟體包文件:S1040

SA1000:無效的正則表示式

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA1000": true} 來啟用。

軟體包文件:SA1000

SA1001:無效的模板

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA1001

SA1002:time.Parse 中的格式無效

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA1002": true} 來啟用。

軟體包文件:SA1002

SA1003:encoding/binary 函式不支援的引數

encoding/binary 包只能序列化具有已知大小的型別。這排除了 int 和 uint 型別的使用,因為它們的大小在不同架構上是不同的。此外,它不支援序列化 map、channel、string 或函式。

在 Go 1.8 之前,bool 也不受支援。

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA1003": true} 來啟用。

軟體包文件:SA1003

SA1004:time.Sleep 中可疑的小無型別常量

time.Sleep 函式接受 time.Duration 作為其唯一引數。Duration 以納秒為單位。因此,呼叫 time.Sleep(1) 將睡眠 1 納秒。這是常見的 bug 來源,因為其他語言中的 sleep 函式通常接受秒或毫秒。

time 包提供了像 time.Second 這樣的常量來表示長持續時間。這些可以與算術結合以表示任意持續時間,例如 5 * time.Second 表示 5 秒。

如果您確實打算睡眠很短的時間,請使用 n * time.Nanosecond 來告知 Staticcheck 您確實打算睡眠一定量的納秒。

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA1004

SA1005:exec.Command 的第一個引數無效

os/exec 直接執行程式(在 Unix 系統上使用 fork 和 exec 系統呼叫的變體)。這不應與在 shell 中執行命令混淆。shell 支援輸入重定向、管道和一般指令碼等功能。shell 還負責將使用者的輸入分割成程式名稱及其引數。例如,與

ls / /tmp

等效的是

exec.Command("ls", "/", "/tmp")

如果您想在 shell 中執行命令,可以考慮使用以下類似方式——但請注意,並非所有系統,特別是 Windows,都會有 /bin/sh 程式

exec.Command("/bin/sh", "-c", "ls | grep Awesome")

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA1005

SA1007:net/url.Parse 中的 URL 無效

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA1007": true} 來啟用。

軟體包文件:SA1007

SA1008:http.Header map 中的非規範鍵

http.Header map 中的鍵是規範的,這意味著它們遵循大小寫字母的特定組合。諸如 http.Header.Add 和 http.Header.Del 等方法在操作 map 之前會將輸入轉換為此規範形式。

直接操作 http.Header map 時,與使用提供的方法不同,應注意堅持使用規範形式,以避免不一致。以下程式碼片段演示了一個此類不一致之處

h := http.Header{}
h["etag"] = []string{"1234"}
h.Add("etag", "5678")
fmt.Println(h)

// Output:
// map[Etag:[5678] etag:[1234]]

獲取鍵的規範形式的最簡單方法是使用 http.CanonicalHeaderKey。

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA1008

SA1010:(*regexp.Regexp).FindAll 呼叫時 n == 0,這將始終返回零結果

如果 n >= 0,則函式最多返回 n 個匹配/子匹配。要返回所有結果,請指定負數。

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA1010": true} 來啟用。

軟體包文件:SA1010

SA1011:'strings' 包中的各種方法期望有效的 UTF-8,但提供了無效輸入

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA1011": true} 來啟用。

軟體包文件:SA1011

SA1012:將 nil context.Context 傳遞給函式,請考慮使用 context.TODO 代替

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA1012

SA1013:io.Seeker.Seek 被呼叫時,whence 常量作為第一個引數,但應該是第二個

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA1013

SA1014:將非指標值傳遞給 Unmarshal 或 Decode

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA1014": true} 來啟用。

軟體包文件:SA1014

SA1015:以可能洩漏的方式使用 time.Tick。考慮使用 time.NewTicker,並且僅在測試、命令和無限函式中使用 time.Tick

在 Go 1.23 之前,time.Ticker 必須關閉才能被垃圾回收。由於 time.Tick 無法關閉底層 ticker,重複使用它會導致記憶體洩漏。

Go 1.23 修復了此問題,允許在未關閉的情況下收集 ticker。

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA1015": true} 來啟用。

軟體包文件:SA1015

SA1016:捕獲無法捕獲的訊號

並非所有訊號都可以被程序攔截。特別是在類 Unix 系統上,syscall.SIGKILL 和 syscall.SIGSTOP 訊號永遠不會傳遞給程序,而是由核心直接處理。因此,嘗試處理這些訊號是徒勞的。

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA1016

SA1017:與 os/signal.Notify 一起使用的 channel 應是緩衝的

os/signal 包在傳遞訊號時使用非阻塞 channel 傳送。如果 channel 的接收端未準備好,並且 channel 是無緩衝的或已滿,則訊號將被丟棄。為避免錯過訊號,應緩衝 channel 並使用適當的大小。對於僅用於通知單個訊號值的 channel,緩衝區大小為 1 就足夠了。

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA1017": true} 來啟用。

軟體包文件:SA1017

SA1018:strings.Replace 呼叫時 n == 0,這沒有任何效果

當 n == 0 時,將替換零個例項。要替換所有例項,請使用負數,或使用 strings.ReplaceAll。

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA1018": true} 來啟用。

軟體包文件:SA1018

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA1020": true} 來啟用。

軟體包文件:SA1020

SA1021:使用 bytes.Equal 比較兩個 net.IP

net.IP 將 IPv4 或 IPv6 地址儲存為位元組 slice。然而,IPv4 地址的 slice 長度可以是 4 或 16 位元組,使用不同的表示 IPv4 地址的方式。為了正確比較兩個 net.IP,應使用 net.IP.Equal 方法,因為它同時考慮了兩種表示。

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA1021": true} 來啟用。

軟體包文件:SA1021

SA1023:修改 io.Writer 實現中的緩衝區

Write 不能修改 slice 資料,即使是臨時的。

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA1023": true} 來啟用。

軟體包文件:SA1023

SA1024:string 的 cutset 包含重複字元

strings.TrimLeft 和 strings.TrimRight 函式接受 cutsets,而不是字首。cutset 被視為要從字串中刪除的字元集。例如,

strings.TrimLeft("42133word", "1234")

將得到字串“word”——從字串左側刪除任何是 1、2、3 或 4 的字元。

為了從另一個字串中刪除一個字串,請使用 strings.TrimPrefix 代替。

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA1024": true} 來啟用。

軟體包文件:SA1024

SA1025:無法正確使用 (*time.Timer).Reset 的返回值

自 2019.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA1025": true} 來啟用。

軟體包文件:SA1025

SA1026:不能 marshaling channel 或 function

自 2019.2 版本起可用

預設:關閉。透過設定 "analyses": {"SA1026": true} 來啟用。

軟體包文件:SA1026

SA1027:64 位變數的原子訪問必須是 64 位對齊的

在 ARM、x86-32 和 32 位 MIPS 上,呼叫者有責任安排 64 位字進行原子訪問的 64 位對齊。變數或已分配的 struct、array 或 slice 中的第一個字可以依賴於 64 位對齊。

您可以使用 structlayout 工具檢查 struct 中欄位的對齊方式。

自 2019.2 版本起可用

預設:關閉。透過設定 "analyses": {"SA1027": true} 來啟用。

軟體包文件:SA1027

SA1028:sort.Slice 只能用於 slice

sort.Slice 的第一個引數必須是 slice。

自 2020.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA1028": true} 來啟用。

軟體包文件:SA1028

SA1029:context.WithValue 呼叫中的不當鍵

提供的鍵必須是可比較的,並且不應是 string 或任何其他內建型別,以避免包之間的衝突。WithValue 的使用者應為鍵定義自己的型別。

為了避免在分配給 interface{} 時分配,context 鍵通常具有 struct{} 的具體型別。或者,匯出的 context 鍵變數的靜態型別應為指標或介面。

自 2020.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA1029": true} 來啟用。

軟體包文件:SA1029

SA1030:strconv 函式呼叫中的引數無效

此檢查驗證 strconv 中各種解析和格式化函式的 format、number base 和 bit size 引數。

自 2021.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA1030": true} 來啟用。

軟體包文件:SA1030

SA1031:傳遞給編碼器的重疊位元組 slice

在形式為 Encode(dst, src) 的編碼函式中,dst 和 src 被發現引用了相同的記憶體。這可能導致 src 位元組在讀取之前被覆蓋,當編碼器每位元組寫入多於一個位元組時。

自 2024.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA1031": true} 來啟用。

軟體包文件:SA1031

SA1032:errors.Is 引數順序錯誤

errors.Is 函式的第一個引數是我們擁有的錯誤,第二個引數是我們正在嘗試匹配的錯誤。例如

if errors.Is(err, io.EOF) { ... }

此檢查檢測到某些引數被交換的情況。它會標記任何第一個引數引用包級錯誤變數的呼叫,例如

if errors.Is(io.EOF, err) { /* this is wrong */ }

自 2024.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA1032": true} 來啟用。

軟體包文件:SA1032

SA2001:空臨界區,是否打算延遲解鎖?

下面這種空臨界區

mu.Lock()
mu.Unlock()

非常可能是拼寫錯誤,原本的意思是

mu.Lock()
defer mu.Unlock()

請注意,有時空臨界區可能有用,作為一種訊號機制來等待另一個 goroutine。很多時候,有更簡單的方法可以達到相同的效果。當不是這種情況時,程式碼應有充分的註釋以避免混淆。將此類註釋與 //lint:ignore 指令結合使用可用於抑制這種罕見的誤報。

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA2001

SA2002:在 goroutine 中呼叫 testing.T.FailNow 或 SkipNow,這是不允許的

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA2002": true} 來啟用。

軟體包文件:SA2002

SA2003:在鎖定後立即延遲 Lock,很可能意味著延遲 Unlock

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA2003": true} 來啟用。

軟體包文件:SA2003

SA3000:TestMain 未呼叫 os.Exit,隱藏了測試失敗

測試可執行檔案(以及 'go test')如果任何測試失敗,將以非零狀態碼退出。當指定自己的 TestMain 函式時,您有責任安排此行為,透過呼叫 os.Exit 並傳入正確的程式碼。正確的程式碼由 (*testing.M).Run 返回,因此實現 TestMain 的常用方法是使其以 os.Exit(m.Run()) 結束。

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA3000

SA3001:在 benchmark 中賦值給 b.N 會扭曲結果

testing 包動態設定 b.N 以提高 benchmark 的可靠性,並將其用於計算以確定單個操作的持續時間。Benchmark 程式碼不得更改 b.N,因為這會使結果失真。

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA3001

SA4000:二元運算子兩側的表示式相同

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA4000

SA4001:&*x 被簡化為 x,它不會複製 x

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA4001

SA4003:與負數比較無符號值是毫無意義的

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA4003

SA4004:迴圈在一次迭代後無條件退出

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA4004

SA4005:欄位賦值永遠不會被觀察到。您是想使用指標接收器嗎?

自 2021.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA4005": true} 來啟用。

軟體包文件:SA4005

SA4006:分配給變數的值在被覆蓋之前從未被讀取。忘記了錯誤檢查還是死程式碼?

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA4006": true} 來啟用。

軟體包文件:SA4006

SA4008:迴圈條件中的變數永遠不會改變,您是否遞增了錯誤的變數?

例如

for i := 0; i < 10; j++ { ... }

這可能也發生在迴圈因為無條件的終止迴圈的控制流而只能執行一次的情況下。例如,當迴圈體包含無條件的 break、return 或 panic 時

func f() {
    panic("oops")
}
func g() {
    for i := 0; i < 10; i++ {
        // f unconditionally calls panic, which means "i" is
        // never incremented.
        f()
    }
}

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA4008": true} 來啟用。

軟體包文件:SA4008

SA4009:函式引數在其首次使用前被覆蓋

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA4009": true} 來啟用。

軟體包文件:SA4009

SA4010:append 的結果永遠不會在任何地方被觀察到

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA4010": true} 來啟用。

軟體包文件:SA4010

SA4011:無效果的 break 語句。您是想 break 出外層迴圈嗎?

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA4011

SA4012:將值與 NaN 比較,儘管沒有值等於 NaN

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA4012": true} 來啟用。

軟體包文件:SA4012

SA4013:對布林值進行兩次取反(!!b)等同於寫 b。這要麼是冗餘的,要麼是拼寫錯誤。

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA4013

SA4014:if/else if 鏈具有重複的條件且無副作用;如果條件第一次沒有匹配,第二次也不會匹配

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA4014

SA4015:對從整數轉換的浮點數呼叫 math.Ceil 等函式沒有任何用處

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA4015": true} 來啟用。

軟體包文件:SA4015

SA4016:某些按位操作,例如 x ^ 0,沒有任何用處

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA4016

SA4017:丟棄沒有副作用的函式的返回值,使呼叫無意義

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA4017": true} 來啟用。

軟體包文件:SA4017

SA4018:變數的自賦值

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA4018": true} 來啟用。

軟體包文件:SA4018

SA4019:同一檔案中的多個相同構建約束

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA4019

SA4020:型別 switch 中不可達的 case 子句

在如下型別 switch 中

type T struct{}
func (T) Read(b []byte) (int, error) { return 0, nil }

var v any = T{}

switch v.(type) {
case io.Reader:
    // ...
case T:
    // unreachable
}

第二個 case 子句永遠無法到達,因為 T 實現 io.Reader 並且 case 子句按源順序進行評估。

另一個例子

type T struct{}
func (T) Read(b []byte) (int, error) { return 0, nil }
func (T) Close() error { return nil }

var v any = T{}

switch v.(type) {
case io.Reader:
    // ...
case io.ReadCloser:
    // unreachable
}

儘管 T 有 Close 方法,因此實現了 io.ReadCloser,但 io.Reader 總是先匹配。io.Reader 的方法集是 io.ReadCloser 的子集。因此,不可能匹配第二個 case 而不匹配第一個 case。

結構上等價的介面

前一個示例的一個特殊情況是結構上等價的介面。給定以下宣告

type T error
type V error

func doSomething() error {
    err, ok := doAnotherThing()
    if ok {
        return T(err)
    }

    return U(err)
}

以下型別 switch 將具有不可達的 case 子句

switch doSomething().(type) {
case T:
    // ...
case V:
    // unreachable
}

T 總是會先於 V 匹配,因為它們在結構上是等價的,因此 doSomething() 的返回值同時實現了兩者。

自 2019.2 版本起可用

預設:開啟。

軟體包文件:SA4020

SA4022:將變數的地址與 nil 進行比較

像 ‘if &x == nil’ 這樣的程式碼是無意義的,因為取一個變數的地址總是會得到一個非 nil 指標。

自 2020.1 版本起可用

預設:開啟。

軟體包文件:SA4022

SA4023:不可能將介面值與無型別 nil 進行比較

在底層,介面由兩個元素實現:型別 T 和值 V。V 是一個具體值,例如 int、struct 或指標,絕不是介面本身,並且具有型別 T。例如,如果我們將在介面中儲存 int 值 3,則生成的介面值將是,示意性地,(T=int, V=3)。值 V 也被稱為介面的動態值,因為在程式執行期間,給定的介面變數可能持有不同的值 V(和對應的型別 T)。

只有當 V 和 T 都未設定時,介面值才為 nil,(T=nil, V 未設定)。特別是,nil 介面將始終持有一個 nil 型別。如果我們儲存型別為 *int 的 nil 指標在介面值中,則內部型別將是 *int,無論指標的值如何:(T=*int, V=nil)。因此,即使指標值 V 為 nil,這樣的介面值也將是非 nil 的。

這種情況可能令人困惑,並且在將 nil 值儲存在介面值(例如錯誤返回值)中時出現

func returnsError() error {
    var p *MyError = nil
    if bad() {
        p = ErrBad
    }
    return p // Will always return a non-nil error.
}

如果一切順利,函式返回一個 nil p,因此返回值是一個錯誤介面值,其中包含 (T=*MyError, V=nil)。這意味著如果呼叫者將返回的錯誤與 nil 進行比較,即使什麼壞事都沒發生,看起來也總是像有錯誤一樣。要向呼叫者返回一個真正的 nil 錯誤,函式必須返回一個顯式的 nil

func returnsError() error {
    if bad() {
        return ErrBad
    }
    return nil
}

對於返回錯誤的函式,最好始終在簽名中使用 error 型別(如上所示),而不是具體的型別(如 *MyError),以幫助確保錯誤已正確建立。例如,os.Open 返回一個錯誤,即使(如果非 nil)它總是具體型別為 *os.PathError。

在任何時候使用介面時,都可能出現與此處描述的類似情況。只要記住,如果任何具體值已儲存在介面中,該介面將不會為 nil。有關更多資訊,請參閱“反射定律”:https://golang.org.tw/doc/articles/laws_of_reflection.html

此文字摘自 https://golang.org.tw/doc/faq#nil_error,根據知識共享署名 3.0 許可授權。

自 2020.2 版本起可用

預設:關閉。透過設定 "analyses": {"SA4023": true} 來啟用。

軟體包文件:SA4023

SA4024:檢查內建函式的可能不可能的返回值

len 和 cap 內建函式的返回值不能為負數。

參見 https://golang.org.tw/pkg/builtin/#lenhttps://golang.org.tw/pkg/builtin/#cap

示例

if len(slice) < 0 {
    fmt.Println("unreachable code")
}

自 2021.1 版本起可用

預設:開啟。

軟體包文件:SA4024

SA4025:字面量整數除法結果為零

當除以兩個整數常量時,結果也將是整數。因此,2 / 3 這樣的除法結果為 0。這對以下所有示例都成立

_ = 2 / 3
const _ = 2 / 3
const _ float64 = 2 / 3
_ = float64(2 / 3)

Staticcheck 會標記此類除法,如果雙方都是整數字面量,因為除法截斷為零的可能性極小。Staticcheck 不會標記涉及命名常量的整數除法,以避免產生過多的誤報。

自 2021.1 版本起可用

預設:開啟。

軟體包文件:SA4025

SA4026:Go 常量無法表示負零

在 IEEE 754 浮點數運算中,零有一個符號,可以是正的或負的。這在某些數值程式碼中可能很有用。

然而,Go 常量無法表示負零。這意味著字面量 -0.0 和 0.0 具有相同的理想值(零),並且在執行時都將表示正零。

要顯式可靠地建立負零,您可以使用 math.Copysign 函式:math.Copysign(0, -1)。

自 2021.1 版本起可用

預設:開啟。

軟體包文件:SA4026

SA4027:(*net/url.URL).Query 返回副本,修改它不會改變 URL

(*net/url.URL).Query 解析 net/url.URL.RawQuery 的當前值並將其作為 net/url.Values 型別的 map 返回。除非將 map 編碼並分配給 URL 的 RawQuery,否則對此 map 的後續更改不會影響 URL。

因此,以下程式碼模式是一個昂貴的 no-op:u.Query().Add(key, value)。

自 2021.1 版本起可用

預設:開啟。

軟體包文件:SA4027

SA4028:x % 1 始終為零

自 2022.1 版本起可用

預設:開啟。

軟體包文件:SA4028

SA4029:對 slice 進行排序的無效嘗試

sort.Float64Slice、sort.IntSlice 和 sort.StringSlice 是型別,而不是函式。執行 x = sort.StringSlice(x) 沒有任何作用,更不用說對任何值進行排序了。正確的用法是 sort.Sort(sort.StringSlice(x)) 或 sort.StringSlice(x).Sort(),但有更方便的輔助函式,即 sort.Float64s、sort.Ints 和 sort.Strings。

自 2022.1 版本起可用

預設:開啟。

軟體包文件:SA4029

SA4030:生成隨機數的無效嘗試

math/rand 包中接受上限的函式,例如 Intn,在半開區間 [0,n) 中生成隨機數。換句話說,生成的數字將 >= 0 且 < n——它們不包括 n。因此,rand.Intn(1) 不會生成 0 或 1,它總是生成 0。

自 2022.1 版本起可用

預設:開啟。

軟體包文件:SA4030

SA4031:檢查永不為 nil 的值是否為 nil

自 2022.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA4031": true} 來啟用。

軟體包文件:SA4031

SA4032:將 runtime.GOOS 或 runtime.GOARCH 與不可能的值進行比較

自 2024.1 版本起可用

預設:開啟。

軟體包文件:SA4032

SA5000:賦值給 nil map

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA5000": true} 來啟用。

軟體包文件:SA5000

SA5001:在檢查可能錯誤之前延遲 Close

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA5001

SA5002:空 for 迴圈('for {}')會產生 spin 並可能阻塞排程器

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA5002": true} 來啟用。

軟體包文件:SA5002

SA5003:無限迴圈中的 defer 將永遠不會執行

defer 的作用域是圍繞的函式,而不是圍繞的塊。在一個永不返回的函式中,即包含無限迴圈的函式,defer 將永遠不會執行。

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA5003

SA5004:'for { select { ...' 帶有空 default 分支會產生 spin

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA5004

SA5005:finalizer 引用被 finalizer 的物件,阻止垃圾回收

finalizer 是與物件關聯的函式,當垃圾回收器準備好收集該物件時執行,即當物件不再被任何東西引用時。

然而,如果 finalizer 引用了該物件,它將始終作為對該物件的最終引用,阻止垃圾回收器收集該物件。finalizer 將永遠不會執行,物件也將永遠不會被收集,導致記憶體洩漏。這就是為什麼 finalizer 應該使用其第一個引數來操作物件。這樣,在將物件傳遞給 finalizer 之前,引用的數量可以暫時變為零。

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA5005": true} 來啟用。

軟體包文件:SA5005

SA5007:無限遞迴呼叫

遞迴呼叫自身的函式需要有退出條件。否則,它將無限遞迴,直到系統記憶體耗盡。

此問題可能由簡單的 bug 引起,例如忘記新增退出條件。它也可以“故意”發生。某些語言具有尾呼叫最佳化,使得某些無限遞迴呼叫是安全的。然而,Go 不實現 TCO,因此應該使用迴圈。

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA5007": true} 來啟用。

軟體包文件:SA5007

SA5008:無效的結構標籤

自 2019.2 版本起可用

預設:開啟。

軟體包文件:SA5008

SA5010:不可能的型別斷言

某些型別斷言可以被靜態證明是不可能的。當型別斷言的兩個引數的方法集發生衝突時,例如透過包含具有不同簽名的相同方法。

Go 編譯器在從介面值斷言到具體型別時已應用此檢查。如果具體型別缺少介面中的方法,或者函式簽名不匹配,則型別斷言永遠無法成功。

此檢查在從一個介面斷言到另一個介面時應用相同的邏輯。如果兩個介面型別包含相同的方法但具有不同的簽名,那麼型別斷言也永遠無法成功。

自 2020.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA5010": true} 來啟用。

軟體包文件:SA5010

SA5011:可能出現 nil 指標解引用

指標被無條件解引用,同時也在另一個地方進行了 nil 檢查。這表明指標可能是 nil,解引用它可能會導致 panic。這通常是由於程式碼順序不當或缺少 return 語句造成的。請考慮以下示例

func fn(x *int) {
    fmt.Println(*x)

    // This nil check is equally important for the previous dereference
    if x != nil {
        foo(*x)
    }
}

func TestFoo(t *testing.T) {
    x := compute()
    if x == nil {
        t.Errorf("nil pointer received")
    }

    // t.Errorf does not abort the test, so if x is nil, the next line will panic.
    foo(*x)
}

Staticcheck 嘗試推斷哪些函式會中止控制流。例如,它知道函式在呼叫 panic 或 log.Fatal 後不會繼續執行。然而,有時這種檢測會失敗,特別是在有條件的情況下。請考慮以下示例

func Log(msg string, level int) {
    fmt.Println(msg)
    if level == levelFatal {
        os.Exit(1)
    }
}

func Fatal(msg string) {
    Log(msg, levelFatal)
}

func fn(x *int) {
    if x == nil {
        Fatal("unexpected nil pointer")
    }
    fmt.Println(*x)
}

Staticcheck 會標記 x 的解引用,儘管它完全安全。Staticcheck 無法推斷出對 Fatal 的呼叫會退出程式。目前,最簡單的解決方法是修改 Fatal 的定義,如下所示

func Fatal(msg string) {
    Log(msg, levelFatal)
    panic("unreachable")
}

我們還硬編碼了來自常見日誌記錄包(如 logrus)的函式。如果我們缺少對流行包的支援,請提交一個 issue。

自 2020.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA5011": true} 來啟用。

軟體包文件:SA5011

SA5012:將奇數大小的 slice 傳遞給期望偶數大小的函式

一些接受 slice 作為引數的函式期望 slice 具有偶數個元素。通常,這些函式將 slice 中的元素視為對。例如,strings.NewReplacer 接受成對的舊字串和新字串,用奇數個元素呼叫它將是錯誤的。

自 2020.2 版本起可用

預設:關閉。透過設定 "analyses": {"SA5012": true} 來啟用。

軟體包文件:SA5012

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA6000": true} 來啟用。

軟體包文件:SA6000

SA6001:透過位元組 slice 索引 map 時錯過了最佳化機會

Map 鍵必須是可比較的,這排除了位元組 slice 的使用。這通常導致使用字串鍵並將位元組 slice 轉換為字串。

通常,將位元組 slice 轉換為字串需要複製資料併產生分配。然而,編譯器會識別 m[string(b)] 並直接使用 b 的資料,而無需複製它,因為它知道在 map 查詢期間資料不會改變。這導致了反直覺的情況,即

k := string(b)
println(m[k])
println(m[k])

將比以下方式效率低

println(m[string(b)])
println(m[string(b)])

因為第一種版本需要複製和分配,而第二種則不需要。

有關此最佳化的歷史記錄,請檢視 Go 儲存庫中的提交 f5f5a8b6209f84961687d993b93ea0d397f5d5bf。

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA6001": true} 來啟用。

軟體包文件:SA6001

SA6002:將非指標值儲存在 sync.Pool 中會分配記憶體

sync.Pool 用於避免不必要的分配並減少垃圾回收器的工作量。

當將非指標值傳遞給接受介面的函式時,需要將該值放在堆上,這意味著額外的分配。Slice 是放入 sync.Pool 的常見項,它們是具有 3 個欄位(length、capacity 和指向陣列的指標)的結構。為了避免額外的分配,應儲存指向 slice 的指標。

有關此問題的討論,請參閱 https://go-review.googlesource.com/c/go/+/24371 上的註釋。

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA6002": true} 來啟用。

軟體包文件:SA6002

SA6003:在 range 之前將字串轉換為 rune slice

您可能想迴圈遍歷字串中的 rune。與其將字串轉換為 rune slice 並迴圈遍歷它,不如直接迴圈遍歷字串本身。即,

for _, r := range s {}

for _, r := range []rune(s) {}

將產生相同的值。然而,第一種方法將更快,並避免不必要的記憶體分配。

請注意,如果您對索引感興趣,range 字串和 range rune slice 會產生不同的索引。前者產生位元組偏移量,而後者產生 rune slice 中的索引。

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA6003": true} 來啟用。

軟體包文件:SA6003

SA6005:使用 strings.ToLower 或 strings.ToUpper 進行低效的字串比較

將兩個字串轉換為相同的大小寫並像這樣進行比較

if strings.ToLower(s1) == strings.ToLower(s2) {
    ...
}

比使用 strings.EqualFold(s1, s2) 進行比較要昂貴得多。這是由於記憶體使用以及計算複雜性。

strings.ToLower 需要為新字串分配記憶體,並且會完全轉換兩個字串,即使它們在第一個位元組就不同。另一方面,strings.EqualFold 一次比較一個字元。它不需要建立兩個中間字串,並且可以在找到第一個不匹配的字元後立即返回。

有關此問題的更深入解釋,請參閱 https://blog.digitalocean.com/how-to-efficiently-compare-strings-in-go/

自 2019.2 版本起可用

預設:開啟。

軟體包文件:SA6005

SA6006:使用 io.WriteString 寫入 []byte

使用 io.WriteString 寫入位元組 slice,例如

io.WriteString(w, string(b))

是不必要的且低效的。從 []byte 到 string 的轉換必須分配和複製資料,而我們可以簡單地使用 w.Write(b) 代替。

自 2024.1 版本起可用

預設:開啟。

軟體包文件:SA6006

SA9001:range 迴圈中的 defer 可能不會按預期執行

自 2017.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA9001": true} 來啟用。

軟體包文件:SA9001

SA9002:使用非八進位制的 os.FileMode,看起來像是有意使用八進位制。

自 2017.1 版本起可用

預設:開啟。

軟體包文件:SA9002

SA9003:if 或 else 分支中的空主體

自 2017.1 版本起可用,非預設

預設:關閉。透過設定 "analyses": {"SA9003": true} 來啟用。

軟體包文件:SA9003

SA9004:只有第一個常量有顯式型別

在常量宣告中,例如

const (
    First byte = 1
    Second     = 2
)

常量 Second 的型別與常量 First 不同。此構造不應與

const (
    First byte = iota
    Second
)

混淆,其中 First 和 Second 確實具有相同的型別。型別僅在未為常量分配顯式值時才傳遞。

因此,在宣告具有顯式值的列舉時,不要寫

const (
      EnumFirst EnumType = 1
      EnumSecond         = 2
      EnumThird          = 3
)

型別的這種差異會導致各種令人困惑的行為和 bug。

變數宣告中的錯誤型別

這種不正確的列舉最明顯的問題表現為編譯錯誤

package pkg

const (
    EnumFirst  uint8 = 1
    EnumSecond       = 2
)

func fn(useFirst bool) {
    x := EnumSecond
    if useFirst {
        x = EnumFirst
    }
}

編譯失敗,錯誤為

./const.go:11:5: cannot use EnumFirst (type uint8) as type int in assignment

丟失方法集

更微妙的問題發生在具有方法和可選介面的型別上。考慮以下

package main

import "fmt"

type Enum int

func (e Enum) String() string {
    return "an enum"
}

const (
    EnumFirst  Enum = 1
    EnumSecond      = 2
)

func main() {
    fmt.Println(EnumFirst)
    fmt.Println(EnumSecond)
}

這段程式碼將輸出

an enum
2

因為 EnumSecond 沒有顯式型別,因此預設為 int。

自 2019.1 版本起可用

預設:開啟。

軟體包文件:SA9004

SA9005:嘗試 marshal 一個沒有公共欄位或自定義 marshal 的 struct

encoding/json 和 encoding/xml 包僅對 struct 中的匯出欄位進行操作,不對未匯出欄位進行操作。嘗試 (un)marshal 僅由未匯出欄位組成的 struct 通常是錯誤的。

此檢查不會標記涉及定義了自定義 marshal 行為(例如透過 MarshalJSON 方法)的型別的呼叫。它也不會標記空 struct。

自 2019.2 版本起可用

預設:關閉。透過設定 "analyses": {"SA9005": true} 來啟用。

軟體包文件:SA9005

SA9006:固定大小整數值的可疑位移

將值移位超過其大小將始終清除該值。

例如

v := int8(42)
v >>= 8

將始終結果為 0。

此檢查僅標記固定大小整數值的位移操作。也就是說,int、uint 和 uintptr 永遠不會被標記,以避免在某些奇異但有效的位操作技巧中出現潛在的誤報。

// Clear any value above 32 bits if integers are more than 32 bits.
func f(i int) int {
    v := i >> 32
    v = v << 32
    return i-v
}

自 2020.2 版本起可用

預設:開啟。

軟體包文件:SA9006

SA9007:刪除不應刪除的目錄

幾乎從不正確刪除系統目錄,如 /tmp 或使用者的主目錄。然而,錯誤地這樣做可能相當容易,例如,錯誤地使用 os.TempDir 而不是 ioutil.TempDir,或者忘記在 os.UserHomeDir 的結果後面新增字尾。

編寫

d := os.TempDir()
defer os.RemoveAll(d)

在您的單元測試中將對您系統的穩定性產生毀滅性影響。

此檢查會標記嘗試刪除以下目錄的嘗試

  • os.TempDir
  • os.UserCacheDir
  • os.UserConfigDir
  • os.UserHomeDir

自 2022.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA9007": true} 來啟用。

軟體包文件:SA9007

SA9008:型別斷言的 else 分支可能沒有讀取正確的值

在 if 語句中宣告變數時(如 'if foo := ...; foo {'),相同的變數也將位於 else 分支的作用域中。這意味著在以下示例中

if x, ok := x.(int); ok {
    // ...
} else {
    fmt.Printf("unexpected type %T", x)
}

else 分支中的 x 將引用 x, ok :=; 中的 x,而不是正在進行型別斷言的 x。型別斷言失敗的結果是要斷言的型別的零值,因此 else 分支中的 x 始終具有值 0 和型別 int。

自 2022.1 版本起可用

預設:關閉。透過設定 "analyses": {"SA9008": true} 來啟用。

軟體包文件:SA9008

SA9009:無效的 Go 編譯器指令

找到了一個潛在的 Go 編譯器指令,但由於它以空格開頭而無效。

自 2024.1 版本起可用

預設:開啟。

軟體包文件:SA9009

ST1000:不正確或缺失的包註釋

包必須有一個根據 https://golang.org.tw/wiki/CodeReviewComments#package-comments 中概述的準則格式化的包註釋。

自 2019.1 版本起可用,非預設

預設:關閉。透過設定 "analyses": {"ST1000": true} 來啟用。

軟體包文件:ST1000

ST1001:不推薦使用點匯入

不推薦在外部測試包之外使用點匯入。

dot_import_whitelist 選項可用於白名單某些匯入。

引用 Go 程式碼審查註釋

import . 形式在測試中可能很有用,由於迴圈依賴關係,這些測試不能成為被測試包的一部分

package foo_test

import (
    "bar/testutil" // also imports "foo"
    . "foo"
)

在這種情況下,測試檔案不能在 foo 包中,因為它使用了 bar/testutil,它匯入了 foo。所以我們使用 import . 形式讓檔案假裝屬於 foo 包,即使它不是。除了這種情況之外,請不要在您的程式中使用 import .。它會使程式更難閱讀,因為不清楚 Quux 這樣的名稱是當前包中的頂級識別符號還是匯入包中的。

自 2019.1 版本起可用

選項 dot_import_whitelist

預設:關閉。透過設定 "analyses": {"ST1001": true} 來啟用。

軟體包文件:ST1001

ST1003:選擇的識別符號不佳

識別符號,例如變數名和包名,遵循一定的規則。

有關詳情,請參閱以下連結

自 2019.1 版本起可用,非預設

選項 initialisms

預設:關閉。透過設定 "analyses": {"ST1003": true} 來啟用。

軟體包文件:ST1003

ST1005:錯誤字串格式不正確

錯誤字串遵循一組指南,以確保統一性和良好的可組合性。

引用 Go 程式碼審查註釋

錯誤字串不應大寫(除非以專有名詞或縮寫開頭)或以標點符號結尾,因為它們通常會跟隨其他上下文顯示。也就是說,使用 fmt.Errorf(“something bad”) 而不是 fmt.Errorf(“Something bad”),這樣 log.Printf(“Reading %s: %v”, filename, err) 就可以在訊息中間沒有多餘的大寫字母的情況下進行格式化。

自 2019.1 版本起可用

預設:關閉。透過設定 "analyses": {"ST1005": true} 來啟用。

軟體包文件:ST1005

ST1006:選擇的接收者名稱不當

引用 Go 程式碼審查註釋

方法接收者的名稱應反映其身份;通常,其型別的單字母或雙字母縮寫就足夠了(例如,“c”或“cl”代表“Client”)。不要使用泛稱名稱,如“me”、“this”或“self”,這些識別符號是面嚮物件語言的典型用法,它們更側重於方法而不是函式。名稱不必像方法引數那樣具有描述性,因為它的作用是顯而易見的,並且沒有文件目的。它可以非常簡短,因為它將出現在型別的幾乎所有方法的每一行上;熟悉程度允許簡短。也要保持一致:如果你在一個方法中將接收者稱為“c”,那麼在另一個方法中就不要稱其為“cl”。

自 2019.1 版本起可用

預設:關閉。透過設定 "analyses": {"ST1006": true} 來啟用。

軟體包文件:ST1006

ST1008:函式的錯誤值應為其最後一個返回值

函式的錯誤值應為其最後一個返回值。

自 2019.1 版本起可用

預設:關閉。透過設定 "analyses": {"ST1008": true} 來啟用。

軟體包文件:ST1008

ST1011:time.Duration 型別變數的名稱選擇不當

time.Duration 值表示一段時間,該時間表示為納秒計數。像 5 * time.Microsecond 這樣的表示式會產生 5000 的值。因此,不適合用任何時間單位(如 Msec 或 Milli)來字尾 time.Duration 型別的變數。

自 2019.1 版本起可用

預設:關閉。透過設定 "analyses": {"ST1011": true} 來啟用。

軟體包文件:ST1011

ST1012:錯誤變數的名稱選擇不當

作為 API 的一部分的錯誤變數應命名為 errFoo 或 ErrFoo。

自 2019.1 版本起可用

預設:關閉。透過設定 "analyses": {"ST1012": true} 來啟用。

軟體包文件:ST1012

ST1013:應使用常量代替 HTTP 錯誤程式碼的魔術數字

HTTP 有大量的狀態碼。雖然其中一些是眾所周知的(200、400、404、500),但大多數都不是。net/http 包為所有屬於各種規範的狀態碼提供了常量。建議使用這些常量而不是硬編碼魔術數字,以極大地提高程式碼的可讀性。

自 2019.1 版本起可用

選項 http_status_code_whitelist

預設:關閉。透過設定 "analyses": {"ST1013": true} 來啟用。

軟體包文件:ST1013

ST1015:switch 的 default case 應為第一個或最後一個 case

自 2019.1 版本起可用

預設:關閉。透過設定 "analyses": {"ST1015": true} 來啟用。

軟體包文件:ST1015

ST1016:使用一致的方法接收者名稱

自 2019.1 版本起可用,非預設

預設:關閉。透過設定 "analyses": {"ST1016": true} 來啟用。

軟體包文件:ST1016

ST1017:不要使用 Yoda 條件

Yoda 條件是“if 42 == x”之類的條件,其中字面量位於比較的左側。這些是分配是表示式的語言中常見的慣用法,用於避免“if (x = 42)”之類的錯誤。在 Go 中,不允許這種錯誤,我們更喜歡更慣用的“if x == 42”。

自 2019.2 版本起可用

預設:關閉。透過設定 "analyses": {"ST1017": true} 來啟用。

軟體包文件:ST1017

ST1018:避免在字串字面量中使用零寬度和控制字元

自 2019.2 版本起可用

預設:關閉。透過設定 "analyses": {"ST1018": true} 來啟用。

軟體包文件:ST1018

ST1019:多次匯入同一個包

Go 允許多次匯入同一個包,只要使用不同的匯入別名即可。也就是說,下面的程式碼是有效的

import (
    "fmt"
    fumpt "fmt"
    format "fmt"
    _ "fmt"
)

然而,這種情況很少是故意的。通常,這是由於重構程式碼時意外添加了重複的匯入語句。它也是一項鮮為人知的特性,這可能會導致混淆。

請注意,有時此功能可能會被有意使用(例如,請參閱 https://github.com/golang/go/commit/3409ce39bfd7584523b7a8c150a310cea92d879d)——如果您想在程式碼庫中允許此模式,建議停用此檢查。

自 2020.1 版本起可用

預設:關閉。透過設定 "analyses": {"ST1019": true} 來啟用。

軟體包文件:ST1019

ST1020:匯出函式的文件應以函式名開頭

文件註釋最好是完整的句子,這樣可以支援多種自動呈現方式。第一句應為單句摘要,以宣告的名稱開頭。

如果每個文件註釋都以其描述的專案名稱開頭,則可以使用 go 工具的 doc 子命令並對輸出進行 grep。

有關如何編寫良好文件的更多資訊,請參閱 https://golang.org.tw/doc/effective_go#commentary

自 2020.1 版本起可用,非預設

預設:關閉。透過設定 "analyses": {"ST1020": true} 來啟用。

軟體包文件:ST1020

ST1021:匯出型別的文件應以型別名開頭

文件註釋最好是完整的句子,這樣可以支援多種自動呈現方式。第一句應為單句摘要,以宣告的名稱開頭。

如果每個文件註釋都以其描述的專案名稱開頭,則可以使用 go 工具的 doc 子命令並對輸出進行 grep。

有關如何編寫良好文件的更多資訊,請參閱 https://golang.org.tw/doc/effective_go#commentary

自 2020.1 版本起可用,非預設

預設:關閉。透過設定 "analyses": {"ST1021": true} 來啟用。

軟體包文件:ST1021

ST1022:匯出變數或常量的文件應以變數名開頭

文件註釋最好是完整的句子,這樣可以支援多種自動呈現方式。第一句應為單句摘要,以宣告的名稱開頭。

如果每個文件註釋都以其描述的專案名稱開頭,則可以使用 go 工具的 doc 子命令並對輸出進行 grep。

有關如何編寫良好文件的更多資訊,請參閱 https://golang.org.tw/doc/effective_go#commentary

自 2020.1 版本起可用,非預設

預設:關閉。透過設定 "analyses": {"ST1022": true} 來啟用。

軟體包文件:ST1022

ST1023:變數宣告中的冗餘型別

自 2021.1 版本起可用,非預設

預設:關閉。透過設定 "analyses": {"ST1023": true} 來啟用。

軟體包文件:ST1023

appends:檢查 append 後缺少值

此檢查器報告呼叫 append 時沒有傳遞要附加到切片的值。

s := []string{"a", "b", "c"}
_ = append(s)

此類呼叫始終是空操作,並且經常表明存在潛在的錯誤。

預設:開啟。

軟體包文件:appends

asmdecl:報告彙編檔案與 Go 宣告之間的不匹配

預設:開啟。

軟體包文件:asmdecl

assign:檢查無用的賦值

此檢查器報告 x = x 或 a[i] = a[i] 形式的賦值。這些幾乎總是無用的,即使它們不是無用的,通常也是一個錯誤。

預設:開啟。

軟體包文件:assign

atomic:檢查使用 sync/atomic 包的常見錯誤

atomic 檢查器查詢形式為

x = atomic.AddUint64(&x, 1)

的賦值語句,這些語句不是原子的。

預設:開啟。

軟體包文件:atomic

atomicalign:檢查對 sync/atomic 函式的非 64 位對齊引數

預設:開啟。

軟體包文件:atomicalign

bools:檢查涉及布林運算子的常見錯誤

預設:開啟。

軟體包文件:bools

buildtag:檢查 //go:build 和 // +build 指令

預設:開啟。

軟體包文件:buildtag

cgocall:檢測 cgo 指標傳遞規則的某些違規行為

檢查無效的 cgo 指標傳遞。這會查詢使用 cgo 呼叫 C 程式碼的程式碼,傳遞的值型別幾乎總是與 cgo 指標共享規則不符。具體來說,它會警告嘗試將 Go 的 chan、map、func 或 slice 傳遞給 C,無論是直接傳遞、透過指標、陣列還是結構體傳遞。

預設:開啟。

軟體包文件:cgocall

composites:檢查未鍵入的複合字面量

此分析器會報告一個診斷,用於從其他包匯入的結構體型別的複合字面量,這些字面量未使用欄位鍵入的語法。此類字面量很脆弱,因為向結構體新增新欄位(即使是未匯出的)也會導致編譯失敗。

例如,

err = &net.DNSConfigError{err}

應替換為

err = &net.DNSConfigError{Err: err}

預設:開啟。

軟體包文件:composites

copylocks:檢查被錯誤地按值複製的鎖

無意中複製包含鎖(如 sync.Mutex 或 sync.WaitGroup)的值可能會導致兩個副本都發生故障。通常,此類值應透過指標引用。

預設:開啟。

軟體包文件:copylocks

deepequalerrors:檢查對錯誤值呼叫 reflect.DeepEqual

deepequalerrors 檢查器查詢形式為

reflect.DeepEqual(err1, err2)

的呼叫,其中 err1 和 err2 是錯誤。不鼓勵使用 reflect.DeepEqual 來比較錯誤。

預設:開啟。

軟體包文件:deepequalerrors

defers:報告 defer 語句中的常見錯誤

defers 分析器在 defer 語句會導致 time.Since 呼叫非 defer 時報告診斷,因為經驗表明這幾乎總是一個錯誤。

例如

start := time.Now()
...
defer recordLatency(time.Since(start)) // error: call to time.Since is not deferred

正確的程式碼是

defer func() { recordLatency(time.Since(start)) }()

預設:開啟。

軟體包文件:defers

deprecated:檢查已棄用的識別符號的使用情況

deprecated 分析器查詢已棄用的符號和包匯入。

要了解 Go 對已棄用識別符號的文件記錄和訊號約定,請參閱 https://golang.org.tw/wiki/Deprecated

預設:開啟。

軟體包文件:deprecated

directive:檢查 Go 工具鏈指令,如 //go:debug

此分析器檢查包目錄中所有 Go 原始檔中的已知 Go 工具鏈指令的問題,即使是那些被 //go:build 約束排除的檔案,以及所有非 Go 原始檔。

對於 //go:debug(請參閱 https://golang.org.tw/doc/godebug),分析器會檢查指令是否僅放置在 Go 原始檔中,僅放在包註釋上方,並且僅在 package main 或 *_test.go 檔案中。

將來可能會支援其他已知指令。

此分析器不檢查 //go:build,該檢查由 buildtag 分析器處理。

預設:開啟。

軟體包文件:directive

embed:檢查 //go:embed 指令的使用情況

此分析器會檢查是否匯入了 embed 包(如果存在 //go:embed 指令),並提供一個建議的修復方法來新增缺失的匯入(如果缺失)。

此分析器還檢查 //go:embed 指令是否出現在單個變數宣告之前。

預設:開啟。

軟體包文件:embed

errorsas:報告將非指標或非錯誤值傳遞給 errors.As

errorsas 分析器報告 errors.As 的呼叫,其中第二個引數的型別不是指向實現 error 的型別的指標。

預設:開啟。

軟體包文件:errorsas

fillreturns:建議修復由於返回值的數量不正確而導致的錯誤

此檢查器為“錯誤的返回值數量(需要 %d,得到 %d)”型別的型別錯誤提供建議的修復。例如

func m() (int, string, *bool, error) {
    return
}

將變為

func m() (int, string, *bool, error) {
    return 0, "", nil, nil
}

此功能與 https://github.com/sqs/goreturns 類似。

預設:開啟。

軟體包文件:fillreturns

framepointer:報告在儲存之前破壞了幀指標的彙編

預設:開啟。

軟體包文件:framepointer

gofix:應用基於 go:fix 註釋指令的修復

gofix 分析器會內聯標記為內聯的函式和常量。

函式

給定一個標記為內聯的函式,如下所示

//go:fix inline
func Square(x int) int { return Pow(x, 2) }

此分析器將建議在此函式呼叫處(在同一包或其他包中)進行內聯。

內聯可用於移除對已棄用函式的依賴

// Deprecated: prefer Pow(x, 2).
//go:fix inline
func Square(x int) int { return Pow(x, 2) }

它還可以用於移除對過時包的依賴,例如當匯入路徑發生更改或提供更高主版本時

package pkg

import pkg2 "pkg/v2"

//go:fix inline
func F() { pkg2.F(nil) }

將呼叫 pkg.F() 替換為 pkg2.F(nil) 可能不會對程式產生任何影響,因此此機制提供了一種低風險的方式來更新大量的呼叫。我們建議在可能的情況下,根據新 API 來表達舊 API,以實現自動遷移。

inliner 會小心處理,以避免行為更改,即使是細微的更改,例如引數表示式的求值順序更改。當它無法安全地消除所有引數變數時,它可能會引入一個“繫結宣告”,形式為

var params = args

以正確的順序求值引數表示式並將它們繫結到引數變數。由於生成的程式碼轉換在風格上可能不是最優的,因此可以透過指定 -gofix.allow_binding_decl=false 標誌給分析器驅動程式來停用此類內聯。

(在無法安全地“縮減”呼叫——即用適當替換的函式 f 的主體替換呼叫 f(x)——的情況下,inliner 機制能夠將 f 替換為函式字面量 func(){…}()。但是,gofix 分析器會無條件地丟棄所有此類“字面化”,同樣是出於風格原因。)

常量

給定一個標記為內聯的常量,如下所示

//go:fix inline
const Ptr = Pointer

此分析器將建議將 Ptr 的用法替換為 Pointer。

與函式一樣,內聯可用於替換已棄用的常量和過時包中的常量。

僅當常量定義引用另一個命名常量時,它才能標記為內聯。

“//go:fix inline”註釋必須出現在單個 const 宣告的上方,如上所示;在 const 宣告是組的一部分時,如本例所示

const (
   C = 1
   //go:fix inline
   Ptr = Pointer
)

或在組上方,應用於組中的每個常量

//go:fix inline
const (
    Ptr = Pointer
    Val = Value
)

該提案 https://golang.org.tw/issue/32816 引入了“//go:fix”指令。

您可以使用此(不受官方支援)命令批次應用 gofix 修復

$ go run golang.org/x/tools/internal/gofix/cmd/gofix@latest -test ./...

(不要使用“go get -tool”將 gopls 新增為模組的依賴項;gopls 命令必須從其釋出分支構建。)

預設:開啟。

軟體包文件:gofix

hostport:檢查傳遞給 net.Dial 的地址格式

此分析器會標記使用 fmt.Sprintf 生成網路地址字串的程式碼,如下例所示

addr := fmt.Sprintf("%s:%d", host, 12345) // "will not work with IPv6"
...
conn, err := net.Dial("tcp", addr)       // "when passed to dial here"

分析器建議使用正確的方法,即呼叫 net.JoinHostPort,來修復這個問題

addr := net.JoinHostPort(host, "12345")
...
conn, err := net.Dial("tcp", addr)

對於“%s:%s”格式字串,也會產生類似的診斷和修復。

預設:開啟。

軟體包文件:hostport

httpresponse:檢查 HTTP 響應使用錯誤

使用 net/http 包的一個常見錯誤是在檢查確定響應是否有效的錯誤之前,就 defer 呼叫關閉 http.Response Body。

resp, err := http.Head(url)
defer resp.Body.Close()
if err != nil {
    log.Fatal(err)
}
// (defer statement belongs here)

此檢查器透過報告此類錯誤的診斷來幫助發現潛在的 nil 解引用錯誤。

預設:開啟。

軟體包文件:httpresponse

ifaceassert:檢測不可能的介面到介面的型別斷言

此檢查器會標記型別斷言 v.(T) 和相應的型別開關,其中 v 的靜態型別 V 是一個介面,它不可能實現目標介面 T。當 V 和 T 包含同名但簽名不同的方法時,會發生這種情況。示例

var v interface {
    Read()
}
_ = v.(io.Reader)

v 中的 Read 方法與 io.Reader 中的 Read 方法簽名不同,因此此斷言不可能成功。

預設:開啟。

軟體包文件:ifaceassert

infertypeargs:檢查呼叫表示式中不必要的型別引數

如果型別引數可以從函式引數或其他型別引數推斷出來,則可以從呼叫表示式中省略顯式型別引數

func f[T any](T) {}

func _() {
    f[string]("foo") // string could be inferred
}

預設:開啟。

軟體包文件:infertypeargs

loopclosure:檢查來自巢狀函式對迴圈變數的引用

此分析器報告函式字面量引用外部迴圈的迭代變數的位置,並且迴圈以某種方式(例如,使用 go 或 defer)呼叫該函式,使其可能比迴圈迭代壽命長,並可能觀察到變數的錯誤值。

注意:迭代變數的生命週期可能比迴圈迭代長,這僅發生在 Go 版本 <=1.21 中。在 Go 1.22 及更高版本中,迴圈變數的生命週期已更改為為每次迴圈迭代建立一個新的迭代變數。(請參閱 go.dev/issue/60078。)

在此示例中,所有推遲的函式在迴圈完成後執行,因此它們都觀察到 v 的最終值 [<go1.22]。

for _, v := range list {
    defer func() {
        use(v) // incorrect
    }()
}

一種修復方法是為迴圈的每次迭代建立一個新變數

for _, v := range list {
    v := v // new var per iteration
    defer func() {
        use(v) // ok
    }()
}

在 Go 1.22 版本之後,前面的兩個 for 迴圈是等效的,並且都是正確的。

下一個示例使用 go 語句並具有類似的問題 [<go1.22]。此外,由於迴圈與 goroutine 併發更新 v,因此存在資料競爭。

for _, v := range elem {
    go func() {
        use(v)  // incorrect, and a data race
    }()
}

修復與之前相同。該檢查器還報告 golang.org/x/sync/errgroup.Group 啟動的 goroutine 中的問題。並行測試中常見的一種難以發現的此形式的變體

func Test(t *testing.T) {
    for _, test := range tests {
        t.Run(test.name, func(t *testing.T) {
            t.Parallel()
            use(test) // incorrect, and a data race
        })
    }
}

t.Parallel() 呼叫導致函式的其餘部分與迴圈併發執行 [<go1.22]。

分析器僅在最後一個語句中報告引用,因為它不夠深入,無法理解後續可能使引用無害的語句的效果。(“最後一個語句”是複合語句(如 if、switch 和 select)中遞迴定義的。)

參見:https://golang.org.tw/doc/go_faq.html#closures_and_goroutines

預設:開啟。

軟體包文件:loopclosure

lostcancel:檢查 context.WithCancel 返回的 cancel func 是否被呼叫

context.WithCancel、WithTimeout、WithDeadline 和 WithCancelCause 等變體返回的取消函式必須被呼叫,否則新的 context 將保持活動狀態,直到其父 context 被取消。(background context 永遠不會被取消。)

預設:開啟。

軟體包文件:lostcancel

maprange:檢查 range 語句中對 maps.Keys 和 maps.Values 的不必要呼叫

考慮一個像這樣寫的迴圈

for val := range maps.Values(m) {
    fmt.Println(val)
}

這應該寫成不呼叫 maps.Values

for _, val := range m {
    fmt.Println(val)
}

golang.org/x/exp/maps 返回切片而不是迭代器來表示 Keys/Values,但應同樣移除不必要的呼叫

for _, key := range maps.Keys(m) {
    fmt.Println(key)
}

應重寫為

for key := range m {
    fmt.Println(key)
}

預設:開啟。

軟體包文件:maprange

modernize:透過使用現代構造簡化程式碼

此分析器報告了透過使用 Go 及其標準庫的更現代特性來簡化和澄清現有程式碼的機會。

每個診斷都提供了一個修復。我們的目的是讓這些修復能夠安全地批次應用,而不會改變程式的行為。在某些情況下,建議的修復可能不完美,並且可能導致(例如)未使用的匯入或未使用的區域性變數,從而導致構建失敗。但是,這些問題通常很容易修復。我們認為任何行為改變程式的現代化程式都有嚴重錯誤,並將努力修復它。

要批次應用所有現代化修復,您可以使用以下命令

$ go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...

(不要使用“go get -tool”將 gopls 新增為模組的依賴項;gopls 命令必須從其釋出分支構建。)

如果工具警告存在衝突的修復,您可能需要多次執行它,直到它乾淨地應用了所有修復。此命令不是官方支援的介面,並且將來可能會更改。

應像往常一樣在合併前審查此工具產生的更改。在某些情況下,迴圈可能會被簡單的函式呼叫替換,導致迴圈內的註釋被丟棄。可能需要人工判斷以避免丟失有價值的註釋。

modernize 報告的每個診斷都有一個特定的類別。(類別列在下面。)某些類別的診斷,例如“efaceany”(它會在安全的情況下將“interface{}”替換為“any”),數量特別多。分兩步應用修復可能會減輕程式碼審查的負擔,第一步僅包含類別為“efaceany”的修復,第二步包含所有其他修復。這可以透過使用 -category 標誌來實現

$ modernize -category=efaceany  -fix -test ./...
$ modernize -category=-efaceany -fix -test ./...

modernize 診斷類別

  • forvar:移除因 go1.22 中迴圈的新語義而變得不必要的 x := x 變數宣告。

  • slicescontains:將“for i, elem := range s { if elem == needle { …; break }}”替換為呼叫 slices.Contains,該函式已在 go1.21 中新增。

  • minmax:將 if/else 條件賦值替換為呼叫內建的 min 或 max 函式,這些函式已在 go1.21 中新增。

  • sortslice:將 sort.Slice(s, func(i, j int) bool { return s[i] < s[j] }) 替換為呼叫 slices.Sort(s),該函式已在 go1.21 中新增。

  • efaceany:將 interface{} 替換為“any”型別,該型別已在 go1.18 中新增。

  • mapsloop:將圍繞 m[k]=v map 更新的迴圈替換為呼叫 maps 包中的 Collect、Copy、Clone 或 Insert 函式之一,該函式已在 go1.21 中新增。

  • fmtappendf:將 []byte(fmt.Sprintf…) 替換為 fmt.Appendf(nil, …),該函式已在 go1.19 中新增。

  • testingcontext:將測試中的 context.WithCancel 用法替換為 t.Context,該函式已在 go1.24 中新增。

  • omitzero:將 omitempty 替換為結構體上的 omitzero,該選項已在 go1.24 中新增。

  • bloop:將基準測試中的“for i := range b.N”或“for range b.N”替換為“for b.Loop()”,並移除任何先前對 b.StopTimer、b.StartTimer 和 b.ResetTimer 的呼叫。

    B.Loop 故意規避了編譯器最佳化,例如內聯,以便基準測試不會完全被最佳化掉。但是,目前,它可能會在某些情況下導致基準測試變慢,因為它會增加分配;請參閱 https://golang.org.tw/issue/73137

  • rangeint:將三子句“for i := 0; i < n; i++”迴圈替換為“for i := range n”,該語法已在 go1.22 中新增。

  • stringsseq:在“for range strings.Split(…)”中將 Split 替換為 go1.24 中更高效的 SplitSeq,或將 Fields 替換為 FieldSeq。

  • stringscutprefix:將某些 HasPrefix 後跟 TrimPrefix 的用法替換為 CutPrefix,該函式已新增到 strings 包中(go1.20)。

  • waitgroup:將 sync.WaitGroup 的舊複雜用法替換為 go1.25 中更簡單的 WaitGroup.Go 方法。

預設:開啟。

軟體包文件:modernize

nilfunc:檢查函式與 nil 之間的無用比較

無用比較類似於 f == nil,而不是 f() == nil。

預設:開啟。

軟體包文件:nilfunc

nilness:檢查冗餘或不可能的 nil 比較

nilness 檢查器檢查包中每個函式的控制流圖,並報告 nil 指標解引用、退化 nil 指標和帶有 nil 值的 panic。退化比較的形式為 x==nil 或 x!=nil,其中 x 被靜態地已知為 nil 或非 nil。這些通常是錯誤的,尤其是在與錯誤相關的控制流中。檢查帶有 nil 值的 panic,因為它們無法透過

if r := recover(); r != nil {

此檢查報告以下條件

if f == nil { // impossible condition (f is a function)
}

p := &v
...
if p != nil { // tautological condition
}

if p == nil {
    print(*p) // nil dereference
}

if p == nil {
    panic(p)
}

有時控制流可能非常複雜,導致錯誤難以發現。在下面的示例中,err.Error 表示式保證會 panic,因為在第一次返回後,err 必須為 nil。中間的迴圈只是一個干擾。

...
err := g.Wait()
if err != nil {
    return err
}
partialSuccess := false
for _, err := range errs {
    if err == nil {
        partialSuccess = true
        break
    }
}
if partialSuccess {
    reportStatus(StatusMessage{
        Code:   code.ERROR,
        Detail: err.Error(), // "nil dereference in dynamic method call"
    })
    return nil
}

預設:開啟。

軟體包文件:nilness

nonewvars:“:=”左側沒有新變數”的建議修復

此檢查器為“:=”左側沒有新變數”型別的型別錯誤提供建議的修復。例如

z := 1
z := 2

將變為

z := 1
z = 2

預設:開啟。

軟體包文件:nonewvars

noresultvalues:意外返回值”的建議修復

此檢查器為“不期望返回值”或“返回值的數量過多”型別的型別錯誤提供建議的修復。例如

func z() { return nil }

將變為

func z() { return }

預設:開啟。

軟體包文件:noresultvalues

printf:檢查 Printf 格式字串和引數的一致性

此檢查適用於格式化函式(如 [fmt.Printf] 和 [fmt.Sprintf])的呼叫,以及任何檢測到的這些函式的包裝器(如 [log.Printf])。它報告各種錯誤,例如格式字串中的語法錯誤以及動詞與其引數之間的不匹配(數量和型別)。

有關格式運算子及其運算元型別的完整列表,請參閱 fmt 包的文件。

預設:開啟。

軟體包文件:printf

recursiveiter:檢查低效的遞迴迭代器

當一個返回迭代器(iter.Seq 或 iter.Seq2)的函式在 range 語句的運算元中呼叫自身時,此分析器會發出報告,因為這效率低下。

在為遞迴資料型別(如樹或連結串列)實現迭代器(例如 iter.Seq[T])時,很容易遞迴地對每個子元素的迭代器進行 range 操作。

這是一個二叉樹上的簡單迭代器的示例

type tree struct {
    value       int
    left, right *tree
}

func (t *tree) All() iter.Seq[int] {
    return func(yield func(int) bool) {
        if t != nil {
            for elem := range t.left.All() { // "inefficient recursive iterator"
                if !yield(elem) {
                    return
                }
            }
            if !yield(t.value) {
                return
            }
            for elem := range t.right.All() { // "inefficient recursive iterator"
                if !yield(elem) {
                    return
                }
            }
        }
    }
}

雖然它正確地枚舉了樹中的元素,但它隱藏了一個重大的效能問題——實際上是兩個。考慮一個具有 N 個節點的平衡樹。迭代根節點將導致 All 被呼叫一次,對樹中的每個節點進行呼叫。當在葉節點上呼叫 yield(t.value) 時,這會導致一系列巢狀的活動 range-over-func 語句。

第一個效能問題是每個 range-over-func 語句通常需要堆分配一個變數,因此迭代樹會分配與樹中元素一樣多的變數,總共 O(N) 次分配,所有這些都是不必要的。

第二個問題是,對葉子節點的每次 yield 呼叫都會使每個封閉的 range 迴圈接收一個值,然後它們會立即將其傳遞給各自的 yield 函式。這會導致每個元素有 log(N) 次動態 yield 呼叫,總共 O(N*log N) 次動態呼叫,而只需要 O(N) 次。

遞迴迭代器更好的實現策略是首先為您的遞迴資料型別定義“every”運算子,其中 every(f) 報告 f(x) 是否對資料型別中的每個元素 x 都為 true。對於我們的樹,every 函式將是

func (t *tree) every(f func(int) bool) bool {
    return t == nil ||
        t.left.every(f) && f(t.value) && t.right.every(f)
}

然後迭代器可以簡單地表示為該函式的平凡包裝器

func (t *tree) All() iter.Seq[int] {
    return func(yield func(int) bool) {
        _ = t.every(yield)
    }
}

實際上,tree.All 計算 yield 對每個元素返回 true,如果 yield 返回 false 則短路,然後丟棄最終的布林結果。

這具有更好的效能特徵:它對樹的每個元素進行一次動態呼叫,並且不進行任何堆分配。它也更清晰。

預設:開啟。

軟體包文件:recursiveiter

shadow:檢查變數是否可能被意外遮蔽

此分析器檢查被遮蔽的變數。被遮蔽的變數是在內部作用域中宣告的變數,其名稱和型別與外部作用域中的變數相同,並且內部變數在聲明後被提及。

(此定義可以進行細化;該模組會產生太多誤報,並且尚未預設啟用。)

例如

func BadRead(f *os.File, buf []byte) error {
    var err error
    for {
        n, err := f.Read(buf) // shadows the function variable 'err'
        if err != nil {
            break // causes return of wrong value
        }
        foo(buf)
    }
    return err
}

預設:關閉。透過設定 "analyses": {"shadow": true} 來啟用。

軟體包文件:shadow

shift:檢查等於或超過整數寬度的大小位移

預設:開啟。

軟體包文件:shift

sigchanyzer:檢查非緩衝 os.Signal 通道

此檢查器報告形式為

signal.Notify(c <-chan os.Signal, sig ...os.Signal),

其中 c 是一個非緩衝通道,可能會錯過訊號。

預設:開啟。

軟體包文件:sigchanyzer

simplifycompositelit:檢查複合字面量簡化

形式為

[]T{T{}, T{}}

將被簡化為

[]T{{}, {}}

這是“gofmt -s”應用的簡化之一。

此分析器會忽略生成程式碼。

預設:開啟。

軟體包文件:simplifycompositelit

simplifyrange:檢查 range 語句簡化

形式為

for x, _ = range v {...}

將被簡化為

for x = range v {...}

形式為

for _ = range v {...}

將被簡化為

for range v {...}

這是“gofmt -s”應用的簡化之一。

此分析器會忽略生成程式碼。

預設:開啟。

軟體包文件:simplifyrange

simplifyslice:檢查切片簡化

形式為

s[a:len(s)]

將被簡化為

s[a:]

這是“gofmt -s”應用的簡化之一。

此分析器會忽略生成程式碼。

預設:開啟。

軟體包文件:simplifyslice

slog:檢查無效的結構化日誌呼叫

slog 檢查器查詢對 log/slog 包中的函式的呼叫,這些函式接受交替的鍵值對。它報告在鍵位置的引數既不是字串也不是 slog.Attr 的呼叫,以及最後一個鍵缺少其值的呼叫。例如,它會報告

slog.Warn("message", 11, "k") // slog.Warn arg "11" should be a string or a slog.Attr

slog.Info("message", "k1", v1, "k2") // call to slog.Info missing a final value

預設:開啟。

軟體包文件:slog

sortslice:檢查 sort.Slice 的引數型別

sort.Slice 需要一個切片型別的引數。檢查傳遞給 sort.Slice 的 interface{} 值實際上是一個切片。

預設:開啟。

軟體包文件:sortslice

stdmethods:檢查知名介面方法的簽名

有時一個型別可能旨在滿足一個介面,但由於其方法簽名中的錯誤而未能滿足。例如,此 WriteTo 方法的結果應為 (int64, error),而不是 error,才能滿足 io.WriterTo

type myWriterTo struct{...}
func (myWriterTo) WriteTo(w io.Writer) error { ... }

此檢查確保每個方法名與標準庫中的幾個知名介面方法之一匹配的方法都具有該介面的正確簽名。

檢查的方法名包括

Format GobEncode GobDecode MarshalJSON MarshalXML
Peek ReadByte ReadFrom ReadRune Scan Seek
UnmarshalJSON UnreadByte UnreadRune WriteByte
WriteTo

預設:開啟。

軟體包文件:stdmethods

stdversion:報告使用過新的標準庫符號

stdversion 分析器報告對標準庫中符號的引用,這些符號是在高於檔案當前 Go 版本的新 Go 版本中引入的。(請記住,檔案的 Go 版本由其 go.mod 檔案中的 'go' 指令或檔案頂部的 "//go:build go1.X" 構建標籤定義。)

分析器不會為引用“過新”型別的“過新”欄位或方法報告診斷,因為這可能導致誤報,例如,當透過類型別名訪問欄位或方法時,該類型別名由 Go 版本約束保護。

預設:開啟。

軟體包文件:stdversion

stringintconv:檢查 string(int) 轉換

此檢查器標記形式為 string(x) 的轉換,其中 x 是整數(但不是 byte 或 rune)型別。此類轉換不被推薦,因為它們會返回 Unicode 碼點 x 的 UTF-8 表示,而不是像預期的那樣返回 x 的十進位制字串表示。此外,如果 x 表示一個無效的碼點,則無法在靜態上拒絕轉換。

對於打算使用碼點的轉換,請考慮將其替換為 string(rune(x))。否則,strconv.Itoa 及其等價物返回以所需基數表示的值的字串表示。

預設:開啟。

軟體包文件:stringintconv

structtag:檢查結構體欄位標籤是否符合 reflect.StructTag.Get

還報告與未匯出欄位一起使用的某些結構體標籤(json、xml)。

預設:開啟。

軟體包文件:structtag

testinggoroutine:報告從測試啟動的 goroutine 中呼叫 (*testing.T).Fatal

會突然終止測試的函式,例如 *testing.T 的 Fatal、Fatalf、FailNow 和 Skip{,f,Now} 方法,必須從測試 goroutine 本身呼叫。此檢查器檢測在測試啟動的 goroutine 中發生的對這些函式的呼叫。例如

func TestFoo(t *testing.T) {
    go func() {
        t.Fatal("oops") // error: (*T).Fatal called from non-test goroutine
    }()
}

預設:開啟。

軟體包文件:testinggoroutine

tests:檢查測試和示例的常見錯誤用法

tests 檢查器會遍歷 Test、Benchmark、Fuzzing 和 Example 函式,檢查名稱錯誤、簽名錯誤以及記錄不存在識別符號的示例。

有關 Test、Benchmark 和 Example 的約定,請參閱 golang.org/pkg/testing 包的文件。

預設:開啟。

軟體包文件:tests

timeformat:檢查 time.Format 或 time.Parse 與 2006-02-01 的呼叫

timeformat 檢查器查詢具有 2006-02-01(yyyy-dd-mm)格式的時間格式。在國際上,“yyyy-dd-mm”不常見於日曆日期標準,因此很可能 intended 的是 2006-01-02(yyyy-mm-dd)。

預設:開啟。

軟體包文件:timeformat

unmarshal:報告將非指標或非介面值傳遞給 unmarshal

unmarshal 分析器報告 json.Unmarshal 等函式的呼叫,其中引數型別不是指標或介面。

預設:開啟。

軟體包文件:unmarshal

unreachable:檢查不可達程式碼

unreachable 分析器查詢由於 return 語句、panic 呼叫、無限迴圈或類似構造而導致執行永遠無法到達的語句。

預設:開啟。

軟體包文件:unreachable

unsafeptr:檢查 uintptr 到 unsafe.Pointer 的無效轉換

unsafeptr 分析器報告將整數轉換為指標時 unsafe.Pointer 的可能不正確的用法。從 uintptr 到 unsafe.Pointer 的轉換無效,如果它暗示記憶體中有一個 uintptr 型別的字包含指標值,因為該字對堆疊複製和垃圾收集器是不可見的。

預設:開啟。

軟體包文件:unsafeptr

unusedfunc:檢查未使用的函式、方法等

unusedfunc 分析器報告在宣告本身之外從未引用的函式和方法。

如果一個函式是未匯出的且未被引用(除了在其自身的宣告內),則認為該函式未使用。

如果一個方法未匯出,未被引用(除了在其自身的宣告內),並且其名稱與同一包中宣告的某個介面型別的方法名稱不匹配,則認為該方法未使用。

在某些情況下,該工具可能會報告誤報,例如

  • 對於從另一個包使用 go:linkname 機制引用的未匯出函式的宣告,如果宣告的文件註釋也沒有 go:linkname 註釋。

    (此類程式碼無論如何都強烈不推薦:linkname 註釋(如果必須使用的話)應同時用於宣告和別名。)

  • 對於“runtime”包中的編譯器內建函式,儘管從未引用,但編譯器已知它們,並且是透過編譯後的目的碼間接呼叫的。

  • 對於僅從彙編呼叫的函式。

  • 對於僅從其構建標籤未在當前構建配置中選擇的檔案中呼叫的函式。

有關這些限制的討論,請參閱 https://github.com/golang/go/issues/71686

unusedfunc 演算法不如 golang.org/x/tools/cmd/deadcode 工具精確,但它的優點在於它在模組化分析框架內執行,可以在 gopls 中實現近乎即時的反饋。

unusedfunc 分析器還報告未使用的型別、變數和常量。列舉(使用 iota 定義的常量)被忽略,因為即使是未使用的值也必須保留才能保持邏輯順序。

預設:開啟。

軟體包文件:unusedfunc

unusedparams:檢查函式中未使用的引數

unusedparams 分析器檢查函式以檢視是否有任何未使用的引數。

為確保健全性,它會忽略

  • “地址已獲取”函式,即作為值使用的函式,而不是直接呼叫;它們的簽名可能需要符合 func 型別。
  • 匯出的函式或方法,因為它們可能在另一個包中被地址獲取。
  • 未匯出的方法,其名稱與同一包中宣告的介面方法匹配,因為方法的簽名可能需要符合介面型別。
  • 函式體為空或僅包含對 panic 的呼叫的函式。
  • 未命名引數,或命名為“_”(空白識別符號)的引數。

分析器建議將引數名替換為“_”的修復方法,但在這種情況下,可以透過呼叫“重構:移除未使用的引數”程式碼操作來獲得更深入的修復,該操作將完全消除引數以及所有相應的呼叫點引數,同時注意保留引數表示式中的任何副作用;請參閱 https://github.com/golang/tools/releases/tag/gopls%2Fv0.14

此分析器會忽略生成程式碼。

預設:開啟。

軟體包文件:unusedparams

unusedresult:檢查某些函式呼叫的結果是否未使用

fmt.Errorf 等函式會返回一個結果且沒有副作用,因此丟棄結果總是一個錯誤。其他函式可能會返回一個不能被忽略的錯誤,或者一個必須被呼叫的清理操作。此分析器在呼叫此類函式且結果被忽略時報告。

函式集可以透過標誌控制。

預設:開啟。

軟體包文件:unusedresult

unusedvariable:檢查未使用的變數並建議修復

預設:開啟。

軟體包文件:unusedvariable

unusedwrite:檢查未使用的寫入

分析器報告從未讀取的對結構體欄位和陣列的寫入例項。具體來說,當複製結構體物件或陣列時,其元素會由編譯器隱式複製,並且對該副本的任何元素寫入都不會對原始物件產生任何影響。

例如

type T struct { x int }

func f(input []T) {
    for i, v := range input {  // v is a copy
        v.x = i  // unused write to field x
    }
}

另一個例子是關於非指標接收者

type T struct { x int }

func (t T) f() {  // t is a copy
    t.x = i  // unused write to field x
}

預設:開啟。

軟體包文件:unusedwrite

waitgroup:檢查 sync.WaitGroup 的誤用

此分析器檢測在新的 goroutine 中誤呼叫 (*sync.WaitGroup).Add 方法,導致 Add 與 Wait 發生競爭

// WRONG
var wg sync.WaitGroup
go func() {
        wg.Add(1) // "WaitGroup.Add called from inside new goroutine"
        defer wg.Done()
        ...
}()
wg.Wait() // (may return prematurely before new goroutine starts)

正確的程式碼是在啟動 goroutine 之前呼叫 Add

// RIGHT
var wg sync.WaitGroup
wg.Add(1)
go func() {
    defer wg.Done()
    ...
}()
wg.Wait()

預設:開啟。

軟體包文件:waitgroup

yield:報告 yield 的呼叫,其中結果被忽略

yield 函式返回 false 後,呼叫者不應再次呼叫 yield 函式;通常迭代器應儘快返回。

此示例未能檢查 yield 呼叫的結果,導致此分析器報告診斷

yield(1) // yield may be called again (on L2) after returning false
yield(2)

修正後的程式碼是這個

if yield(1) { yield(2) }

或者簡單地

_ = yield(1) && yield(2)

忽略 yield 的結果並不總是錯誤。例如,這是一個有效的單元素迭代器

yield(1) // ok to ignore result
return

只有當返回 false 的 yield 呼叫可能後跟另一個呼叫時,這才是錯誤。

預設:開啟。

軟體包文件:yield


本文件的原始碼可以在 golang.org/x/tools/gopls/doc 下找到。