【go语言学习笔记】04 Go 语言工程管理

文章目录

    • 一、质量保证
      • 1. 单元测试
        • 1.1 定义
        • 1.2 Go 语言的单元测试
        • 1.3 单元测试覆盖率
      • 2. 基准测试
        • 2.1 定义
        • 2.2 Go 语言的基准测试
        • 2.3 计时方法
        • 2.4 内存统计
        • 2.5 并发基准测试
        • 2.6 基准测试实战
      • 3. 特别注意
    • 二、性能优化
      • 1. 代码规范检查
        • 1.1 定义
        • 1.2 golangci-lint
          • 1.2.1 安装
          • 1.2.2 使用
          • 1.2.3 golangci-lint 配置
          • 1.2.4 集成 golangci-lint 到 CI
      • 2. 性能优化
        • 2.1 堆内存和栈内存
        • 2.2 逃逸分析
        • 2.3 优化技巧
    • 三、协作开发
      • 1. Go 语言中的包
        • 1.1 定义
        • 1.2 使用包
        • 1.3 作用域
        • 1.4 自定义包
        • 1.5 init 函数
      • 2. Go 语言中的模块
        • 2.1 go mod
        • 2.2 使用第三方模块

一、质量保证

1. 单元测试

单元测试是保证代码质量的好方法,但单元测试也不是万能的,使用它可以降低 Bug 率,但也不要完全依赖。除了单元测试外,还可以辅以 Code Review、人工测试等手段更好地保证代码质量。

1.1 定义

顾名思义,单元测试强调的是对单元进行测试。在开发中,一个单元可以是一个函数、一个模块等。一般情况下,要测试的单元应该是一个完整的最小单元,比如 Go 语言的函数。

单元测试由开发者自己编写,也就是谁改动了代码,谁就要编写相应的单元测试代码以验证本次改动的正确性。

1.2 Go 语言的单元测试

虽然每种编程语言里单元测试的概念是一样的,但它们对单元测试的设计不一样。Go 语言也有自己的单元测试规范。

下例通过递归的方式实现了斐波那契数列的计算。

func Fibonacci(n int) int {if n < 0 {return 0}if n == 0 {return 0}if n == 1 {return 1}return Fibonacci(n-1) + Fibonacci(n-2)
}

该 Fibonacci 函数在main.go文件中,那么对 Fibonacci 函数进行单元测试的代码需要放在同一目录的main_test.go中,测试代码如下:

func TestFibonacci(t *testing.T) {//预先定义的一组斐波那契数列作为测试用例fsMap := map[int]int{}fsMap[0] = 0fsMap[1] = 1fsMap[2] = 1fsMap[3] = 2fsMap[4] = 3fsMap[5] = 5fsMap[6] = 8fsMap[7] = 13fsMap[8] = 21fsMap[9] = 34for k, v := range fsMap {fib := Fibonacci(k)if v == fib {t.Logf("结果正确:n为%d,值为%d", k, fib)} else {t.Errorf("结果错误:期望%d,但是计算的值是%d", v, fib)}}
}

在这个单元测试中,通过 map 预定义了一组测试用例,然后通过 Fibonacci 函数计算结果。同预定义的结果进行比较,如果相等,则说明 Fibonacci 函数计算正确,不相等则说明计算错误。

然后即可运行如下命令,进行单元测试:

➜ go test -v .

这行命令会运行当前目录下的所有单元测试,因为只写了一个单元测试,所以可以看到结果如下所示:

➜ go test -v .
=== RUN   TestFibonaccimain_test.go:21: 结果正确:n为0,值为0main_test.go:21: 结果正确:n为1,值为1main_test.go:21: 结果正确:n为6,值为8main_test.go:21: 结果正确:n为8,值为21main_test.go:21: 结果正确:n为9,值为34main_test.go:21: 结果正确:n为2,值为1main_test.go:21: 结果正确:n为3,值为2main_test.go:21: 结果正确:n为4,值为3main_test.go:21: 结果正确:n为5,值为5main_test.go:21: 结果正确:n为7,值为13
--- PASS: TestFibonacci (0.00s)
PASS
ok      test     (cached)

在打印的测试结果中可以看到 PASS 标记,说明单元测试通过,而且还可以看到在单元测试中写的日志。

