如何快速写出高质量的 Go 代码?
前言
团队协作开发中,必然存在着不同的代码风格,并且诸如 http body close
,unhandled 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() {
^
类似的工具还有 structcheck,varcheck
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 代码?的更多相关文章
- 如何写出高质量的JavaScript代码
优秀的Stoyan Stefanov在他的新书中(<Javascript Patterns>)介绍了很多编写高质量代码的技巧,比如避免使用全局变量,使用单一的var关键字,循环式预存长度等 ...
- 如何写出高质量的Python代码--做好优化--改进算法点滴做起
小伙伴你的程序还是停留在糊墙吗?优化代码可以显示程序员的素质欧! 普及一下基础了欧: 一层for简写:y = [1,2,3,4,5,6],[(i*2) for i in y ] 会输出 ...
- 如何组织css,写出高质量的css代码
!如何组织css一:css的API 属于基础部分,这部分的能力用“对”和“错”来评判. 比如说把文字设置为红色,只能用color:red:这种写法是对的,其他任何写法都是错的. 二:css框架 不能用 ...
- 如何写出高质量的技术博客 这边文章出自http://www.jianshu.com/p/ae9ab21a5730 觉得不错直接拿过来了 好东西要大家分享嘛
如何写出高质量的技术博客?答案是:如果你想,就一定能写出高质量的技术博客.看起来很唯心,但这就是事实.有足够愿力去做一件目标明确,有良好反馈系统的事情往往很简单.就是不停地训练,慢慢地,你自己 ...
- 用聚合数据API(苏州实时公交API)快速写出小程序
利用聚合数据API快速写出小程序,过程简单. 1.申请小程序账号 2.进入开发 3.调用API.比如“苏州实时公交”小程序,选择的是苏州实时公交API. 苏州实时公交API文档:https://www ...
- IDEA最常用快捷键汇总+快速写出Main函数
IDEA可以说是当下Java程序员日常开发的神器,但是想要发挥这款神器的牛逼威力,必须得熟练使用它的各种快捷键才行.本篇总结下使用IDEA(也就是IntelliJ IDEA )进行日常开发中最常用的快 ...
- 老司机告诉你高质量的Java代码是怎么练成的?
一提起程序员,首先想到的一定是"码农",对,我们是高产量的优质"码农",我们拥有超跃常人的逻辑思维以及不走寻常路的分析.判别能力,当然,我们也有良好的编码规范, ...
- 编程精粹--编写高质量C语言代码(3):自己设计并使用断言(二)
接着上一遍文章<<编程精粹--编写高质量C语言代码(2):自己设计并使用断言(一)>>,继续学习怎样自己设计并使用断言,来更加easy,更加不费力地自己主动寻找出程序中的错误. ...
- 如何编写高质量的js代码--底层原理
转自: 如何编写高质量的 JS 函数(1) -- 敲山震虎篇 本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/7lCK9cHmunvYlbm ...
随机推荐
- C. Success Rate
Success Rate 题目链接 题意 给你两个分数形式的数,然后有两种变化方式 上下都+1 仅下面部分+1 让你求第一个分数变化到第二个分数的最小步数. 思路 有几种特殊情况分类讨论一下. 首先我 ...
- 【LeetCode】343. Integer Break 解题报告(Python & C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 数学解法 动态规划 日期 题目地址:https:// ...
- Bean拷贝工具
Apache BeanUtils Spring BeanUtils cglib BeanCopier Hutool BeanUtil Mapstruct Dozer 1.Apache BeanUti ...
- Java语言程序设计复习提纲
这是我在准备Java考试时整理的提纲,如果是通过搜索引擎搜索到这篇博客的师弟师妹,建议还是先参照PPT和课本,这个大纲也不是很准确,自己总结会更有收获,多去理解含义,不要死记硬背,否则遇到概念辨析题 ...
- nssm常用命令(在Windows系统下安装服务的工具)
nssm install servername //创建servername服务 nssm start servername //启动服务 nssm stop servername //暂停服务 ns ...
- Java初学者作业——编写 Java 程序,用户输入 3 个操作数,分别求出最大值、最小值和平均值。
返回本章节 返回作业目录 需求说明: 编写 Java 程序,用户输入 3 个操作数,分别求出最大值.最小值和平均值. 实现思路: 定义 Java 类,定义 3 个方法,用来求 3 个数字的最大值.最小 ...
- Browser Events 常用浏览器事件
事件 说明 click 鼠标点击时触发此事件 dblclick 鼠标双击时触发此事件 mousedown 按下鼠标时触发此事件 mouseup 鼠标按下后松开鼠标时触发此事件 mouseover 当鼠 ...
- Solon 1.6.12 发布,类似 Spring 的生态体系
关于官网 千呼万唤始出来: https://solon.noear.org .整了一个月多了,总体样子有了...还得不断接着整! 关于 Solon Solon 是一个轻量级应用开发框架.支持 Web. ...
- Java 字符与字符串
字符 // 定义字符 char c1 = 'a'; char c2 = '1'; char c3 = '中'; // 自动装箱 Character c = c1; // 自动拆箱 c1 = c; // ...
- 初识python: 递归函数 - 分解质因数
分解质因数: 任何一个合数都可以写成几个质数相乘的形式.其中每个质数都是这个合数的因数,叫做这个合数的分解质因数.分解质因数只针对合数. 比如: 8 分解质因数是:2*2*2 10分解质因数是:2*5 ...