golangci-lint 程式碼檢查

wacho發表於2021-08-11

此文章轉載參考 mp.weixin.qq.com/s/h6cez-1E-WumjI7...

靜態程式碼檢查

靜態程式碼檢查是一個老生常態的問題,它能很大程度上保證程式碼質量。Go 語言自帶套件為我們提供了靜態程式碼分析工具 vet,它能用於檢查 go 專案中可以透過編譯但仍可能存在錯誤的程式碼。靜態程式碼檢查是一個老生常態的問題,它能很大程度上保證程式碼質量。Go 語言自帶套件為我們提供了靜態程式碼分析工具 vet,它能用於檢查 go 專案中可以透過編譯但仍可能存在錯誤的程式碼。

以上談到的工具,我們可以稱之為 linter。在維基百科是如下定義 lint 的:

在電腦科學中,lint 是一種工具程式的名稱,它用來標記原始碼中,某些可疑的、不具結構性(可能造成bug)的段落。它是一種靜態程式分析工具,最早適用於C語言,在UNIX平臺上開發出來。後來它成為通用術語,可用於描述在任何一種計算機程式語言中,用來標記原始碼中有疑義段落的工具。

而在 Go 語言領域, golangci-lint 是一個集大成者的 linter 框架。它整合了非常多的 linter,包括了上文提到的幾個,合理使用它可以幫助我們更全面地分析與檢查 Go 程式碼。golangci-lint 所支援的 linter 項可以檢視頁面 golangci-lint.run/usage/linters/#g...

使用 golangci-lint

下載

go get github.com/golangci/golangci-lint/cmd/golangci-lint@latest

檢查安裝成功

$ golangci-lint version
golangci-lint has version v1.41.1

檢視幫助文件

golangci-lint help linters

  • 預設生效的 linters

image-20210806202635527

  • 預設不生效的 linters

image-20210806202808804

  • linters 分類

image-20210806202922712

可以看出,golangci-lint 框架支援的 linter 非常全面,它包括了 bugs、error、format、unused、module 等常見類別的分析 linter。

例項

下面用例項展示一下,目錄結構如下:

.
├── exam
│ └── exam.go
├── go.mod
└── main.go

其中,main.go 程式碼如下:

package main

import "fmt"

func main() {
    s1 := "this is a string"
    fmt.Printf("inappropriate formate %s\n", &s1)

    i := 1
    fmt.Println(i != 0 || i != 1)

    arr := []int{1, 2, 3}
    for _, i := range arr {
        go func() {
            fmt.Println(i)
        }()
    }
}

exam.go程式碼如下:

package exam

import "fmt"

func check() {
    t := unexistType{}
    fmt.Println(t)
}

func unused() {
    i := 1
}

這二個檔案的原始碼都存在一些問題,此時,我們透過 golangci-lint 工具來對原始碼檔案進行檢查

image-20210806203448071

可以看到,我們在程式根目錄中執行 golangci-lint run 命令,它等效於 golangci-lint run ./... 。此時,它將 main.gotypecheckDemo.go 中存在的潛在問題都檢測到了,並標記了是何種 linter 檢測(這裡是 typecheck 和 govet 兩種)到的。

當然,也可以透過命令 golangci-lint run dir1 dir2/... dir3/file1.go 對某特定的檔案或資料夾進行分析。

靈活運用命令選項
  • golangci-lint 可以透過 -E/--enable 去開啟指定 linter,或者 -D/--disable 禁止指定 linter。
1golangci-lint run --disable-all -E errcheck

如上命令代表的就是除了 errcheck 的 linter,禁止其他所有的 linter 生效。

  • golangci-lint 還可以透過 -p/--preset 指定一系列 linter 開啟。
1golangci-lint run -p bugs -p error

如上命令代表的就是所有屬於 bugserror 分類的 linter 生效。

  • 更多命令選項,可以透過 golangci-lint run -h 檢視
配置檔案

當然,如果我們要為專案配置 golangci-lint,最好的方式還是配置檔案。golangci-lint 在當前工作目錄按如下順序搜尋配置檔案。

  • .golangci.yml
  • .golangci.yaml
  • .golangci.toml
  • .golangci.json

在 golangci-lint 官方文件 golangci-lint.run/usage/configurat... 中,提供了一個示例配置檔案,非常地詳細,在這其中包含了所有支援的選項、描述和預設值。

這裡給出一個簡單的配置文件例項:

linters-settings:
    errcheck:
        check-type-assertions: true

goconst:
    5    min-len: 2
    6    min-occurrences: 3

    gocritic:
        enabled-tags:
          - diagnostic
          - experimental
          - opinionated
          - performance
          - style
    gove:
        check-shadowing: true
    nolintlint:
        require-explanation: true
        require-specific: true

