golangci-lint在cicd中的应用

https://my.oschina.net/u/2950272/blog/4992726

1.前言

为了保证线上质量,需要每次上线对代码进行静态分析,而依赖开发者自觉是很难保证的。本文主要详细介绍一系列上线必须要通过的静态扫描规范。

2.必读官方规范

golang 代码风格指南是官方开发指南,被golint 约束,主要包含内容如下:

3.必须通过准入的拦截工具

单纯的 fmt 和lint 级别的扫描,对控制开发质量是远远不够的,还需要其他静态工具配合检查。

golangci-lint集合了各种检查工具

  • deadcode, 检查根本没用的代码片段,不包括没使用的函数
  • depguard, 通过自定义依赖包白、黑名单,确保import的包都是可控的
  • errcheck, 用于检查未检查的error返回值, 能防止一些严重的bug case
  • goconst, 查找代码中出现的重复常量字符串,给出可以定义为常量的优化建议
  • gocritic, The most opinionated Go source code linter
  • gofmt,go 格式化工具
  • goimports, 检查没使用的imports
  • golint,不同于fmt,主要检查代码风格问题
  • gomnd, 检测幻数的工具,什么是幻数?幻数是一种数字文字,没有定义为常量,但可能会更改,因此可能难以更新。 直接在任何源代码中使用数字而不进行解释被认为是不好的编程习惯。 它使程序更难以阅读,理解和维护。
  • goprintffuncname,PrintF 类的函数,应该已f结尾
  • gosec, 检查源码安全问题,包括https://github.com/securego/gosec#available-rules
  • gosimple, 简化代码工具
  • govet, 检查Go 代码正确性的工具
  • ineffassign, 检查无效赋值
  • interfacer, interface 建议
  • lll, 每行代码长度检查,不超过150字符
  • misspell, 拼写检查,防止线上出现拼写出错导致的bug
  • noctx,http request 的context 检查
  • nolintlint, 用于查找格式错误或解释不充分的内容。nolint 必须按格式加以解释://nolint:gosec // this is not used in a secure application
  • rowserrcheck,检查sql.Rows.Err是否被成功检查到
  • scopelint,检查没固定的变量可能引发的问题
  • staticcheck, 静态检查工具
  • structcheck, 检查没有使用的结构体filed
  • stylecheck, 风格检查工具
  • typecheck, 类型检查工具
  • unconvert, 去掉不必要的类型转换
  • unparam, 报告没使用的函数入参
  • unused, 检查没使用的常量,变量,函数和类型
  • varcheck, 检查没使用的全量变量和常量
  • whitespace, 检查函数前后没必要的空行

详细了解,请参考:https://golangci-lint.run/usage/linters/

4.工具本地安装

4.1 golangci-lint 安装 详见官网

4.2 vscode 配置

在setting.json 中设置lintTool, vscode 会再当前目录下搜索.golangci.yml 或 .golangci.json

1
2
3
4
"go.lintTool":"golangci-lint",
"go.lintFlags": [
"--fast"
]

4.3 对goland 配置

打开 File Watcher 使用golangci-lint 的模板

可以添加--new-from-rev=master 使用增量扫描

可以添加--tests=false 跳过test

配置 --fast 加速

4.4 命令行扫描

1
golangci-lint run --tests=false --timeout=5m --new-from-rev=master -c lint.yml ./... 相对master分支增量扫描

5.推荐yml配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
linters-settings:
depguard:
list-type: blacklist
packages:
# logging is allowed only by logutils.Log, logrus
# is allowed to use only in logutils package
- github.com/sirupsen/logrus
dupl:
threshold: 100
funlen:
lines: 100
statements: 50
goconst:
min-len: 2
min-occurrences: 3
gocritic:
enabled-tags:
- diagnostic
- performance
- style
enable-checks:
- builtinShadow # in opinionated group
- importShadow # in opinionated group
- paramTypeCombine # in opinionated group
gocyclo:
min-complexity: 20
golint:
min-confidence: 0
gomnd:
settings:
mnd:
# don't include the "operation" and "assign"
checks: argument,case,condition,return
lll:
line-length: 150
misspell:
locale: US
govet:
check-shadowing: true
maligned:
suggest-new: true
nolintlint:
allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space)
allow-unused: false # report any unused nolint directives
require-explanation: false # don't require an explanation for nolint directives
require-specific: false # don't require nolint directives to be specific about which linter is being skipped

linters:
disable-all: true
enable:
- deadcode
- depguard
- errcheck
- goconst
- gocritic
- gofmt
- goimports
- golint
- gomnd
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- interfacer
- lll
- misspell
- noctx
- nolintlint
- rowserrcheck
- scopelint
- staticcheck
- structcheck
- stylecheck
- typecheck
- unconvert
- unparam
- unused
- varcheck
- whitespace

issues:
new-from-rev: 12343 #某个commit之后才开始
exclude-rules:
- path: _test\.go # 对test go文件排除一些规则
linters:
- gocyclo
- gosec
- dupl
exclude:
- whyNoLint
- weak random number generator
- don't use Yoda conditions
- weak cryptographic primitive
- should not use built-in type string as key for value

6.golint错误检查

未指出 min-confidence 在不同等级有不同区别的提示,默认都是严格显示的。当 mini-confidence 大于 1 时,所有错误都会被接受。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
1.exported type T should have comment or be unexported
要暴露出去的结构体或者函数都应该写注释

2.comment on exported type U should be of the form "U …"
定义的type要被使用,对函数或者type写注释,注释要以函数名或type名字开头。在以"_test.go"结尾的文件,不应该要求写注释
eg:
// H is a type
type H int