Go 语言测试框架可以让开发者很容易地进行单元测试,但是需要遵循五点规则:

  1. 含有单元测试代码的 go 文件必须以 _test.go 结尾,Go 语言测试工具只认符合这个规则的文件。
  2. 单元测试文件名 _test.go 前面的部分最好是被测试的函数所在的 go 文件的文件名,比如以上示例中单元测试文件叫 main_test.go,因为测试的 Fibonacci 函数在 main.go 文件里。
  3. 单元测试的函数名必须以 Test 开头,是可导出的、公开的函数。
  4. 测试函数的签名必须接收一个指向 testing.T 类型的指针,并且不能返回任何值。
  5. 函数名最好是 Test + 要测试的函数名,比如例子中是 TestFibonacci,表示测试的是 Fibonacci 这个函数。

单元测试的重点在于熟悉业务代码的逻辑、场景等,以便尽可能地全面测试,保障代码质量。

1.3 单元测试覆盖率

Go 语言提供了非常方便的命令来查看单元测试覆盖率。还是以 Fibonacci 函数的单元测试为例,通过一行命令即可查看它的单元测试覆盖率。

➜ go test -v --coverprofile=res.cover .

这行命令包括 --coverprofile 这个 Flag,它可以得到一个单元测试覆盖率文件,运行这行命令还可以同时看到测试覆盖率。Fibonacci 函数的测试覆盖率如下:

PASS
coverage: 85.7% of statements
ok      test     0.367s  coverage: 85.7% of statements

可以看到,测试覆盖率为 85.7%。从这个数字来看,Fibonacci 函数应该没有被全面地测试,这时候就需要查看详细的单元测试覆盖率报告了。

运行如下命令,可以得到一个 HTML 格式的单元测试覆盖率报告:

➜ go tool cover -html=res.cover -o=res.html

命令运行后,会在当前目录下生成一个 html 文件,内容如下:
单元测试覆盖率报告

红色标记的部分是没有测试到的,绿色标记的部分是已经测试到的。这就是单元测试覆盖率报告的好处,通过它可以很容易地检测自己写的单元测试是否完全覆盖。

2. 基准测试

2.1 定义

基准测试(Benchmark)是一项用于测量和评估软件性能指标的方法,主要用于评估代码的性能。

2.2 Go 语言的基准测试

Go 语言的基准测试和单元测试规则基本一样,只是测试函数的命名规则不一样。

Fibonacci 函数的基准测试代码如下:

func BenchmarkFibonacci(b *testing.B){for i:=0;i<b.N;i++{Fibonacci(10)}
}

Go 语言基准测试和单元测试的不同点如下:

  1. 基准测试函数必须以 Benchmark 开头,必须是可导出的;
  2. 函数的签名必须接收一个指向 testing.B 类型的指针,并且不能返回任何值;
  3. 最后的 for 循环很重要,被测试的代码要放到循环里;
  4. b.N 是基准测试框架提供的,表示循环的次数,因为需要反复调用测试的代码,才可以评估性能。

可以通过如下命令来测试 Fibonacci 函数的性能:

➜ go test -bench=. .
goos: darwin
goarch: amd64
pkg: test
BenchmarkFibonacci-8     3461616               343 ns/op
PASS
ok      test     2.230s

运行基准测试也要使用 go test 命令,不过要加上 -bench 这个 Flag,它接受一个表达式作为参数,以匹配基准测试的函数,"."表示运行所有基准测试。

输出的结果中函数后面的 -8 表示运行基准测试时对应的 GOMAXPROCS 的值。接着的 3461616 表示运行 for 循环的次数,也就是调用被测试代码的次数,最后的 343 ns/op 表示每次需要花费 343 纳秒。

基准测试的时间默认是 1 秒,也就是 1 秒调用 3461616 次、每次调用花费 343 纳秒。如果想让测试运行的时间更长,可以通过 -benchtime 指定,比如 3 秒,代码如下所示:

go test -bench=. -benchtime=3s .

2.3 计时方法

进行基准测试之前会做一些准备,比如构建测试数据等,这些准备也需要消耗时间,所以需要把这部分时间排除在外。这就需要通过 ResetTimer 方法重置计时器,示例代码如下:

func BenchmarkFibonacci(b *testing.B) {n := 10b.ResetTimer() //重置计时器for i := 0; i < b.N; i++ {Fibonacci(n)}
}

这样可以避免因为准备数据耗时造成的干扰。

除了 ResetTimer 方法外,还有 StartTimer 和 StopTimer 方法,可以灵活地控制什么时候开始计时、什么时候停止计时。

2.4 内存统计

在基准测试时,还可以统计每次操作分配内存的次数,以及每次操作分配的字节数,这两个指标可以作为优化代码的参考。要开启内存统计可以通过 ReportAllocs() 方法:

func BenchmarkFibonacci(b *testing.B) {n := 10b.ReportAllocs() //开启内存统计b.ResetTimer() //重置计时器for i := 0; i < b.N; i++ {Fibonacci(n)}
}

