Golang linting完全实践指南

Golang
318
0
0
2023-08-19

Golang编程时间中,为了便于调试和代码质量和安全性检查。利用该方法可以在开发周期的早期捕获错误,并且检查团队编程风格,提高一致性。这对团队协作开发特别有用,可以提高开发的效率,保持代码质量和安全性。

概述

Linting代码是可以做的最基本的事情之一,以确保项目中的编码实践一致。通过捆绑 gofmt ,Go 已经比大多数其他编程语言走得更远,这是一种格式化工具,可确保所有 Go 代码看起来相同,但它只处理代码的格式化方式。在go vet工具可用于检测无法被编译捕获的有问题的代码,但是也只能找到部分问题。

开发更全面的linting工具的是golang社区的一个热门开发领域。社区产生了大量的linter模块,每个都有特定的目的。比如:

Unused:用于检查Golang代码中未使用的常量、变量、函数和类型。

Goconst:查找可以被常量替换的重复字符串。

Gocyclo:计算并检查函数的cyclomatic复杂度。

Errcheck:检测Golang代码中无法检查到的错误。

面对这么多的linting模块,开发者必须自己下载每个单独的linter并管理它们的版本。此外,按顺序运行它们中的每一个可能太慢,因此引入了golang ci-lint,这是一个并行运行linters的 Go linters 聚合器,重用Go构建缓存,并缓存分析结果以大大提高后续运行的性能。

出于方便和性能原因,该项目旨在并行聚合和运行多个单独的linter。当安装该程序时,将获得大约48个linter检查器,用户可以继续挑选特定的检查器以适合自己的实际情况和需求。除了能够在开发调试时候在本地运行之外,还可以将linting加入到CI/CD和DevOps流程中,自动的、持续集成的进行检查测试。

安装

使用go install 安装golangci-lint,支持在有golang开发环境的任何操作系统上本地安装。也可以在官方下载特定操作系统下的二进制包安装。

比如在macOS下可以使用brew安装:

 brew install golangci-lint
brew upgrade golangci-lint 

Linux和Windows下可以使用安装脚本安装:

简单用法

安装后,通过以下命令查看版本:

 golangci-lint version
golangci-lint has version v.40.1 built from (unknown, mod sum: "h1:pBrCqt9BgI9LfGCTKRTSe1DfMjR6BkOPERPaXJYXA6Q=") on (unknown) 

可以通过 help linter命令查看当前启用的linter规则,默认时绝大多数的linter都没有启用(即Disabled)。

 golangci-lint help linters 

在项目目录的根目录下通过golangci-lint run运行,即可进行linters检查,如果有问题,就会打印对应的错误信息,记录该问题所有上下文,包括问题的简短描述,以及出现问题的文件和行号。

 golangci-lint run 

golangci-lint的报告,默认提供高亮显示,对代码行和标记的标识符都用不同颜色表示,可以方便快捷地获得关键信息。

也可以指定文件和目录检查,其语法如下:

 golangci-lint run cc dir2 dir3/chongchong.go 

配置

GolangCI-Lint可以针对不同用例提供灵活多样的配置。可以通过命令行选项或配置文件进行配置。注意命令行参数具有较高优先级,覆盖掉配置文件中的配置项目。基本示例:

 golangci-lint run --disable-all -E revive -E errcheck -E nilerr -E gosec 

可以通过help linters当前情况下预设的规则:

 golangci-lint help linters | sed -n '/Linters presets:/,$p'
Linters presets:
bugs: asciicheck, bodyclose, durationcheck, errcheck, errorlint, exhaustive, exportloopref, gosec, govet, makezero, nilerr, noctx, rowserrcheck, scopelint, sqlclosecheck, staticcheck, typecheck
comment: godot, godox, misspell
complexity: cyclop, funlen, gocognit, gocyclo, nestif
error: errcheck, errorlint, goerr, wrapcheck
format: gci, gofmt, gofumpt, goimports
import: depguard, gci, goimports, gomodguard
metalinter: gocritic, govet, revive, staticcheck
module: depguard, gomoddirectives, gomodguard
performance: bodyclose, maligned, noctx, prealloc
sql: rowserrcheck, sqlclosecheck
style: asciicheck, depguard, dogsled, dupl, exhaustivestruct, forbidigo, forcetypeassert, gochecknoglobals, gochecknoinits, goconst, gocritic, godot, godox, goerr, goheader, golint, gomnd, gomoddirectives, gomodguard, goprintffuncname, gosimple, ifshort, importas, interfacer, lll, makezero, misspell, nakedret, nlreturn, nolintlint, paralleltest, predeclared, promlinter, revive, stylecheck, tagliatelle, testpackage, thelper, tparallel, unconvert, wastedassign, whitespace, wrapcheck, wsl
test: exhaustivestruct, paralleltest, testpackage, tparallel
unused: deadcode, ineffassign, structcheck, unparam, unused, varcheck 

可以通过-p 或者-presetor标志来运行预设

 golangci-lint run -p bugs -p error 