3.a blank import should be only in a main or test package, or have a comment justifying it
import时要使用'_'时,要写注释。在main package或者测试文件中不需要写注释

4.exported const Whatsit should have comment (or a comment on this block) or be unexported
const模块要添加注释

5.context.Context should be the first parameter of a function
context参数要放在函数参数的第一个,当min-confidence 大于0.9时会忽略,小于等于0.9会显示

6.should not use basic type string as key in context.WithValue
context.WithValue的key参数不能是基本类型

7.if block ends with a return statement, so drop this else and outdent its block
如果else中包含return语句,则去掉整个else把else中的语句移到外面。

8.error should be the last type when returning multiple items
返回参数中有error时,把error作为返回参数的最后一个参数。当min-confidence大于0.9时会忽略,小于等于0.9时会显示

9.should replace errors.New(fmt.Sprintf(…)) with fmt.Errorf(…)
如果想打印指定的error内容,应该使用error.New(fmt.Sprintf(...))。

10.error var should have name of the form errFoo
给error.New()声明一个变量应该使用err或者Err开头。当min-confidence大于0.9时会忽略,小于等于0.9时会显示

11.error strings should not be capitalized or end with punctuation or a newline
error信息不应该以大写字母开头或者以标点符号或者换行结尾。当min-confidence大于0.8时会忽略,小于等于0.8时会显示

12.should not use dot imports
不应该使用'.'导入y--

13.should replace x += 1 with x++
x += 1应该用x++;y-=1应该用y--。当min-confidence大于0.8时会忽略,小于等于0.8时会显示

14.don’t use an underscore in package name
包名中不应该使用'_'。error信息不应该以大写字母开头或者以标点符号或者换行结尾。当min-confidence大于0.9时会忽略,小于等于0.9时会显示

15.don’t use underscores in Go names
变量名、函数名、参数名(入参,返回参数)不应该使用下划线,应该使用驼峰命名法。当min-confidence大于0.9时会忽略,小于等于0.9时会显示

16.struct field Url should be URL
简写应该都大写。'URL','ID','qID','isEOF'等。当min-confidence大于0.8时会忽略,小于等于0.8时会显示

17.don’t use ALL_CAPS in Go names; use CamelCase
不要用全大写加下划线。当min-confidence大于0.8时会忽略,小于等于0.8时会显示

18.don’t use leading k in Go names
const修饰的常量不能以小写字母'k'接大写字母开头。当min-confidence大于0.8时会忽略,小于等于0.8时会显示

19.don’t use MixedCaps in package name
go的包名不要使用大小写混合,应该全小写

20.should have a package comment, unless it’s in another file for this package
应该对包写注释。当min-confidence大于0.2时会忽略,小于等于0.2时会显示

21.package comment should be of the form "Package testdata …"
包的注释应该以"Package packagename"开头

22.package comment should not have leading space
包注释最前面不应该有空格

23.package comment is detached; there should be no blank lines between it and the package statement
包注释和'package'之间不应该有空格。当min-confidence大于0.9时会忽略,小于等于0.9时会显示

24.should omit 2nd value from range; this loop is equivalent to for x := range ...
用range写for循环时,如果第二个参数不需要,可以直接写成 for x:= range ...
for x,_ = range ... ===> for x := range ...

25.should omit values from range; this loop is equivalent to for range ...
如果range循环两个参数都不需要,可以写成 for range ...
for _,_ = range ... || for _ = range ... ===> for range ...

26.receiver name should be a reflection of its identity; don’t use generic names such as “this” or "self"
结构体的方法,引用结构体的名称应该要关联结构体
func (this foo) f1() ===> func (f foo) f1()

27.receiver name should not be an underscore, omit the name if it is unused
结构体的方法,引用结构体的名称不应该使用'_'。如果没有使用,删除它

28.receiver name a should be consistent with previous receiver name b for bar
同一结构体的方法,引用结构体的名称应该一致

29.exported method U.Other should have comment or be unexported
对外暴露的结构体对应的方法应该有注释

30.type name will be used as donut.DonutMaker by other packages, and that stutters; consider calling this Maker
对外暴露的结构体或者变量,命名时不应该以包名作为前缀。例如你的包名叫'dount',要定义一个结构体,结构体的名称应该用'Maker',不应该使用'DountMaker',因为别的包导入的时候会用'dount.Maker',使用'dount.DountMaker'会有信息冗余。当min-confidence大于0.8时会忽略,小于等于0.8时会显示

31.*var rpcTimeoutMsec is of type time.Duration; don’t use unit-specific suffix “Msec”
如果变量的结果是 *time.Duration类型,不要用'Msec'或者'Secs'结尾。当min-confidence大于0.9时会忽略,小于等于0.9时会显示

32.exported func Exported returns unexported type foo.hidden, which can be annoying to use
非该结构体的要暴露的函数返回参数为该结构体。这样用起来会很麻烦。当min-confidence大于0.8时会忽略,小于等于0.8时会显示

7.忽略Linit

使用 //nolint, nolint 可以对一行或者函数或者整个文件使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//nolint:govet,errcheck //对文件级别生效
package main

import (
"fmt"
"math/rand"
"time"
)

func main() {
rand.Seed(time.Now().UnixNano())
fmt.Println(rand.Int()) //nolint:gosec //对行级别生效
}

//nolint //对函数数级别生效
func nolintFunc() {

}

8.参考文档

1.拦截工具详细文档:https://golangci-lint.run/usage/linters/

2.golangci-lint文档 https://golangci-lint.run/

3.https://github.com/golang/go/wiki/CodeReviewComments

4.https://golang.org/doc/effective_go.html