再运行基准测试,就可以看到如下结果:

go test -bench=.  .
goos: darwin
goarch: amd64
pkg: test
BenchmarkFibonacci-8  2486265  486 ns/op  0 B/op  0 allocs/op
PASS
ok      test     2.533s

可以看到相比原来的基准测试多了两个指标,分别是 0 B/op 和 0 allocs/op。前者表示每次操作分配了多少字节的内存,后者表示每次操作分配内存的次数。这两个指标可以作为代码优化的参考,尽可能地越小越好。

以上两个指标不是越小越好,因为有时候代码实现需要空间换时间,所以要根据自己的具体业务而定,做到在满足业务的情况下越小越好。

在运行 go test 命令时,也可以使用 -benchmem 这个 Flag 进行内存统计。如下所示:

go test -bench=. -benchmem  .

这种通过 -benchmem 查看内存的方法适用于所有的基准测试用例。

2.5 并发基准测试

除了普通的基准测试外,Go 语言还支持并发基准测试,可以测试在多个 goroutine 并发下代码的性能。以 Fibonacci 为例,它的并发基准测试代码如下:

func BenchmarkFibonacciRunParallel(b *testing.B) {n := 10b.RunParallel(func(pb *testing.PB) {for pb.Next() {Fibonacci(n)}})
}

可以看到,Go 语言通过 RunParallel 方法运行并发基准测试。RunParallel 方法会创建多个 goroutine,并将 b.N 分配给这些 goroutine 执行。

2.6 基准测试实战

以 Fibonacci 函数为例,根据前述的基准测试,会发现它并没有分配新的内存,也就是说 Fibonacci 函数慢并不是因为内存,排除掉这个原因,就可以归结为所写的算法问题了。

在递归运算中,一定会有重复计算,这是影响递归的主要因素。解决重复计算可以使用缓存,把已经计算好的结果保存起来,就可以重复使用了。

修改后的 Fibonacci 函数的代码如下:

//缓存已经计算的结果
var cache = map[int]int{}
func Fibonacci(n int) int {if v, ok := cache[n]; ok {return v}result := 0switch {case n < 0:result = 0case n == 0:result = 0case n == 1:result = 1default:result = Fibonacci(n-1) + Fibonacci(n-2)}cache[n] = resultreturn result
}

改造后再来运行基准测试,结果如下所示:

BenchmarkFibonacci-8  97823403  11.7 ns/op

可以看到,结果为 11.7 纳秒,相比优化前的 343 纳秒,性能足足提高了 28 倍。

3. 特别注意

go test 以及 go tool cover 等相关命令通过如下使用 “=” 给参数赋值的方式仅适用于Mac和Linux,但不适用于Windows。
示例:

go tool cover -html=res.cover -o=res.html

上述命令在Windows中运行会生成如下错误信息:

too many arguments
For usage information, run "go tool cover -help"

正确的使用方式是:

go tool cover -html res.cover -o res.html

这适用于所有平台。

请注意,这种不良的使用模式遍布整个 go 生态系统。

二、性能优化

在项目开发中,保证代码质量和性能的手段不只有单元测试和基准测试,还有代码规范检查和性能优化

  • 代码规范检查是对单元测试的一种补充,它可以从非业务的层面检查代码是否还有优化的空间,比如变量是否被使用、是否是死代码等等。
  • 性能优化是通过基准测试来衡量的,这样才知道优化部分是否真的提升了程序的性能。

1. 代码规范检查

1.1 定义

代码规范检查,顾名思义,是从 Go 语言层面出发,依据 Go 语言的规范对代码进行的静态扫描检查,这种检查和业务无关。

比如定义了个常量从未使用过,虽然对代码运行并没有造成什么影响,但是这个常量是可以删除的。再比如调用了一个函数,该函数返回了一个 error,但是并没有对该 error 做判断,这种情况下,程序也可以正常编译运行。但是代码写得不严谨,因为返回的 error 被忽略了。

除了上述这两种情况,还有拼写问题、死代码、代码简化检测、命名中带下划线、冗余代码等,都可以使用代码规范检查检测出来。

1.2 golangci-lint

要想对代码进行检查,则需要对代码进行扫描,静态分析写的代码是否存在规范问题。

静态代码分析是不会运行代码的。

可用于 Go 语言代码分析的工具有很多,比如 golint、gofmt、misspell 等,如果一一引用配置,就会比较烦琐,所以通常不会单独地使用它们,而是使用 golangci-lint。

golangci-lint 是一个集成工具,它集成了很多静态代码分析工具。通过配置这一工具,可以很灵活地启用需要的代码规范检查。