linters:

  disable-all: true
  enable:
      - bodyclose
      - deadcode
      - depguard
      - dogsled
      - dupl
      - errcheck
      - exportloopref
      - exhaustive
      - goconst
      - gocritic
      - gofmt
      - goimports
      - gomnd
      - gocyclo
      - gosec
      - gosimple
      - govet
      - ineffassign
      - misspell
      - nolintlint
      - nakedret
      - prealloc
      - predeclared
      - revive
      - staticcheck
      - structcheck
      - stylecheck
      - thelper
      - tparallel
      - typecheck
      - unconvert
      - unparam
      - varcheck
      - whitespace
      - wsl


run:
  issues-exit-code: 1

使用 pre-commit hook

在專案開發中,我們都會使用到 git,因此我們可以將程式碼靜態檢查放在一個 git 觸發點上,而不用每次寫完程式碼手動去執行 golangci-lint run 命令。這裡,我們就需要用到 git hooks。

git hooks

git hooks 是 git 的一種鉤子機制,可以讓使用者在 git 操作的各個階段執行自定義的邏輯。git hooks 在專案根目錄的 .git/hooks 下面配置,配置檔案的名稱是固定的,實質上就是一個個 shell 指令碼。根據 git 執行體,鉤子被分為客戶端鉤子和服務端鉤子兩類。

客戶端鉤子包括:pre-commitprepare-commit-msgcommit-msgpost-commit等,主要用於控制客戶端git的提交工作流。服務端鉤子:pre-receivepost-receiveupdate,主要在服務端接收提交物件時、推送到伺服器之前呼叫。

注意,以 .sample 結尾的檔名是官方示例,這些示例指令碼是不會執行的,只有重新命名後才會生效(去除 .sample 字尾)。

而 pre-commit 正如其名一樣,它在 git add 提交之後,執行 git commit 時執行,指令碼執行沒報錯就繼續提交,反之就駁回提交的操作。

pre-commit

試想,如果我們同時開發多個專案,也許專案的所採用的的程式語言並不一樣,那麼它們所需要的 git hooks 將不一致,此時我們是否要手動給每個專案都配置一個單獨的 pre-commit 指令碼呢,或者我們是否要去手動下載每一個鉤子指令碼呢。

實際上,並不需要這麼麻煩。這裡就引出了 pre-commit 框架,它是一個與語言無關的用於管理 git hooks 鉤子指令碼的工具

  • 安裝
$ pip install pre-commit
或者
$ curl https://pre-commit.com/install-local.py | python -
或者
$ brew install pre-commit
  • 安裝成功
$ pre-commit --version
pre-commit 1.20.0
  • 編寫配置檔案

首先我們在專案根目錄下新建一個 .pre-commit-config.yaml 檔案,這個檔案我們可以透過 pre-commit sample-config 得到最基本的配置模板,透過 pre-commit 支援的 hooks 列表 https://pre-commit.com/hooks.html中,我們找到了 golangci-lint。

image-20210809213007293

因此,使用 golangci-lint 的 .pre-commit-config.yaml 配置內容如下

repos:
  - repo: https://github.com/golangci/golangci-lint
    rev: v1.41.1 # the current latest version
    hooks:
      - id: golangci-lint
  • 安裝 git hook 指令碼

    $ pre-commit install
    pre-commit installed at .git/hooks/pre-commit

此時,生成了新的 Python 語言編寫的 .git/hooks/pre-commit 鉤子檔案。

  • git commit 觸發 golangci-lint 檢查**
➜  go-web git:(master)git add .  
➜  go-web git:(master)git commit -m "golangci-lint test"
golangci-lint............................................................Failed
- hook id: golangci-lint
- exit code: 1

router.go:8:6: `CollectRoute` is unused (deadcode)
func CollectRoute(r *gin.Engine) *gin.Engine {
     ^
model/time.go:32:15: func `Time.local` is unused (unused)
func (t Time) local() time.Time {
              ^
main.go:17:14: bools: suspect or: i != 0 || i != 1 (govet)
        fmt.Println(i != 0 || i != 1)
                    ^
main.go:22:16: loopclosure: loop variable i captured by func literal (govet)
                        fmt.Println(i)
                                    ^
main.go:14:2: printf: fmt.Printf format %s has arg &s1 of wrong type *string (govet)
        fmt.Printf("inappropriate formate %s\n", &s1)
        ^
main.go:43:4: printf: fmt.Println call has possible formatting directive %v (govet)
                        fmt.Println("startup service failed, err:%v\n", err)

總結

程式碼質量是每位開發者都必須重視的問題,golangci-lint 提供的一系列 linter 外掛能夠在很大程度上幫助 Gopher 及時發現與解決潛在 bug。同時,藉助 golangci-lint 還可以有效規範專案組內的程式碼風格,減輕 code review 的心智負擔,希望 能有效利用它。

git-commit 工具透過配置檔案生成 git hooks 所需要的 pre-commit 鉤子指令碼,這可以將透過 golangci-lint 的靜態程式碼檢查工作,由手工動作轉化為自動化流程。上文關於 git-commit 的介紹比較簡單,想更詳細探究可以直接去官網 pre-commit.com/index.html 學習。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章