Go语言:代码检查和优化!

简介: 代码规范检查,是根据 Go 语言的规范,对代码进行 静态扫描检查,这种检查和业务没有关系。比如程序中定义了个常量,从未使用过,虽然代码运行没有什么影响,但是为了节省内存,我们可以删除它,这种情况可以通过代码规范检查检测出来。

代码规范检查

代码规范检查,是根据 Go 语言的规范,对代码进行 静态扫描检查,这种检查和业务没有关系。

比如程序中定义了个常量,从未使用过,虽然代码运行没有什么影响,但是为了节省内存,我们可以删除它,这种情况可以通过代码规范检查检测出来。

golangci-lint

golangci-lint 是一个集成工具,它集成了很多静态代码分析工具(静态代码分析是不会运行代码的),我们通过配置这个工具,便可灵活启用需要的代码规范检查。

安装

golangci-lint 是 Go 语言编写的,可以从源代码安装它,在终端输入命令:

go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.32.2

此处安装的是  v1.32.2 版本,安装完成后,检查是否安装成功,输入命令:

golangci-lint version
//golangci-lint has version v1.32.2
复制代码

安装成功后,我们使用它来进行代码检查,比如我们有如下代码:

const name = "微客鸟窝"
func main() {
}
复制代码

终端输入命令:

golangci-lint run test/

表示检测目录 test 下的代码,运行结果:

test\test.go:3:7: `name` is unused (deadcode)
const name = "微客鸟窝"
      ^