1.2.1 安装

如果要使用 golangci-lint,首先需要安装。 golangci-lint 有以下几种安装方式:

(1)Binaries(在Linux 和 Windows 环境下,建议通过如下方式进行安装)

# binary will be $(go env GOPATH)/bin/golangci-lint
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.53.3

(2)Install from Source

因为 golangci-lint 本身就是 Go 语言编写的,所以可以从源代码安装。

go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.53.3

(3)brew安装(仅限MacOS)

brew install golangci-lint
brew upgrade golangci-lint

安装完成后,在终端输入如下命令,检测是否安装成功。

➜ golangci-lint version
golangci-lint has version v1.53.3 built with go1.20.5
1.2.2 使用

可以运行如下命令运行golangci-lint:

golangci-lint run
# 等价于
golangci-lint run ./...

也可以指定要分析的目录和文件:

golangci-lint run dir1 dir2/... dir3/file1.go

目录不会递归分析其子目录中的文件。要想递归地分析该目录下所有文件,需要在路径后附加\...

1.2.3 golangci-lint 配置

golangci-lint 的配置比较灵活可以自定义要启用哪些 linter。golangci-lint 默认启用的 linter,包括这些:

errcheck - Errcheck是一个用于检查Go代码中未检查的errors的程序。在某些情况下,这些未经检查的errors可能是严重的错误
gosimple - 用于Go源代码的Linter,专门用于简化代码
govet - Vet检查Go源代码并报告可疑的结构,例如Printf调用的参数与格式字符串不一致
ineffassign - 检测何时不使用对现有变量的赋值
staticcheck - 它是来自staticcheck的一组规则。它与staticcheck二进制文件不同。staticcheck的作者不支持也不赞成在golangci-lint中使用staticcheck作为库
unused - 检查Go代码中未使用的常量、变量、函数和类型

golangci-lint 支持的更多 linter,可以在终端中输入 golangci-lint linters 命令查看,并且可以看到每个 linter 的说明。

如果要修改默认启用的 linter,就需要对 golangci-lint 进行配置,即在项目根目录下新建一个名字为 .golangci.yml 的文件,这就是 golangci-lint 的配置文件。在运行代码规范检查的时候,golangci-lint 会自动使用它。假设只启用 unused 检查,可以这样配置:

linters:disable-all: trueenable:- unused

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

service:golangci-lint-version: 1.53.3 # use the fixed version to not introduce new linters unexpectedly

此外,还可以针对每个启用的 linter 各自进行配置,比如要设置 misspell 这个 linter 拼写检测的语言为 US,可以使用如下代码设置:

linters-settings:misspell:# Correct spellings using locale preferences for US or UK.# Setting locale to US will correct the British spelling of 'colour' to 'color'.# Default is to use a neutral variety of English.locale: US# Default: []ignore-words:- someword

关于 golangci-lint 的更多配置可以参考官方文档,这里给出一个常用的配置,代码如下:

linters:disable-all: trueenable:- errcheck- goconst- goimports- gosimple- govet- ineffassign- misspell- staticcheck- unusedservice:golangci-lint-version: 1.53.3 # use the fixed version to not introduce new linters unexpectedly
1.2.4 集成 golangci-lint 到 CI

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

不管使用 Jenkins,还是 Gitlab CI,或者 Github Action,都可以通过Makefile的方式运行 golangci-lint。可以在项目根目录下创建一个 Makefile 文件,并添加如下代码:

getdeps:@mkdir -p ${GOPATH}/bin@which golangci-lint 1>/dev/null || (echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.53.3
)
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

2. 性能优化

性能优化的目的是让程序更好、更快地运行,但是它不是必要的。所以在程序开始的时候不必刻意追求性能优化,先大胆地写代码就好了,写正确的代码是性能优化的前提。

是否进行性能优化取决于两点:业务需求自我驱动。所以不要刻意地去做性能优化,尤其是不要提前做,先保证代码正确并上线,然后再根据业务需要,决定是否进行优化以及花多少时间优化。自我驱动其实是一种编码能力的体现,比如有经验的开发者在编码的时候,潜意识地就避免了逃逸,减少了内存拷贝,在高并发的场景中设计了低延迟的架构。

2.1 堆内存和栈内存

在比较 C 语言中,内存分配是手动申请的,内存释放也需要手动完成。

  • 手动控制有一个很大的好处就是需要多少就申请多少,可以最大化地利用内存
  • 但是这种方式也有一个明显的缺点,就是如果忘记释放内存,就会导致内存泄漏