针对项目,可以通过项目配置来进行预设配置特定的linter选项,而这是通过命令行选项无法实现的。配置文件格式可以支持yml、toml或json格式,为了方便建议使用yml格式,即.golangci.yml或.golangci.yaml文件。只需在项目目录的根目录中创建项目特定的配置。程序将自动在要检查的文件的目录中查找它们,并在可以继承查询父目录中的配置。一个典型的.golangci.yml配置如下(注意yml格式的缩进):

 linters-settings:
errcheck:
check-type-assertions: true
goconst:
min-len:
min-occurrences:
gocritic:
enabled-tags:
- diagnostic
- experimental
- opinionated
- performance
- style
govet:
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: 

注释忽略

有时针对个定的linting问题需要临时忽略其显示,可以通过nolint标志或者配置文件中设置忽略项来说实现。假设一个main.go代码:

 package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
    rand.Seed(time.Now().UnixNano())
    fmt.Println(rand.Int())
} 

我们对其执行golangci-lint run,会报以下错误:

 golangci-lint run -E gosec
main.go::14: G404: Use of weak random number generator (math/rand instead of crypto/rand) (gosec)
fmt.Println(rand.Int())
^ 

linter规则中会提示使用更加安全的Intfrom方法产生随机数,但是可能性能上不是很好,综合考虑需要忽略该提示。

 func main() {
    rand.Seed(time.Now().UnixNano())
    fmt.Println(rand.Int()) //nolint
} 

通过由//nolint注释,就可以告诉linting检查器忽略对该行的检查。也可以通过//nolint:xxx(比如gosec)忽略特定的linting检查:

 fmt.Println(rand.Int()) //nolint:gosec 

除了按照行来忽略,也支持通过代码块(比如函数,{})来设置忽略:

 //nolint
func aFunc() {
} 

对整个源文件,可以通过在其开头注释,可以让检查器忽略对该文件的检查:

 //nolint:govet,errcheck
package main 

排除规则

可以在配置文件中指定排除规则,以便更精细地控制哪些文件被检查,以及报告哪些问题。例如,可以设置不检查某些测试文件 (_test.go)上运行,或者禁止在项目范围内产生某些错误:

 .golangci.yml
issues:
exclude-rules:
- path: _test\.go # 测试文件禁止这些linters:
- gocyclo
- gosec
- dupl
# 项目范围忽略掉gosec检查
- linters:
- gosec 

忽略历史问题

添加golangci-lint到现有项目时,可能会遇到很多问题,并且可能很难一次解决所有问题。有一个new-from-rev设置允许仅显示在特定git修订后创建的新问题,这样可以在忽略历史欠债情况下,对新问题都好好解决。可以通过git log找到该commitID,然后在配置文件中指定它,如下所示:

 .golangci.yml
issues:
# 只显示git revision:a6分支后的问题
new-from-rev:a6 

集成

编辑器中集成

GolangCI-Lint支持与多个编辑器集成以获得快速反馈。比如对VSCode,只需安装Go 扩展,并将在settings.json文件中添加配置项:

 {
    "go.lintTool":"golangci-lint",
    "go.lintFlags": [
    "--fast"
    ]
} 

Vim 用户可以通过各种丰富插件来集成,比如vim-go、ALE和Syntastic。

可以在coc.nvim、vim-lsp或 nvim.lspconfig集成。

此处,以coc.nvim集成为例,说明一下基本步骤:

首先,安装语言服务器:

 go install github/nametake/golangci-lint-langserver@latest 

然后用:CocConfig其配置文件,并添加以下几行:

 coc-settings.json
{
    "languageserver": {
        "golangci-lint-languageserver": {
            "command": "golangci-lint-langserver",
            "filetypes": ["go"],
            "initializationOptions": {
                "command": ["golangci-lint", "run", "--out-format", "json"]
            }
        }
    }
} 

设置pre-commit hook

可以在pre-commit钩子中配置golangci-lint,来自动检查,确保所有源代都通过预设的检查。pre-commit按照此页面上的说明安装包管理器,然后.pre-commit-config.yaml在项目的根目录中创建一个文件并使用以下内容填充它:

 .pre-commit-config.yaml
repos:
- repo: github /tekwizely/pre-commit-golang
rev: v.8.3 # change this to the latest version
hooks:
- id: golangci-lint 

该配置文件扩展了pre-commit-golang存储库,它支持 Golang 项目的各种钩子。该golangci-lint钩子仅针对暂存文件,这在引入golangci-lint现有项目时非常方便。保存文件后,运行pre-commit install以在当前存储库中设置 git hook 脚本。

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

在后续提交中,指定的挂钩将对所有暂存 .go 文件上运行,并在发现错误时停止提交过程。在修复所有的linting 问题问题之前,是无法提交成功的。

持续集成 (CI) 工作流

在 CI 工作流中运行,可防止未通过linting检查的代码加入代码库。它本质上保证与项目的主分支合并的任何代码都符合要求的标准。GolangCI-Lint可以很容易地添加到的持续集成工作流程中。比如对于使用GitHub Actions的用户:,

golangci-lint在ci过程中进行设置时,请确保当前正在使用的版本以便它产生与本地环境一致的结果。

结论

使用Linting自动检查项目,是保持团队项目编程风格,基本语法错误检查,基本安全检查的可靠方法。在DevOps流行的今天,善用该工具可以提高编程效率、安全性,保证团队高效协作。