前言

团队协作开发中,必然存在着不同的代码风格,并且诸如 http body closeunhandled error 等低级错误不能完全避免。通过使用 ci lint 能够及早的发现并修复问题,提高代码质量和幸福感。

目前主流的工具是 golangci-lint,里面集成了许多的 linters,安装及使用看官网文档即可,非常详细易读,本文主要是配合一些样例代码来分析一下相关 linters 的功能。

linters

deadcode

检查未使用的代码

var a = 100

func foo() {
println("hello,world")
}
main.go:3:5: `a` is unused (deadcode)
var a = 100
^
main.go:5:6: `foo` is unused (deadcode)
func foo() {
^

类似的工具还有 structcheckvarcheck

errcheck

检查未处理的错误

由于 Go 语言的错误处理机制,导致代码中遍地的 if err != nil ,因此有不耐烦的同学,在某些自认为不会出错的地方直接不处理,比如 json.Marshal(),虽然多数情况下没啥问题,不过一但出了问题加班啥的估计跑不了。

可以通过修改配置文件来定制不同情况是否报告

linters-settings:
errcheck:
# 检查类型断言
check-type-assertions: true
# 检查使用 _ 来处理错误
check-blank: true
func main() {
foo() var i interface{} = 1
ii := i.(int)
fmt.Println(ii) num, _ := strconv.Atoi("110")
fmt.Println(num)
} func foo() error {
return errors.New("i am error")
}
$ golangci-lint run
main.go:10:5: Error return value is not checked (errcheck)
foo()
^
main.go:13:8: Error return value is not checked (errcheck)
ii := i.(int)
^
main.go:16:7: Error return value of `strconv.Atoi` is not checked (errcheck)
num, _ := strconv.Atoi("110")
^

gosimple

简化代码

func main() {
t := time.Now()
fmt.Println(time.Now().Sub(t))
}
$ golangci-lint run
main.go:10:14: S1012: should use `time.Since` instead of `time.Now().Sub` (gosimple)
fmt.Println(time.Now().Sub(t))
^

原始仓库中还有许多别的测试用例,感兴趣的同学可以看看,可以修改配置文件来指定生效的规则,默认是 all。

govet

go vet 是官方提供的工具,可以检查出许多问题,如 printf 参数不匹配、unmarshall 时未传递指针或者接口、循环变量捕获问题等。

type AAA struct {
A int `json:"a"`
} func main() {
fmt.Printf("%s", true) var a AAA
if err := json.Unmarshal([]byte(`{"a":1}`), a); err != nil {
panic(err)
} var s []int
for i, v := range s {
go func() {
fmt.Println(i)
fmt.Println(v)
}()
}
}
$ golangci-lint run
main.go:23:16: loopclosure: loop variable i captured by func literal (govet)
fmt.Println(i)
^
main.go:24:16: loopclosure: loop variable v captured by func literal (govet)
fmt.Println(v)
^
main.go:13:2: printf: fmt.Printf format %s has arg true of wrong type bool (govet)
fmt.Printf("%s", true)
^
main.go:16:26: unmarshal: call of Unmarshal passes non-pointer as second argument (govet)
if err := json.Unmarshal([]byte(`{"a":1}`), a); err != nil {
^

ineffassign

检查无效的赋值,即变量赋值后并未使用

func main() {
a := os.Getenv("a")
if a == "" {
a = "unknown"
}
}
$ golangci-lint run
main.go:8:3: ineffectual assignment to a (ineffassign)
a = "unknown"
^

本以为这种情况应该不多,但是测试了一下我们组内的某个项目,发现还不少。

staticcheck

这个提供了更加多的检查项目,包括标准库的各种误用、并发问题、测试问题、无用的代码、代码正确性问题、性能问题、代码简化、代码风格等。

func main() {
var m map[string]int
m["a"] = 1 if strings.ToLower("a") == strings.ToLower("A") {
println(true)
}
}
$ golangci-lint run
main.go:7:2: SA5000: assignment to nil map (staticcheck)
m["a"] = 1
^
main.go:9:5: SA6005: should use strings.EqualFold instead (staticcheck)
if strings.ToLower("a") == strings.ToLower("A") {
^

asciicheck

检查代码中是否存在非 ASCII 的字符。

type TestA struct{}
type TеstB struct{} func main() {
a := TestA{}
b := TеstB{}
println(a, b)
}
$ golangci-lint run --disable-all -E asciicheck
main.go:4:6: identifier "TеstB" contain non-ASCII character: U+0435 'е' (asciicheck)
type TеstB struct{}
^

这种的话肉眼根本无法识别,记得之前工作的时候就遇到过类似情况,你眼中的门他不一定同一个门。

bidichk

这个和安全有关,检查危险的 unicode 字符序列,细节内容可以看源代码特洛伊木马攻击

bodyclose

检查 http body 是否关闭,这种问题初学者还是非常容易犯的

func main() {
resp, err := http.Get("https://example.com/")
if err != nil {
panic(err)
}
fmt.Println(resp.Status)
}
$ golangci-lint run --disable-all -E bodyclose
main.go:9:23: response body must be closed (bodyclose)
resp, err := http.Get("https://example.com/")
^

cyclop

检查代码的圈复杂度,cyclomatic complexity 是衡量代码复杂度的一种指标,值越高意味着代码复杂度越高,代码越难维护,bug 也可能更多。具体计算如下:

1 is the base complexity of a function

+1 for each 'if', 'for', 'case', '&&' or '||'

默认值是 10,不过一般业务代码稍微写着写着就超了,可以修改配置文件适当增大。

errorlint

Working with Errors in Go 1.13 引入了错误包裹机制,方便函数调用过程中形成错误链,更好的定位和处理问题。

这个工具可以检查错误处理是否遵循了其标准。

func main() {
foo()
} func foo() error {
if _, err := os.Open("a.txt"); err != nil {
return fmt.Errorf("call func one err: %s", err)
}
return nil
}
$ golangci-lint run --disable-all -E errorlint
main.go:14:46: non-wrapping format verb for fmt.Errorf. Use `%w` to format errors (errorlint)
return fmt.Errorf("call func one err: %s", err)
^

forbidigo

这个工具可以检查出指定格式的语句,比如线上代码中不应该使用 fmt.Println() 这种临时的 debug 代码,而是应该使用统一的日志组件。

func main() {
fmt.Println("debug message show remove or ues log.Println")
}
$ golangci-lint run --disable-all -E forbidigo
main.go:6:2: use of `fmt.Println` forbidden by pattern `^(fmt\.Print(|f|ln)|print|println)$` (forbidigo)
fmt.Println("debug message show remove or ues log.Println")
^

funlen

相比于 cyclop 来说,该工具就比较直接了,可以判断函数的行数和语句是否超过某个值,

The default limits are 60 lines and 40 statements.

goconst

可以监测出多次使用的字符串,应该用 const 来优化

gofumpt

go fmt 更加严格,追求更加

goimports

对导入的包排序,这个挺重要的,避免你和同事互相打架

gomnd

可以检查出代码中的魔法数字(莫名奇妙出现的数字,如果没有注释,接手的人可能完全读不懂其含义)

func main() {
var num int
if num > 60 {
//
} else {
//
}
}
$ golangci-lint run --disable-all -E gomnd
main.go:7:11: mnd: Magic number: 60, in <condition> detected (gomnd)
if num > 60 {
^

一般使用常量或者枚举等可以优化,使得代码更加易读

const PassingScore = 60

func main() {
var num int
if num > PassingScore {
//
} else {
//
}
}

gosec

安全相关,检查代码中可能存在的安全风险

noctx

检查发送 http request 时是否指定了 context.Context,在涉及到网络请求时,都应该使用 context 机制将上下文串起来,这样方便做超时、链路追踪等。

prealloc

可以检查初始化 slice 时是否可以预估其容量,进而减少动态扩容时元素拷贝的性能开销

predeclared

检查代码中是否有 go 预先定义好的标识符

func copy(a string) string {
return a
} func foo() string {
string := "x"
return string
}
$ golangci-lint run --disable-all -E predeclared
main.go:7:6: function copy has same name as predeclared identifier (predeclared)
func copy(a string) string {
^
main.go:12:2: variable string has same name as predeclared identifier (predeclared)
string := "x"
^

revive

内置了许多检查,用来替代 golint

task\message.go:9:6: exported: type name will be used as task.TaskMessage by other packages, and that stutters; consider calling this Message (revive)
type TaskMessage struct {
^
task\handler.go:16:6: exported: type name will be used as task.TaskDoneMsg by other packages, and that stutters; consider calling this DoneMsg (revive)
type TaskDoneMsg struct {
^
task\dispatch.go:30:6: exported: func name will be used as task.TaskDispatch by other packages, and that stutters; consider calling this Dispatch (revive)
func TaskDispatch(wg *sync.WaitGroup) {

检查了一下我们组某个项目的代码,发现不少类似问题,task.TaskDispath 明显比 task.Dispath 冗余了许多,就像口吃,结结巴巴的说话似的。

unconvert

检查没有必要的类型转化

总结

做为工程师,我相信大家都喜欢阅读优美、高质量的代码,而不是 IDE 满篇高亮警示的代码。既然有这么多免费的 linter 帮你 review 代码,何愁代码质量无法提升?

参考

关于静态分析的科普

https://github.com/fzipp/gocyclo

如何快速写出高质量的 Go 代码?的更多相关文章

  1. 如何写出高质量的JavaScript代码

    优秀的Stoyan Stefanov在他的新书中(<Javascript Patterns>)介绍了很多编写高质量代码的技巧,比如避免使用全局变量,使用单一的var关键字,循环式预存长度等 ...

  2. 如何写出高质量的Python代码--做好优化--改进算法点滴做起

    小伙伴你的程序还是停留在糊墙吗?优化代码可以显示程序员的素质欧! 普及一下基础了欧: 一层for简写:y = [1,2,3,4,5,6],[(i*2) for i in y ]       会输出  ...

  3. 如何组织css,写出高质量的css代码

    !如何组织css一:css的API 属于基础部分,这部分的能力用“对”和“错”来评判. 比如说把文字设置为红色,只能用color:red:这种写法是对的,其他任何写法都是错的. 二:css框架 不能用 ...

  4. 如何写出高质量的技术博客 这边文章出自http://www.jianshu.com/p/ae9ab21a5730 觉得不错直接拿过来了 好东西要大家分享嘛

        如何写出高质量的技术博客?答案是:如果你想,就一定能写出高质量的技术博客.看起来很唯心,但这就是事实.有足够愿力去做一件目标明确,有良好反馈系统的事情往往很简单.就是不停地训练,慢慢地,你自己 ...

  5. 用聚合数据API(苏州实时公交API)快速写出小程序

    利用聚合数据API快速写出小程序,过程简单. 1.申请小程序账号 2.进入开发 3.调用API.比如“苏州实时公交”小程序,选择的是苏州实时公交API. 苏州实时公交API文档:https://www ...

  6. IDEA最常用快捷键汇总+快速写出Main函数

    IDEA可以说是当下Java程序员日常开发的神器,但是想要发挥这款神器的牛逼威力,必须得熟练使用它的各种快捷键才行.本篇总结下使用IDEA(也就是IntelliJ IDEA )进行日常开发中最常用的快 ...

  7. 老司机告诉你高质量的Java代码是怎么练成的?

    一提起程序员,首先想到的一定是"码农",对,我们是高产量的优质"码农",我们拥有超跃常人的逻辑思维以及不走寻常路的分析.判别能力,当然,我们也有良好的编码规范, ...

  8. 编程精粹--编写高质量C语言代码(3):自己设计并使用断言(二)

    接着上一遍文章<<编程精粹--编写高质量C语言代码(2):自己设计并使用断言(一)>>,继续学习怎样自己设计并使用断言,来更加easy,更加不费力地自己主动寻找出程序中的错误. ...

  9. 如何编写高质量的js代码--底层原理

    转自: 如何编写高质量的 JS 函数(1) -- 敲山震虎篇   本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/7lCK9cHmunvYlbm ...

随机推荐

  1. 【LeetCode】641. Design Circular Deque 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址:https://leetcode.com/problems/design-ci ...

  2. C. Propagating tree

    C. Propagating tree time limit per test 2 seconds memory limit per test 256 megabytes input standard ...

  3. codeforces(Intel Code Challenge Elimination Round (Div.1 + Div.2, combined) )(C,D)

    C. Destroying Array time limit per test 1 second memory limit per test 256 megabytes input standard ...

  4. 小白自制Linux开发板(第二季 V3s篇) 一. 换个核心再来一次

    1.前言 大家心心念念(个人认为)的小白自制开发板全新系列正式来了,之前我们使用全志的F1C200s芯片制作了一个小电脑,众所周知,调试很艰难,坑也很多,以至于墨云到现在还是没找到对应的补救方案,为了 ...

  5. CHARACTERIZING ADVERSARIAL SUBSPACES USING LOCAL INTRINSIC DIMENSIONALITY

    目录 概 主要内容 LID LID估计 算法 实验 1 2 3 4 5 Ma X, Li B, Wang Y, et al. Characterizing Adversarial Subspaces ...

  6. matplotlib 进阶之Constrained Layout Guide

    目录 简单的例子 Colorbars Suptitle Legends Padding and Spacing spacing with colobars rcParams Use with Grid ...

  7. Kafka集群安装Version1.0.1(自带Zookeeper)

    1.说明 Kafka集群安装,基于版本1.0.1, 使用kafka_2.12-1.0.1.tgz安装包, 其中2.12是编译工具Scala的版本. 而且不需要另外安装Zookeeper服务, 使用Ka ...

  8. 很漂亮的一个背景控件——ribbons.js

    写博客的人都喜欢优化自己的博客主页,博主也一样,找了一些背景控件,像canvas-nest.js等等,最终选择了ribbons.js,并基于源码,稍作了一丁点的修改,这里分享出来 (function ...

  9. [学习笔记] RabbitMQ的简单使用

    安装依赖 # composer.json { "require": { "php-amqplib/php-amqplib": ">=2.9.0& ...

  10. CSS基础 元素整体透明效果(包含内容+背景及子元素)

    属性名:opacity:数字+px; 数字值取值0-1之间数字 数字值:1表示完全不透明 0表示完全透明使用后效果 html结构代码 <div class="box"> ...