为了让程序员更好地专注于业务代码的实现,Go 语言增加了垃圾回收机制,自动地回收不再使用的内存。

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

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

2.2 逃逸分析

既然栈内存的效率更高,肯定是优先使用栈内存。判断 Go 语言将一个变量分配到堆上还是栈上,需要逃逸分析。

示例:

func newString() *string{s:=new(string)*s = "小明"return s
}

在这个示例中:

  • 通过 new 函数申请了一块内存;
  • 然后把它赋值给了指针变量 s;
  • 最后通过 return 关键字返回。

通过逃逸分析来看下是否发生了逃逸,命令如下:

➜ go build -gcflags="-m -l" ./main.go
# command-line-arguments
./main.go:16:8: new(string) escapes to heap

在这一命令中,-m 表示输出优化信息(可以打印出逃逸分析信息),-l 表示禁止内联优化,可以更好地观察逃逸。从以上输出结果可以看到,发生了逃逸,也就是说指针作为函数返回值的时候,一定会发生逃逸

gcflags参数可以用于指定编译器的参数,可以用于控制代码生成行为、优化等。

逃逸到堆内存的变量不能马上被回收,只能通过垃圾回收标记清除,增加了垃圾回收的压力,所以要尽可能地避免逃逸,让变量分配在栈内存上,这样函数返回时就可以回收资源,提升效率。

对上述代码进行避免逃逸的优化,优化后的函数代码如下:

func newString() string{s:=new(string)*s = "小明"return *s
}

再次通过命令查看以上代码的逃逸分析,命令如下:

➜ go build -gcflags="-m -l" ./main.go
# command-line-arguments
./main.go:14:8: new(string) does not escape

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

关于指针作为函数返回逃逸的例子,有时不直接使用指针也会发生逃逸,示例代码如下:

fmt.Println("小明")

运行逃逸分析会看到如下结果:

➜ go build -gcflags="-m -l" ./main.go
# command-line-arguments
./main.go:13:13: ... argument does not escape
./main.go:13:14: "小明" escapes to heap
./main.go:17:8: new(string) does not escape

可以看到,「小明」这个字符串逃逸到了堆上,这是因为「小明」这个字符串被已经逃逸的指针变量引用,所以它也跟着逃逸了,引用代码如下:

func (p *pp) printArg(arg interface{}, verb rune) {p.arg = arg//省略其他无关代码
}

所以被已经逃逸的指针引用的变量也会发生逃逸

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

func main() {m:=map[int]*string{}s:="小明"m[0] = &s
}

同样运行逃逸分析,结果如下:

➜  gotour go build -gcflags="-m -l" ./main.go
# command-line-arguments
./main.go:16:2: moved to heap: s
./main.go:15:20: map[int]*string literal does not escape

从这一结果可以看到,变量 m 没有逃逸,反而被变量 m 引用的变量 s 逃逸到了堆上。所以被map、slice 和 chan 这三种类型引用的指针一定会发生逃逸的

逃逸分析是判断变量是分配在堆上还是栈上的一种方法,在实际的项目中要尽可能避免逃逸,这样就不会被 GC 拖慢速度,从而提升效率。

从逃逸分析来看,指针虽然可以减少内存的拷贝,但它同样会引起逃逸,所以要根据实际情况选择是否使用指针。

2.3 优化技巧

几个优化的小技巧:

  1. 尽可能避免逃逸,因为栈内存效率更高,还不用 GC。比如小对象的传参,array 要比 slice 效果好;
  2. 如果避免不了逃逸,还是在堆上分配了内存,那么对于频繁的内存申请操作,要学会重用内存,比如使用 sync.Pool;
  3. 选用合适的算法,达到高性能的目的,比如空间换时间。

性能优化的时候,要结合基准测试,来验证自己的优化是否有提升。

除上述3点之外,还有一些小技巧,比如要尽可能避免使用锁、并发加锁的范围要尽可能小、使用 StringBuilder 做 string 和 [ ] byte 之间的转换、defer 嵌套不要太多等等。

Go 语言有一个自带的性能剖析的工具 pprof,通过它可以查看 CPU 分析、内存分析、阻塞分析、互斥锁分析等。

三、协作开发

在 Go 语言中,包是同一目录中,编译在一起的源文件的集合。包里面含有函数、类型、变量和常量,不同包之间的调用,必须要首字母大写才可以。

而模块又是相关的包的集合,它里面包含了很多为了实现该模块的包,并且还可以通过模块的方式,把已经完成的模块提供给其他项目(模块)使用,达到了代码复用、研发效率提高的目的。

