7

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

 3 years ago
source link: https://segmentfault.com/a/1190000040413280
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

代码规范检查

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

golangci-lint

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

golangci-lint 是 Go 语言编写的,可以从源代码安装它,在终端输入命令:
go get github.com/golangci/golangci-lint/cmd/[email protected]

此处安装的是 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 的配置比较多,你可以根据自己需要来配置,可以参考官文档:https://golangci-lint.run/usa... 。这里给一个常用的配置,供大家参考:

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/[email protected])
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 这三种类型引用的指针一定会发生逃逸的。
  • 指针虽然可以减少内存的拷贝,但它同样会引起逃逸,所以要根据实际情况选择是否使用指针。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK