乐趣区

关于golang: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 的配置比拟多,你能够依据本人须要来配置,能够参考官文档: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/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 这三种类型援用的指针肯定会产生逃逸的。
  • 指针尽管能够缩小内存的拷贝,但它同样会引起逃逸,所以要依据理论状况抉择是否应用指针。
退出移动版