所以对于一个项目(模块)来说,它具有模块 ➡ 包 ➡ 函数类型这样三层结构,同一个模块中,可以通过包组织代码,达到代码复用的目的;在不同模块中,就需要通过模块的引入,达到这个目的。

1. Go 语言中的包

1.1 定义

在 Go 语言中,一个包是通过package 关键字定义的,最常见的就是main 包,它的定义如下所示:

package main

一个包就是一个独立的空间,可以在这个包里定义函数、结构体等。这时可以认为这些函数、结构体是属于这个包的。

1.2 使用包

如果想使用一个包里的函数或者结构体,就需要先导入这个包,才能使用,比如常用的 fmt包,代码示例如下所示:

package main
import "fmt"
func main() {fmt.Println("先导入fmt包,才能使用")
}

要导入一个包,需要使用 import 关键字;如果需要同时导入多个包,则可以使用小括号,示例代码如下所示:

import ("fmt""os"
)

1.3 作用域

在Java 语言中,通过 public、private 这些修饰符修饰一个类的作用域,但是在Go 语言中,并没有这样的作用域修饰符,它是通过首字母是否大写来区分的,这同时也体现了 Go 语言的简洁。

Go 语言的作用域可以总结以下两点:

  • Go 语言中,所有的定义,比如函数、变量、结构体等,如果首字母是大写,那么就可以被其他包使用;
  • 如果首字母是小写的,就只能在同一个包内使用。

1.4 自定义包

可以自定义自己的包,通过包的方式把相同业务、相同职责的代码放在一起。比如有一个 util 包,用于存放一些常用的工具函数,项目结构如下所示:

test
├── main.go
└── util└── string.go

在 Go 语言中,一个包对应一个文件夹。上例中的 string.go 文件就属于 util 包,它的包定义如下所示:

package util

可以看到,Go 语言中的包是代码的一种组织形式,通过包把相同业务或者相同职责的代码放在一起。通过包对代码进行归类,便于代码维护以及被其他包调用,提高团队协作效率。

1.5 init 函数

除了 main 这个特殊的函数外,Go 语言还有一个特殊的函数——init,通过它可以实现包级别的一些初始化操作

init 函数没有返回值,也没有参数,它先于 main 函数执行,代码如下所示:

func init() {fmt.Println("init in main.go ")
}

一个包中可以有多个 init 函数,但是它们的执行顺序并不确定,所以如果定义了多个 init 函数的话,要确保它们是相互独立的,一定不要有顺序上的依赖。

init 函数作用主要就是在导入一个包时,对这个包做一些必要的初始化操作,比如数据库连接和一些数据的检查,确保可以正确地使用这个包。

2. Go 语言中的模块

在 Go 语言中,一个模块可以包含很多个包,所以模块是相关的包的集合。

在 Go 语言中:

  • 一个模块通常是一个项目
  • 也可以是一个框架,比如常用的 Web 框架 gin。

2.1 go mod

Go 语言提供了 go mod 命令来方便创建一个模块(项目),比如要创建一个 test 模块,可以通过如下命令实现:

➜ go mod init test
go: creating new go.mod: module test

运行这一命令后,会生成一个 go.mod 文件,它里面的内容如下所示:

module test
go 1.20
  • 第一句是该项目的模块名,也就是 test;
  • 第二句表示要编译该模块至少需要Go 1.20 版本的 SDK。

模块名最好是以自己的域名开头,比如 company.org/test,这样就可以很大程度上保证模块名的唯一,不至于和其他模块重名。

2.2 使用第三方模块

在 Github 上有很多开源的 Go 语言项目,它们都是一个个独立的模块,可以直接使用,提高开发效率,比如 Web 框架 gin-gonic/gin。

在使用第三方模块之前,需要先设置下 Go 代理,也就是 GOPROXY,这样就可以获取到第三方模块。

可以使用 goproxy.io 这个代理,进行如下代码设置即可:

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.io,direct

在实际的项目开发中,除了第三方模块外,还有自己开发的模块,可以放在公司的 GitLab上,然后通过 Go 语言提供的GOPRIVATE 这个环境变量来设置私有模块的代理和下载策略。当使用go get命令下载或更新依赖模块时,Go会首先检查模块是否为私有模块,如果是私有模块,则根据GOPRIVATE的设置来确定下载策略。

具体来说,当GOPRIVATE中有匹配的模式时,Go会使用私有模块代理进行下载。如果GOPRIVATE中没有任何模式匹配,则Go会使用公共代理或直接从代码仓库中下载。示例如下:

# 设置不走 proxy 的私有仓库,多个用逗号相隔(可选)。
go env -w GOPRIVATE=*.corp.example.com

