靜態程式碼檢查
靜態程式碼檢查是一個老生常態的問題,它能很大程度上保證程式碼質量。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
- 預設不生效的 linters
- linters 分類
可以看出,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 工具來對原始碼檔案進行檢查
可以看到,我們在程式根目錄中執行 golangci-lint run
命令,它等效於 golangci-lint run ./...
。此時,它將 main.go
和 typecheckDemo.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
如上命令代表的就是所有屬於 bugs
和 error
分類的 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-commit
、prepare-commit-msg
、commit-msg
、post-commit
等,主要用於控制客戶端git的提交工作流。服務端鉤子:pre-receive
、post-receive
、update
,主要在服務端接收提交物件時、推送到伺服器之前呼叫。
注意,以 .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。
因此,使用 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 協議》,轉載必須註明作者和本文連結