依赖管理
Golang 依赖管理机制发展到现在,主要经历了 GOPATH、govendor、go mod三个时期。而 go mod 发展到现在,已经能够较好地解决依赖管理中的问题了。go mod 的核心原理主要包含三部分内容:
- 版本的两种表达方式:semver (Semantic Versioning) 语义化版本、基于某一 commit 的伪版本号;
- 管理 go.mod 的两个重要工具:go get & go mod;
- 版本选择算法:Minimal Version Selection(MVS)。
- 对所有依赖选择最高的一个版本
gomod的二种表达方式
- 语义化版本 (semver) v${major}.${minor}.${patch} major 不同则认为是不同的两个仓库。
- 基于某一个 commit 的 伪版本号 没有用 go mod 管理的仓库 基本前缀+时间+commit 前 12 位
管理gomod的2个工具
- go mod tidy : 添加或者删除项目所需依赖
- go get github/xxx/xxxx
@upgrade | 默认行为若当前是 semver,更新到最新的 semver;若当前是伪版本,更新到最新的minor version(若有)或者当前base version 的最新commit 的 |
@latest | 修改依赖至最新的minor version ,可能会降低版本(例如可能将v0.1.1-0.20240512124444-bcab2ddalyyy 修改为0.1.0 |
@patch | 更新到最新的 patch 版本 |
@none | 删除依赖 |
@v1.2.3 | semver |
@b23abcd | 拉取特定 commit,至少 7 位 |
@master | master 分支的最新 commit |
- 若 major 版本>1 ,go get 需要加版本后缀: go get github.com/pkg/xxx/v2
- -u (=patch) 同时更新所有依赖中所参与编译的依赖到 minor(patch)版本
- 若 go get 的 目标 package 为 main ,会下载安装二进制到$GOPATH/bin (go < 1.18)
版本选择算法
如果 x 项目依赖了 a,b 依赖,a,b依赖 了 c 项目的v1.1.2,v1.2.3,最终编译所使用的 c 项目版本是 v1.2.3
非理想状态
2 个标记 // indirect 标记 非本项目直接依赖,但在本项目指定该依赖的版本 可能原因:
- 依赖项没有使用 gomod
- 不是所有依赖都在 gomod中
- 手动为依赖项指定较新的版本 +incompatible 该依赖未使用 go mod 管理
- 不影响使用, 但该依赖的依赖未显示指定版本
- 不再认同major 不同为不同项目
常用工具和方法
查询所有依赖项之间的依赖关系 go mod graph 获取参与编译的依赖项代码 go mod vendor
临时修改依赖库中的代码测试
- clone仓库到本地&checkout 对应版本&replace
- replace githubcom/foo/bar =>../../../github.com/for/bar 批量更新依赖
- go get github.com/…
仅安装/运行二进制但不更新当前项目的依赖
- go install example/cmd@latest(go >=1.16)
- go run example.com/cmd@latest (go >=1.17)
- go 1.18后 go get 将只用于更新依赖不安装二进制。
使用建议
- 如果项目庞大,go.mod 臃肿,可以进行拆库,或者对子目录采用submodule(go kratos框架)
- 库维护者,如何优雅地告知使用者某个版本存在 bug
- retract标记 (go >=1.16)
- 使用go mod 后已可以不将工程放在$GOPATH下,如何组织庞大的工程
- 根据实践经验,强烈推荐依然使用$gopath按路径管理所有依赖项,why?
- 各工程的组织结构清晰
- 可以从工程的相对路径获取 go mod
- 根据实践经验,强烈推荐依然使用$gopath按路径管理所有依赖项,why?
经典案例分析
-
指定 go get -u github.com/xxx/xxxx 后,项目编译不通过
-
-u 参数会更新依赖的依赖,可能导致不兼容,如无必须不加-u 参数
-
为什么一些工程不使用go mod ? require ( … github.com/form3tech-oss/jwt-go v3.2.5+incompatible … )
-
在 go mod 推广之前就已经 major 大于 1 了
-
便于 go get 直接拉到 v3 版本。
-
无论依赖库是否使用 go mod ,我们的项目还是使用go mod 特殊情况,可以手动指定indirect依赖
-
惨遭删除 tag,commit,branch 如何解决:?
- 如果只是本项目依赖,删除go.mod 的条目,然后go get 更新到最新的 tag
- 若项目依赖项也依赖,replace 该依赖至正常的 tag ;彻底解决需要go get 依赖链路上的所有项目到最新的 tag。
- 更可怕的是删tag 后,在其他 commit 重新打了相同的 tag。 得删除本地缓存,重新拉取。
- 循环依赖陷进 package 之间是不能循环依赖的,编译就报错, 但是不同项目模块是可能产生循环依赖的。 公共库之间已经明确分工(不同的团队在维护的) ,避免大杂烩,产生循环依赖.
代码规范
这里只举例很小的一部分
- 包名 ConcurrentFeture (java风格)、concurrent_feture(python风格)、concurrentfeture(go 推荐用这个方式)
- 包名尽量不用复数,也尽可能简洁 。
- 包名和函数名避免重复出现
- 变量命名: 小驼峰、大驼峰(公共变量).
- 库升级
- 如superdb从 1.1升级到2.0需要如何操作?
- 新建目录 v2
- 将原来的代码都移动到v2下,包括 go mod 文件,并加上新的修改。
- v2 目录的 go mod ,modulepath 必须加上对应的版本后缀例如 github.com/foo/bar/v2 。
- 打v2的 tag
Uber Go 语言编码规范
https://github.com/xxjwxc/uber_go_guide_cn
link 实践
官方 lint :
- gofmt: 代码整理,给结尾加空等
- govet : 检测程序的正确性
- Http body 是否被正确的 close
- 错误,先 defer,应该先检测err, 这种可能造成程序的 panic。 resp, err := http.Get(…) defer resp.Body.Close() if err != nil { return err }
- Http body 是否被正确的 close
- 原子操作的误用 var x uint64 x = atomic.AddUint64(&x,1) //错误用法 ,此并发原子的
// 正确用法 var x uint64 atomic.AddUint64(&x,1) //错误用法 ,此并发原子的
- 检查 unsafe.Pointer 的正确错误
- 等
- golint 已经废弃 。
- false positives , 提示的问题不一定正确(就是对的也说错的)
- 不同的功能分散在不同的工具,不够便捷。没有统一的配置。
- 很多场景没有满足
golangci-lint
golangci-lint 是社区基于 Go team 的 https://github.com/golang/tools/tree/master/go/analysis 开发的 linter 工具(go vet 也是基于此开发的)
- 解决了false positives问题 //onlint 用了屏蔽检测
- 统一的配置文件,解决不同工具的配置文件问题
- 大大加速,只解析一次ast 树
- 可插拔linter配置,自由选配linters,自身携带大多的 linter
团队实践
- 公司使用某个版本固定的 golangci-lint
- 开发团队统一 golangci-lint 配置(也可以继承公司的基础配置)
参考配置
1run:
2 timeout: 30m
3 go: "1.21"
4 checks:
5 - "all"
6 - "-SA1019"
7 goimports:
8 # 设置哪些包放在第三方包后面,可以设置多个包,逗号隔开
9 local-prefixes: rmq
10issues:
11 exclude-files:
12 - _test.go
13 exclude-dirs:
14 - examples
15 - doc
16
17
18linters:
19 disable-all: true
20 enable:
21# - unused
22 - ineffassign
23# - goimports
24 - gofmt
25 - misspell
26 - unparam
27 - unconvert
28 - govet
29# - errcheck
30 - staticcheck
单元测试
尽早的使用单元测试。
准备、调用、断言, table 测试
不盲目追求覆盖率,只对核心需要进行测试即可
常用table测试
1package yourpackage
2
3import (
4 "testing"
5 "github.com/stretchr/testify/assert"
6)
7
8func Add(a, b int) int {
9 return a + b
10}
11
12func TestAdd(t *testing.T) {
13 testCases := []struct {
14 a int
15 b int
16 expected int
17 }{
18 {a: 1, b: 1, expected: 2},
19 {a: 2, b: 3, expected: 5},
20 {a: 0, b: 0, expected: 0},
21 // 可以添加更多的测试用例
22 }
23
24 for _, tc := range testCases {
25 name := fmt.Sprintf("%d+%d", tc.a, tc.b)
26 t.Run(name, func(t *testing.T) {
27 actual := Add(tc.a, tc.b)
28 assert.Equal(t, tc.expected, actual)
29 })
30 }
31}
mock,断言框架选择
场景 | 项目 | 适用场景 | 地址 |
---|---|---|---|
mock | gomock | 面相接口编程,把接口给 mock 调 | github.com/golang/mock 现在被https://github.com/uber-go/mock 代替 |
mock | monkey | 适合非接口定义,如把一个 函数调用了 mysql 的结果值给 mock 调 | github.com/agiledragon/gomonkey |
断言 | testify | tdd 驱动,有 mock 功能 | github.com/stretchr/testify |
等 |
go test常见用法
go test
1可选参数
2-v 详细结果
3-conver 输出覆盖率
4-run 指定某一个测试用例
5其他如benchmark测试等
看单测哪行没覆盖
- go test ./… -coverprofile=cover.out
- go tool cover -html=cover.out
如果编写单元测试,可以看另外一篇文章:https://www.crblog.cc/go/go-unit-test.html
程序分析
pprof 详细见 https://www.crblog.cc/go/go-pprof.html
线上都开,好排查问题。
三方性能分析工具
statsviz
https://github.com/arl/statsviz, 如果需要监控可视化,可以看这个项目。可以在浏览器上将内存,cpu,goroutine 数等可视化出来。
pyroscope
pyroscope 是 go+node开发的一款可视化性能分析工具,支持很多语言进行性能分析。 https://github.com/grafana/pyroscope
errors 处理
基本准则
- 业务代码处,禁止手动写panic,然捕获,panic并非php中的exception。
- error只处理一次, 在最上层进行处理,如打印 logger,请别到处打印错误日志,重复的日志反而让问题更难排查。
- 想要错误调用链的的堆栈,请使用包装错误包裹下,再返回。
- 任何错误,可以跟错误码关联,没有关联,则表示是内部错误,给默认的错误 code 。
- 异常程序无法控制的错误,需要 recover 中间件进行处理错误,记录 logger。
错误包地址: https://github.com/cr-mao/errors
Links
- go mod https://go.dev/ref/mod#introduction
- go get 工具 https://pkg.go.dev/cmd/go/internal/modget@go1.15.11
- review建议 https://go.dev/wiki/CodeReviewComments
- Effective Go https://go.dev/doc/effective_go
- go blog https://go.dev/blog/
- 库升级 v2 https://go.dev/blog/v2-go-modules
- gofmt: https://pkg.go.dev/cmd/gofmt
- go vet: https://pkg.go.dev/cmd/vet
- golint(deprecated): https://github.com/golang/lint
- golang/analysis: https://github.com/golang/tools/tree/master/go/analysis
- golangci-lint: https://github.com/golangci/golangci-lint
- go单元测试 https://www.crblog.cc/go/go-unit-test.html