要使用一个具体的模块,首先需要安装它。以 Gin 这个 Web 框架为例,通过如下命令即可安装:

go get -u github.com/gin-gonic/gin

安装成功后,像标准包一样通过 import 命令导入即可,代码如下所示:

package main
import ("fmt""github.com/gin-gonic/gin"
)
func main() {fmt.Println("先导入fmt包,才能使用")r := gin.Default()r.Run()
}

以上代码现在还无法编译通过,因为还没有同步 Gin 这个模块的依赖,也就是没有把它添加到go.mod 文件中。通过如下命令可以添加缺失的模块:

go mod tidy

该命令可以把缺失的模块添加进来,同时也可以移除不再需要的模块。所以不用手动去修改 go.mod 文件,通过 Go 语言的工具链比如 go mod tidy 命令,就可以自动地维护、自动地添加或者修改 go.mod 的内容。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/30872.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C# 基础精讲】循环语句:for、while、do-while

循环语句是C#编程中用于重复执行一段代码块的关键结构。C#支持for、while和do-while三种常见的循环语句&#xff0c;它们允许根据条件来控制代码块的重复执行。在本文中&#xff0c;我们将详细介绍这三种循环语句的语法和使用方法。 for循环 for循环是一种常见的循环结构&…

MySQL8是什么-MySQL8知识详解

从今天起&#xff0c;开始更新MySQL8的教程&#xff0c;今天更新MySQL8的第一篇文章&#xff0c;主要讲了MySQL8是什么、MySQL数据库的概念、MySQL的优势和MySQL的发展历史。 1、MySQL8是什么 MySQL 8是一个开源的关系型数据库管理系统。它是MySQL数据库的最新版本&#xff0c…

【RTT驱动框架分析06】-pwn驱动框架分析+pwm驱动实现

pwm pwm应用程序开发 访问 PWM 设备API 应用程序通过 RT-Thread 提供的 PWM 设备管理接口来访问 PWM 设备硬件&#xff0c;相关接口如下所示&#xff1a; 函数描述rt_device_find()根据 PWM 设备名称查找设备获取设备句柄rt_pwm_set()设置 PWM 周期和脉冲宽度rt_pwm_enable…

Tomcat 部署及优化

Tomcat概述 Tomcat 是 Java 语言开发的&#xff0c;Tomcat 服务器是一个免费的开放源代码的 Web 应用服务器&#xff0c;是 Apache 软件基金会的 Jakarta 项目中的一个核心项目&#xff0c;由 Apache、Sun 和其他一些公司及个人共同开发而成。在中小型系统和并发访问用户不是很…

【腾讯云 Cloud Studio 实战训练营】基于Cloud Studio构建React完成点餐H5页面

前言 【腾讯云 Cloud Studio 实战训练营】基于Cloud Studio 构建React完成点餐H5页面一、Cloud Studio介绍1.1 Cloud Studio 是什么1.2 相关链接1.3 登录注册 二、实战练习2.1 初始化工作空间2.2 开发一个简版的点餐系统页面1. 安装 antd-mobile2. 安装 less 和 less-loader3. …

opencv基础-38 形态学操作-闭运算(先膨胀,后腐蚀)cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)

闭运算是先膨胀、后腐蚀的运算&#xff0c;它有助于关闭前景物体内部的小孔&#xff0c;或去除物体上的小黑点&#xff0c;还可以将不同的前景图像进行连接。 例如&#xff0c;在图 8-17 中&#xff0c;通过先膨胀后腐蚀的闭运算去除了原始图像内部的小孔&#xff08;内部闭合的…

剑指offer39.数组中出现次数超过一半的数字

这个题非常简单&#xff0c;解法有很多种&#xff0c;我用的是HashMap记录每个元素出现的次数&#xff0c;只要次数大于数组长度的一半就返回。下面是我的代码&#xff1a; class Solution {public int majorityElement(int[] nums) {int len nums.length/2;HashMap<Integ…

为生成式AI提速,亚马逊云科技Amazon EC2 P5满足GPU需求

生成式AI&#xff08;Generative AI&#xff09;已经成为全球范围内的一个重要趋势&#xff0c;得到越来越多企业和研究机构的关注和应用。纽约时间7月26日&#xff0c;亚马逊云科技数据库、数据分析和机器学习全球副总裁Swami Sivasubramanian在亚马逊云科技举办的纽约峰会上更…

剑指 Offer 15. 二进制中1的个数

题目描述 编写一个函数&#xff0c;输入是一个无符号整数&#xff08;以二进制串的形式&#xff09;&#xff0c;返回其二进制表达式中数字位数为 ‘1’ 的个数&#xff08;也被称为 汉明重量).&#xff09;。 提示&#xff1a; 请注意&#xff0c;在某些语言&#xff08;如…

