golang 代码检测工具 golangci-lint 使用教程
背景
为什么需要 lint 工具?大部分公司都有 Code Review 制度,但是纯靠人工、靠主观意识来检测代码的质量是否合规是非常耗时、并且十分脆弱的,如果在 Code Review 之前,能够先用 lint 工具检测一遍,让开发能够先自检,减少问题代码,也能提高 Code Review 的时间和耗时
go 的 lint 工具有非常多:
- go fmt
- go imports
- go vet
- …..
但是我们总不能一个个去安装,那简直太麻烦了,所以开源社区提供了一款 linters 聚合工具:golangci-lint,集合了绝大多数的 lint 工具,可通过配置化来实现团队自定义 lint 代码质量规则,golangci-lint 是目前公认的最好的 linter 整合工具
一、安装 golangci-lint
(一)方式一:go install
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2
(二)方式二:下载安装
github 找到自己的版本,下载安装即可
Releases · golangci/golangci-lint · GitHub
(三)验证
运行:
golangci-lint --version
输出版本即安装成功: golangci-lint has version v1.55.2
二、配置使用
在项目根目录创建.golangci.yml
文件,配置说明如下
完整版本在 https://golangci-lint.run/usage/configuration/
官方参考例子 https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml
分为以下六大部分,按需配置,并不是都需要
- run 运行配置,比如超时,忽略文件等
- output 输出配置,比如格式
- linters-settings 检测器配置,对具体的检测器细化配置
- linters 开启关闭检测器
- issues 检测器输出报告配置,比如忽略某些检测器的输出
- severity 检测器敏感度配置
# 完整版本在 https://golangci-lint.run/usage/configuration/
# 官方参考例子 https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml
# 1. 运行配置,比如超时、忽略文件等
run:
# 运行 golangci-lint 时要使用的 CPU 数量。默认值:计算机中的逻辑 CPU 数量
# concurrency: 1
# 分析超时,例如 30 秒、5 分钟。默认值:1m
# Default: 1m
timeout: 5m
# 发现至少 n 个问题时的退出代码。默认值:1
issues-exit-code: 1
# 是否包含测试文件。默认值:true
tests: false
# 构建标记列表,所有 linter 都使用它。默认值 []。TODO:不知道有什么用
# build-tags:
# - mytag
# 要跳过的目录:不会报告它们的问题。可以在这里使用正则表达式: 'generated.*',正则表达式应用于完整路径,如果设置了路径前缀,则包括路径前缀。
# 默认目录将独立于此选项的值进行跳过(请参阅 skip-dirs-use-default)。
# "/" 将被替换为当前操作系统文件路径分隔符,以便在 Windows 上正常工作。
# 默认值: []
skip-dirs:
- gva
- jxykit
- logs
- web
# 允许跳过目录:
# - vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
# Default: true
# skip-dirs-use-default: false
# 要跳过的文件:将分析它们,但不会报告它们的问题。
# 没有必要包含所有自动生成的文件,我们自信地识别自动生成的文件,如果不是,请告诉我们。
# "/" 将被替换为当前操作系统文件路径分隔符,以便在 Windows 上正常工作。
# Default: []
# skip-files:
# - ".*\\.my\\.go$"
# - lib/bad.go
# 如果设置,我们将其传递给 “go list -mod={option}”。从“go help modules”:
# 如果使用 -mod=readonly 调用,则不允许在上述 go.mod 的隐式自动更新中使用 go 命令。相反,当需要对 go.mod 进行任何更改时,它会失败。此设置对于检查 go.mod 是否不需要更新(例如在持续集成和测试系统中)最有用
# 如果使用 -mod=vendor 调用,则 go 命令假定 vendor 目录包含依赖项的正确副本,并忽略 go.mod 中的依赖项描述。
# 允许的值: readonly|vendor|mod
# Default: ""
# modules-download-mode: readonly
# 允许运行多个并行 golangci-lint 实例。
# If false, golangci-lint 在启动时获取文件锁定。
# Default: false
# allow-parallel-runners: false
# 允许运行多个 golangci-lint 实例,但围绕一个锁序列化它们。
# If false, 如果 golangci-lint 在启动时无法获取文件锁定,则会退出并显示错误。
# Default: false
# allow-serial-runners: true
# 打印 golangci-lint 的平均和最大内存使用量和总时间。
# Default: false
print-resources-usage: true
# 定义 Go 版本限制。
# 主要与自 go1.18 以来的泛型支持有关。
# Default: use Go version from the go.mod file, fallback on the env var `GOVERSION`, fallback on 1.17
# go: '1.19'
# 2. 输出配置,比如格式
output:
# Format: colored-line-number|line-number|json|colored-tab|tab|checkstyle|code-climate|junit-xml|github-actions|teamcity
# 可以通过用逗号分隔它们来指定多个,可以通过用冒号分隔格式名称和路径来为每个它们提供输出。
# 输出路径可以是“stdout”、“stderr”或要写入的文件的路径。
# Example: "checkstyle:report.xml, json:stdout,colored-line-number"
# Default: colored-line-number
format: colored-tab
# 打印有问题的代码行
# Default: true
# print-issued-lines: false
# 在问题文本末尾打印 linter 名称。
# Default: true
# print-linter-name: false
# Make issues output unique by line.
# Default: true
# uniq-by-line: false
# 将前缀添加到输出文件引用。
# Default: ""
# path-prefix: ""
# 对结果进行排序: filepath, line and column.
# Default: false
sort-results: true
# 3. 开启关闭检测器配置
linters:
# 指定代码不检查 _ = c.Error(err.(error)) // nolint: errcheck
# 是否禁用所有 linter。
# Default: false
# disable-all: true
# Enable specific linter
# 默认开启的 linters https://golangci-lint.run/usage/linters/#enabled-by-default
disable-all: true
enable:
- funlen # 用于检测长函数的工具。
- goconst # 查找可由常量替换的重复字符串。
- gocyclo # 计算和检查函数的圈复杂度
- gofmt # 代码格式化。
- ineffassign # 检查一些无用赋值
- staticcheck # 静态语法检查
- typecheck # 类型检查
- goimports # 对 imports 进行格式化排序
- revive # 代码修复;快速、可配置、可扩展、灵活且美观的 Go linter。golint 的直接替代品。
# - gosimple # Linter for Go 源代码,专门用于简化代码。
- govet # Vet语法检查 检查 Go 源代码并报告可疑构造,例如参数与格式字符串不一致的 Printf 调用。
- lll # Reports long lines.行长度限制规则
- rowserrcheck # 检查是否成功检查了行的 Rows.Err。
- errcheck # 错误检查, Errcheck 是一个用于检查 Go 代码中未检查错误的程序。在某些情况下,这些未经检查的错误可能是严重错误。
# - unused # 检查 Go 代码中是否存在未使用的常量、变量、函数和类型。
- sqlclosecheck # 检查那些 sql.Rows, sql.Stmt, sqlx.NamedStmt, pgx.Query 是否关闭
- gocritic # 提供诊断功能,用于检查错误、性能和样式问题。无需通过动态规则重新编译即可扩展。动态规则是以声明方式编写的,包括 AST 模式、筛选器、报告消息和可选建议。
# - bodyclose 检查HTTP响应正文是否关闭成功。. https://github.com/timakin/bodyclose/issues 问题太多了,屏蔽都屏蔽不过来,显式不使用它
#
# # 启用所有可用的 linter。
# # Default: false
# # enable-all: true
# # 禁用特定 linter
# # 默认禁用特定 linters: https://golangci-lint.run/usage/linters/#disabled-by-default
# disable:
# - asasalint
# # 启用预设。
# # https://golangci-lint.run/usage/linters
# # Default: []
# presets:
# - bugs
#
# # Run only fast linters from enabled linters set (first run won't be fast)
# # Default: false
# fast: true
# 4. 检测器 linters 的所有详细配置.
linters-settings:
# 用于检测长函数的工具。
funlen:
# 检查函数中的行数,单个函数的最大行数限制。如果低于 0,则禁用检查。
# Default: 60
lines: 250
# 函数中的语句数。单个函数的最大语句数限制.如果低于 0,则禁用检查。
# Default: 40
statements: 200
# 计算行数时忽略注释。
# Default false
# ignore-comments: true
# 查找可由常量替换的重复字符串。
goconst:
# 字符串常量的最小长度。
# Default: 3
min-len: 2
# 最小重复字符出现次数 触发问题的常量字符串计数的最小出现次数。
# Default: 3
min-occurrences: 5
# 忽略测试文件。
# Default: false
# ignore-tests: true
# 查找与值匹配的现有常量。
# Default: true
# match-constant: false
# 还要搜索重复的数字。
# Default: false
# numbers: true
# 最小值,仅适用于 goconst.numbers
# Default: 3
# min: 2
# 最大值,仅适用于 goconst.numbers
# Default: 3
# max: 2
# 当常量未用作函数参数时忽略。
# Default: true
# ignore-calls: false
# 排除与给定正则表达式匹配的字符串。
# Default: ""
# ignore-strings: 'foo.+'
# 计算和检查函数的圈复杂度
gocyclo:
# 报告的代码圈复杂度最低值
# 默认值:30(但我们建议 10-20)
min-complexity: 30
# min-complexity: 20000
# 对 imports 进行格式化排序
goimports:
# A comma-separated list of prefixes, which, if set, checks import paths with the given prefixes are grouped after 3rd-party packages.
# Default: ""
# local-prefixes: github.com/org/project
# 代码修复;快速、可配置、可扩展、灵活且美观的 Go linter。golint 的直接替代品。
revive:
# 同时打开的最大文件数。See https://github.com/mgechev/revive#command-line-flags
# Defaults to unlimited.
# max-open-files: 2048
# 设置为 false 时,忽略带有“GENERATED”标头的文件,类似于 golint。See https://github.com/mgechev/revive#available-rules for details.
# Default: false
# ignore-generated-header: true
# 设置默认严重性。 See https://github.com/mgechev/revive#configuration
# Default: warning
# severity: error
# 启用所有可用规则
# Default: false
# enable-all-rules: true
# 设置默认故障置信度。这意味着置信度小于 0.8 的 linting 错误将被忽略。
# Default: 0.8
confidence: 0.2
rules: # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#add-constant
- name: var-declaration
# - name: package-comments # 包必须有注释 TODO
- name: dot-imports
- name: blank-imports
# - name: exported # 导出的 方法 必须有注释 TODO
# - name: var-naming 命名规范
- name: indent-error-flow
- name: range
- name: errorf
- name: error-naming
- name: error-strings
- name: receiver-naming
- name: increment-decrement
- name: error-return
#- name: unexported-return
- name: time-naming
- name: context-keys-type
- name: context-as-argument
# Vet语法检查 检查 Go 源代码并报告可疑构造,例如参数与格式字符串不一致的 Printf 调用。
govet:
# 检查 shadowed variables.
# Default: false
check-shadowing: true
## 每个分析器的设置
#settings:
# # Analyzer name, run `go tool vet help` to see all analyzers.
# printf:
# # Comma-separated list of print function names to check (in addition to default, see `go tool vet help printf`).
# # Default: []
# funcs:
# - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof
# - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
# - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
# - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
# shadow:
# # Whether to be strict about shadowing; can be noisy.
# # Default: false
# strict: true
# unusedresult:
# # Comma-separated list of functions whose results must be used
# # (in addition to default:
# # context.WithCancel, context.WithDeadline, context.WithTimeout, context.WithValue, errors.New, fmt.Errorf,
# # fmt.Sprint, fmt.Sprintf, sort.Reverse
# # ).
# # Default: []
# funcs:
# - pkg.MyFunc
# # Comma-separated list of names of methods of type func() string whose results must be used
# # (in addition to default Error,String)
# # Default: []
# stringmethods:
# - MyMethod
#
## 禁用所有分析器
## Default: false
#disable-all: true
## Enable analyzers by name.
## (in addition to default:
## appends, asmdecl, assign, atomic, bools, buildtag, cgocall, composites, copylocks, defers, directive, errorsas,
## framepointer, httpresponse, ifaceassert, loopclosure, lostcancel, nilfunc, printf, shift, sigchanyzer, slog,
## stdmethods, stringintconv, structtag, testinggoroutine, tests, timeformat, unmarshal, unreachable, unsafeptr,
## unusedresult
## ).
## Run `go tool vet help` to see all analyzers.
## Default: []
#enable:
# - appends
#
## 启用所有分析器
## Default: false
## enable-all: true
#
## Disable analyzers by name.
## (in addition to default
## atomicalign, deepequalerrors, fieldalignment, findcall, nilness, reflectvaluecompare, shadow, sortslice,
## timeformat, unusedwrite
## ).
## Run `go tool vet help` to see all analyzers.
## Default: []
#disable:
# - appends
# Reports long lines.行长度限制规则
lll:
# 最大行长,将被报告
# 默认情况下,“\t”计为 1 个字符,可以使用制表符宽度选项 tab-width 进行更改。
# Default: 120.
# line-length: 1000 TOTO
line-length: 100000
# 空格中的制表符宽度
# Default: 1
# tab-width: 1
# 错误检查, Errcheck 是一个用于检查 Go 代码中未检查错误的程序。在某些情况下,这些未经检查的错误可能是严重错误。
errcheck:
# 关于不检查类型断言中的错误的报告:“a := b.(MyStruct)”。
# 默认情况下,不会报告此类情况。
# Default: false
check-type-assertions: false
# 报告将错误分配给空白标识符:'num, _ := strconv.Atoi(numStr)'。
# 默认情况下,不会报告此类情况。
# Default: false
check-blank: true
# 已弃用逗号分隔的 pkg:regex 形式的对列表
# 正则表达式用于忽略 PKG. 中的名称(默认为“fmt:.”)。
# see https://github.com/kisielk/errcheck#the-deprecated-method for details
# ignore: fmt:.*,io/ioutil:^Read.*
# 禁用 errcheck 内置排除列表。
# See `-excludeonly` option in https://github.com/kisielk/errcheck#excluding-functions for details.
# Default: false
# disable-default-exclusions: true
# DEPRECATED use exclude-functions instead.
# Path to a file containing a list of functions to exclude from checking.
# See https://github.com/kisielk/errcheck#excluding-functions for details.
# exclude: /path/to/file.txt
# 要从检查中排除的函数列表,其中每个条目都是要排除的单个函数。
# See https://github.com/kisielk/errcheck#excluding-functions for details.
# exclude-functions:
# - io/ioutil.ReadFile
# - io.Copy(*bytes.Buffer)
# - io.Copy(os.Stdout)
# 提供诊断功能,用于检查错误、性能和样式问题。无需通过动态规则重新编译即可扩展。动态规则是以声明方式编写的,包括 AST 模式、筛选器、报告消息和可选建议。
gocritic:
enabled-checks:
- nestingReduce
- commentFormatting
settings:
nestingReduce:
bodyWidth: 5
# 5. 检测器输出报告配置,比如忽略某些检测器的输出
issues:
exclude-use-default: true
# The list of ids of default excludes to include or disable. By default it's empty.
# 下面的规则,golangci-lint认为应该屏蔽,但是我们选择不屏蔽。所以,`exclude-use-default: true`屏蔽一部分,把下面的再捞出来。
# golanglint-ci维护的忽略列表里有一些是我们不想屏蔽的,捞出来。这里说一下,使用白名单是好于黑名单的。名单随着golanglint-ci引入更多工具,我们跟进享受好处。我们搞黑名单,就变成自己维护,不如golanglint-ci去维护,更好。
include:
- EXC0004 # govet (possible misuse of unsafe.Pointer|should have signature)
- EXC0005 # staticcheck ineffective break statement. Did you mean to break out of the outer loop
- EXC0012 # revive exported (method|function|type|const) (.+) should have comment or be unexported
- EXC0013 # revive package comment should be of the form "(.+)...
- EXC0014 # revive comment on exported (.+) should be of the form "(.+)..."
- EXC0015 # revive should have a package comment, unless it's in another file for this package
exclude-rules:
- path: _test\.go
linters:
- funlen # 规范说单测函数,单个函数可以到160行,但是工具不好做区分处理,这里就直接不检查单测的函数长度
- linters:
- staticcheck
text: "SA6002: argument should be pointer-like to avoid allocations" # sync.pool.Put(buf), slice `var buf []byte` will tiger this
- linters:
- lll
source: "^//go:generate " # Exclude lll issues for long lines with go:generate
max-same-issues: 0
new: false
max-issues-per-linter: 0
# 6. 检测器的敏感度配置
severity:
# 设置问题的默认严重性。如果定义了严重性规则,但问题不匹配,或者没有为规则提供严重性,这将是应用的默认严重性。
# 严重性应与所选输出格式支持的严重性名称匹配。
# - Code climate: https://docs.codeclimate.com/docs/issues#issue-severity
# - Checkstyle: https://checkstyle.sourceforge.io/property_types.html#SeverityLevel
# - GitHub: https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
# - TeamCity: https://www.jetbrains.com/help/teamcity/service-messages.html#Inspection+Instance
#
# Default: ""
default-severity: error
# 如果设置为 true,则“severity-rules”正则表达式将区分大小写。
# Default: false
case-sensitive: true
#service:
# golangci-lint-version: 1.28.x
然后在项目根目录.golangci.yml
文件所在的目录,执行:
golangci-lint run
即可进行检测,有代码问题会输出结果
以上是简单的手动执行 golangci-lint,但是在实际开发中,我们一般会集成到一些开发工具中,让 lint 更加高效有用:
-
集成到 git:每次 git 提交都需要进行 lint,确保代码质量符合规范
-
集成到 CI/CD:通过流水线执行 lint,确保代码合规才能 merge 到 master 生产环境
- gitlab CI/CD
- Github Action
- Jenkins
- ….
三、接入 git
通过 git hook: pre-commit 钩子实现代码提交自动运行 golangci-lint
在 .git/hooks/pre-commit
创建文件(或修改现有文件)
#!/bin/bash
set -x # 开启命令追踪
if ! command -v golangci-lint &> /dev/null; then
echo "golangci-lint not install"
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2
fi
cd $(dirname $0)/..
golangci-lint run
if [ $? -ne 0 ]; then
echo "Linting failed - please fix the issues before committing"
exit 1
fi
这样子每次提交 commit 就会自动运行 lint,如果出现错误,提交将会失败,
可搜索 git hook 教程学习,然后进行实际配置
四、接入 Github Action
集成到 Github Action 中,提交代码,执行流水线自动执行 golangci-lint
在 .github/workflows/lint.yml
中:
name: Lint
on: [push, pull_request]
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/set
注意:这只是个案例模版,实际配置请学习官方 Github Action yaml 配置说明
五、接入 gitlab CI/CD
集成到 gitlab CI/CD
在项目根目录创建 .gitlab-ci.yml
文件:
stages:
- lint # 代码静态检查
- test # 单元测试
- build # 构建二进制文件
- deploy # 部署阶段
variables:
GO_VERSION: "1.21"
BINARY_NAME: "myapp"
ARTIFACTS_DIR: "artifacts"
# 所有作业都会继承的基础配置
.default_job: &default_job
image: golang:${GO_VERSION}
before_script:
- mkdir -p ${ARTIFACTS_DIR}
- go version
golangci-lint:
<<: *default_job
stage: lint
script:
# 安装 golangci-lint
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.55.2
# 运行检查
- $(go env GOPATH)/bin/golangci-lint run ./...
artifacts:
when: on_failure
paths:
- ${ARTIFACTS_DIR}/lint-report.txt
.....
注意:这只是个案例模版,实际配置请学习官方 GitLab yaml 配置说明
六、接入 Idea Goland
和 go fmt 一样,在开发工具中配置 golangci-lint 工具,每次代码保存自动进行 lint 检查
不是很推荐,会影响写代码的速度……….
配置方式和 go imports 的配置方式几乎一致,参考文章:Goland 开发工具一些关键配置
总结
接入 DevOps 、开发工具的逻辑非常简单,无非就是在执行流水线前多执行一个步骤:下载安装 golangci-lint,执行 golangci-lint run
几乎大同小异