test\test.go:4:6: `main` is unused (deadcode)
func main() {
     ^
复制代码

可以看到,程序常量未使用的问题被检测出来了,后续我们就可以对代码进行完善。

golangci-lint 配置

golangci-lint 的配置可以自定义要启用哪些 linter。golangci-lint 默认启用的 linter 有:

deadcode - 死代码检查
errcheck - 返回错误是否使用检查
gosimple - 检查代码是否可以简化
govet - 代码可疑检查,比如格式化字符串和类型不一致
ineffassign - 检查是否有未使用的代码
staticcheck - 静态分析检查
structcheck - 查找未使用的结构体字段
typecheck - 类型检查
unused - 未使用代码检查
varcheck - 未使用的全局变量和常量检查
复制代码

更多的 linter 我们可以在终端输入命令: golangci-lint linters , 来查看。

修改默认启用的 linter ,需要在项目根目录下新建一个名字为 .golangci.yml 的文件,这个就是 golangci-lint 的配置文件。在运行规范检查时,golangci-lint 会自动使用它。

不如我们在团队开发中,需要使用一个固定的 golangci-lint 版本,这样大家就可以基于同样的标准检查代码。需要在配置文件中添加如下代码:

service:
  golangci-lint-version: 1.32.2 # use the fixed version to not introduce new linters unexpectedly
复制代码

golangci-lint 的配置比较多,你可以根据自己需要来配置,可以参考官文档:golangci-lint.run/usage/confi… 。这里给一个常用的配置,供大家参考:

linters-settings:
  golint:
    min-confidence: 0
  misspell:
    locale: US
linters:
  disable-all: true
  enable:
    - typecheck
    - goimports
    - misspell
    - govet
    - golint
    - ineffassign
    - gosimple
    - deadcode
    - structcheck
    - unused
    - errcheck
service:
  golangci-lint-version: 1.32.2 # use the fixed version to not introduce new linters unexpectedly
复制代码

集成 golangci-lint 到 CI

代码检查一定要集成到 CI 流程中,这样提交代码的时候,CI 就会自动检查代码,及时发现问题并进行修正。

我们可以通过 Makefile 的方式来运行 golangci-lint ,在项目根目录创建一个 Makefile 文件,代码为:

getdeps:
   @mkdir -p ${GOPATH}/bin
   @which golangci-lint 1>/dev/null || (echo "Installing golangci-lint" && go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.32.2)
lint:
   @echo "Running $@ check"
   @GO111MODULE=on ${GOPATH}/bin/golangci-lint cache clean
   @GO111MODULE=on ${GOPATH}/bin/golangci-lint run --timeout=5m --config ./.golangci.yml
verifiers: getdeps lint
复制代码

然后可以把如下命令添加到你的 CI 中了,它可以帮你自动安装 golangci-lint,并检查你的代码。make verifiers

堆分配还是栈

Go 语言有两部分内存空间:栈内存和堆内存。

  • 栈内存由编译器自动分配和释放,开发者无法控制。栈内存一般存储函数中的局部变量、参数等,函数创建的时候,这些内存会被自动创建;函数返回的时候,这些内存会被自动释放。
  • 堆内存的生命周期比栈内存要长,如果函数返回的值还会在其他地方使用,那么这个值就会被编译器自动分配到堆上。堆内存相比栈内存来说,不能自动被编译器释放,只能通过垃圾回收器才能释放,所以栈内存效率会很高。

逃逸分析

一个变量具体是分配到堆上还是栈上,需要进行逃逸分析来查看。

示例:

func newString() *string{
  s := new(string) //通过 new 函数申请了一块内存,赋值给了指针变量 s
  *s = "微客鸟窝"
  return s //通过 return 关键字返回
}
复制代码

逃逸分析命令:

$ go build -gcflags="-m -l" ./test/test.go
# command-line-arguments
test\test.go:4:8: new(string) escapes to heap
复制代码
  • -m 表示打印出逃逸分析信息
  • -l 表示禁止内联,可以更好地观察逃逸

上面结果发现,发生了逃逸,表明指针作为函数返回值的时候,一定会发生逃逸。逃逸到堆内存的变量不能马上被回收,只能通过垃圾回收标记清除,增加了垃圾回收的压力,所以要尽可能地避免逃逸,让变量分配在栈内存上,这样函数返回时就可以回收资源,提升效率。

代码优化:

func newString() string {
  s := new(string)
  *s = "微客鸟窝"
  return *s
}
复制代码

逃逸分析命令:

$ go build -gcflags="-m -l" ./test/test.go
# command-line-arguments
test\test.go:4:10: new(string) does not escape
复制代码

虽然还是声明了指针变量 s,但是函数返回的并不是指针,所以没有发生逃逸。

Go 语言中有 3 个比较特殊的类型,它们是 slice、map 和 chan,被这三种类型引用的指针也会发生逃逸:

func main() {
  m := map[int]*string{}
  s := "微客鸟窝"
  m[0] = &s
}
复制代码
$ go build -gcflags="-m -l" ./test/test.go
# command-line-arguments
test\test.go:5:2: moved to heap: s
test\test.go:4:22: map[int]*string{} does not escape
复制代码

逃逸分析结果发现,变量 m 没有逃逸,反而被变量 m 引用的变量 s 逃逸到了堆上。

  • 被map、slice 和 chan 这三种类型引用的指针一定会发生逃逸的。
  • 指针虽然可以减少内存的拷贝,但它同样会引起逃逸,所以要根据实际情况选择是否使用指针。


相关文章
|
1天前
|
Java 编译器 Go
探索Go语言的性能优化技巧
在本文中,我们将深入探讨Go语言的底层机制,以及如何通过代码层面的优化来提升程序性能。我们将讨论内存管理、并发控制以及编译器优化等关键领域,为你提供一系列实用的技巧和最佳实践。
|
1天前
|
Cloud Native Go API
Go语言在微服务架构中的创新应用与实践
本文深入探讨了Go语言在构建高效、可扩展的微服务架构中的应用。Go语言以其轻量级协程(goroutine)和强大的并发处理能力,成为微服务开发的首选语言之一。通过实际案例分析,本文展示了如何利用Go语言的特性优化微服务的设计与实现,提高系统的响应速度和稳定性。文章还讨论了Go语言在微服务生态中的角色,以及面临的挑战和未来发展趋势。
|
1天前
|
安全 Go 调度
探索Go语言的并发模式:协程与通道的协同作用
Go语言以其并发能力闻名于世,而协程(goroutine)和通道(channel)是实现并发的两大利器。本文将深入了解Go语言中协程的轻量级特性,探讨如何利用通道进行协程间的安全通信,并通过实际案例演示如何将这两者结合起来,构建高效且可靠的并发系统。
|
1天前
|
安全 Go 开发者
破译Go语言中的并发模式:从入门到精通
在这篇技术性文章中,我们将跳过常规的摘要模式,直接带你进入Go语言的并发世界。你将不会看到枯燥的介绍,而是一段代码的旅程,从Go的并发基础构建块(goroutine和channel)开始,到高级模式的实践应用,我们共同探索如何高效地使用Go来处理并发任务。准备好,让Go带你飞。
|
2天前
|
运维 Go 开发者
Go语言在微服务架构中的应用与优势
本文深入探讨了Go语言在构建微服务架构中的独特优势和实际应用。通过分析Go语言的核心特性,如简洁的语法、高效的并发处理能力以及强大的标准库支持,我们揭示了为何Go成为开发高性能微服务的首选语言。文章还详细介绍了Go语言在微服务架构中的几个关键应用场景,包括服务间通信、容器化部署和自动化运维等,旨在为读者提供实用的技术指导和启发。
|
2天前
|
安全 Go 调度
探索Go语言的并发之美:goroutine与channel
在这个快节奏的技术时代,Go语言以其简洁的语法和强大的并发能力脱颖而出。本文将带你深入Go语言的并发机制,探索goroutine的轻量级特性和channel的同步通信能力,让你在高并发场景下也能游刃有余。
|
3天前
|
Go 开发者
Go语言中的并发编程:从基础到实践
在当今的软件开发中,并发编程已经成为了一项不可或缺的技能。Go语言以其简洁的语法和强大的并发支持,成为了开发者们的首选。本文将带你深入了解Go语言中的并发编程,从基础概念到实际应用,帮助你掌握这一重要的编程技能。
|
4天前
|
Go
使用go语言将A助手加入项目中
使用go语言将A助手加入项目中
13 2
|
3天前
|
安全 Go 调度
探索Go语言的并发模型:Goroutine与Channel的魔力
本文深入探讨了Go语言的并发模型,不仅解释了Goroutine的概念和特性,还详细讲解了Channel的用法和它们在并发编程中的重要性。通过实际代码示例,揭示了Go语言如何通过轻量级线程和通信机制来实现高效的并发处理。
|
3天前
|
存储 安全 Go
Go语言切片:从入门到精通的深度探索###
本文深入浅出地剖析了Go语言中切片(Slice)这一核心概念,从其定义、内部结构、基本操作到高级特性与最佳实践,为读者提供了一个全面而深入的理解。通过对比数组,揭示切片的灵活性与高效性,并探讨其在并发编程中的应用优势。本文旨在帮助开发者更好地掌握切片,提升Go语言编程技能。 ###