电动汽车设计、制造、研发的学科、技术和前沿科技综述

引言&#xff1a;电动汽车作为替代传统燃油汽车的一种先进交通工具&#xff0c;不仅具有环保、低噪音等优势&#xff0c;而且对于能源消耗和气候变化等全球性问题也具有重要意义。本文将综述与电动汽车设计、制造、研发相关的学科、技术和前沿科技&#xff0c;以期对电动汽车领…

PHP8的程序结构-PHP8知识详解

在做任何事情之前&#xff0c;都需要遵循一定的规则。在PHP8中&#xff0c;程序能够安照人们的意愿执行程序&#xff0c;主要依靠程序的流程控制语句。 不管多复杂的程序&#xff0c;都是由这些基本的语句组成的。语句是构造程序的基本单位。程序执行的过程就是执行程序语句的…

你不了解的Dictionary和ConcurrentDictionary

最近在做项目时&#xff0c;多线程中使用Dictionary的全局变量时&#xff0c;发现数据并没有存入到Dictionary中&#xff0c;但是程序也没有报错&#xff0c;经过自己的一番排查&#xff0c;发现Dictionary为非线程安全类型&#xff0c;因此我感觉数据没有写进去的原因是多线程…

基于Python 简易实现接口测试自动化

目录 实现思路 统筹脚本 请求封装 日志封装 结果比对 结果邮件 用例获取及数据格式化 请求url转换 测试用例excel结构 测试报告 邮件接收结果 资料获取方法 实现思路 使用excel管理用例用例信息&#xff0c;requests模块发送http请求&#xff0c;实现了记录日志&…

使用vue模拟通讯录列表,对中文名拼音首字母提取并排序

一个功能需求,做一个类似联系人列表的功能,点击名称获取对应的id,样式简陋,只是一个模板,原来是uniapp项目,根据需要改成了vue,需要的自行设计css&#xff08;万是有一个mo的音&#xff09; 流程 获取数据提取首个字的拼音的首个字母排序并分组 上代码&#xff1a; <temp…

SpringBoot自动装配及run方法原理探究

自动装配 1、pom.xml spring-boot-dependencies&#xff1a;核心依赖在父工程中&#xff01;我们在写或者引入一些SpringBoot依赖的时候&#xff0c;不需要指定版本&#xff0c;就因为有这些版本仓库 1.1 其中它主要是依赖一个父工程&#xff0c;作用是管理项目的资源过滤及…

实验室如何选择适合的LIMS实验室管理系统

实验室信息管理系统(LIMS)是从20世纪70年代末开始发展起来的&#xff0c;距今在国外已发展40多年。国内发展历史约20多年&#xff0c;且前十几年国内市场上主要是国外进口的LIMS产品&#xff0c;存在价格高、产品重&#xff0c;实施周期长等水土不服的情况。近十年开始&#xf…

ctf中linux内核态的漏洞挖掘与利用系列(一)

说明 该系列文章主要是从ctf比赛入手&#xff0c;针对linux内核上的漏洞分析、挖掘与利用做讲解&#xff0c;本篇文章主要介绍内核漏洞利用所需的前置知识以及准备工作。 linux内核态与用户态的区别 以 Intel CPU 为例&#xff0c;按照权限级别划分&#xff0c;Intel把 CPU指…

深入浅出对话系统——检索式对话系统进阶方法

引言 本文介绍检索式对话系统进阶方法&#xff0c;主要介绍两篇论文工作。 Fine-grained Post-training for Improving Retrieval-based Dialogue Systems 这里的post-training是定义在pre-training和fine-turning中间的阶段&#xff0c;具体的思想是用一些特定领域的数据去…

Springboot中创建拦截器

目录 目的 实现过程 1、创建拦截器 2、注册拦截器 完整代码 目的 在Springboot项目中创建拦截器&#xff0c;在进入Controller层之前拦截请求&#xff0c;可对拦截到的请求内容做响应处理&#xff0c;如&#xff1a;校验请求参数、验证证书等操作&#xff1b; 实现过程 1、创…

vivado tcl创建工程和Git管理

一、Tcl工程创建 二、Git版本管理 对于创建完成的工程需要Git备份时&#xff0c;不需要上传完整几百或上G的工程&#xff0c;使用tcl指令创建脚本&#xff0c;并只将Tcl脚本上传&#xff0c;克隆时&#xff0c;只需要克隆tcl脚本&#xff0c;使用vivado导入新建工程即可。 优…