网站申请流程,公司网站建设实施方案,洛阳做天然气公司网站,族谱网站开发文章目录 一、质量保证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-lint1.2.1 安装1.2.2… 文章目录 一、质量保证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-lint1.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 mod2.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 语言测试框架可以让开发者很容易地进行单元测试但是需要遵循五点规则
含有单元测试代码的 go 文件必须以 _test.go 结尾Go 语言测试工具只认符合这个规则的文件。单元测试文件名 _test.go 前面的部分最好是被测试的函数所在的 go 文件的文件名比如以上示例中单元测试文件叫 main_test.go因为测试的 Fibonacci 函数在 main.go 文件里。单元测试的函数名必须以 Test 开头是可导出的、公开的函数。测试函数的签名必须接收一个指向 testing.T 类型的指针并且不能返回任何值。函数名最好是 Test 要测试的函数名比如例子中是 TestFibonacci表示测试的是 Fibonacci 这个函数。
单元测试的重点在于熟悉业务代码的逻辑、场景等以便尽可能地全面测试保障代码质量。
1.3 单元测试覆盖率
Go 语言提供了非常方便的命令来查看单元测试覆盖率。还是以 Fibonacci 函数的单元测试为例通过一行命令即可查看它的单元测试覆盖率。
➜ go test -v --coverprofileres.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 -htmlres.cover -ores.html命令运行后会在当前目录下生成一个 html 文件内容如下
红色标记的部分是没有测试到的绿色标记的部分是已经测试到的。这就是单元测试覆盖率报告的好处通过它可以很容易地检测自己写的单元测试是否完全覆盖。
2. 基准测试
2.1 定义
基准测试Benchmark是一项用于测量和评估软件性能指标的方法主要用于评估代码的性能。
2.2 Go 语言的基准测试
Go 语言的基准测试和单元测试规则基本一样只是测试函数的命名规则不一样。
Fibonacci 函数的基准测试代码如下
func BenchmarkFibonacci(b *testing.B){for i:0;ib.N;i{Fibonacci(10)}
}Go 语言基准测试和单元测试的不同点如下
基准测试函数必须以 Benchmark 开头必须是可导出的函数的签名必须接收一个指向 testing.B 类型的指针并且不能返回任何值最后的 for 循环很重要被测试的代码要放到循环里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. -benchtime3s .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 -htmlres.cover -ores.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 有以下几种安装方式
1Binaries在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.32Install from Source
因为 golangci-lint 本身就是 Go 语言编写的所以可以从源代码安装。
go install github.com/golangci/golangci-lint/cmd/golangci-lintv1.53.33brew安装仅限MacOS
brew install golangci-lint
brew upgrade golangci-lint安装完成后在终端输入如下命令检测是否安装成功。
➜ golangci-lint version
golangci-lint has version v1.53.3 built with go1.20.51.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 unexpectedly1.2.4 集成 golangci-lint 到 CI
代码检查一定要集成到 CI 流程中这样开发者提交代码的时候CI 就会自动检查代码及时发现问题并进行修正。
不管使用 Jenkins还是 Gitlab CI或者 Github Action都可以通过Makefile的方式运行 golangci-lint。可以在项目根目录下创建一个 Makefile 文件并添加如下代码
getdeps:mkdir -p ${GOPATH}/binwhich 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 $ checkGO111MODULEon ${GOPATH}/bin/golangci-lint cache cleanGO111MODULEon ${GOPATH}/bin/golangci-lint run --timeout5m --config ./.golangci.yml
verifiers: getdeps lint之后把如下命令添加到 CI 中了就可以自动安装 golangci-lint 并检查代码。
make verifiers2. 性能优化
性能优化的目的是让程序更好、更快地运行但是它不是必要的。所以在程序开始的时候不必刻意追求性能优化先大胆地写代码就好了写正确的代码是性能优化的前提。
是否进行性能优化取决于两点业务需求和自我驱动。所以不要刻意地去做性能优化尤其是不要提前做先保证代码正确并上线然后再根据业务需要决定是否进行优化以及花多少时间优化。自我驱动其实是一种编码能力的体现比如有经验的开发者在编码的时候潜意识地就避免了逃逸减少了内存拷贝在高并发的场景中设计了低延迟的架构。
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 优化技巧
几个优化的小技巧
尽可能避免逃逸因为栈内存效率更高还不用 GC。比如小对象的传参array 要比 slice 效果好如果避免不了逃逸还是在堆上分配了内存那么对于频繁的内存申请操作要学会重用内存比如使用 sync.Pool选用合适的算法达到高性能的目的比如空间换时间。 性能优化的时候要结合基准测试来验证自己的优化是否有提升。 除上述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 (fmtos
)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 GO111MODULEon
go env -w GOPROXYhttps://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 (fmtgithub.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 的内容。