Go语言进阶 | 青训营笔记

74 阅读3分钟

这是我参与「第五届青训营」笔记创作活动的第2天。

一、本堂课重点知识

今天学习的主要内容是语言进阶、依赖管理、单元测试以及项目实战。

二、详细知识点介绍

1. 并发 VS 并行

这篇文章有很好的解释。

1.1 Goroutine

  • 协程:用户态,轻量级线程,栈KB级别。
  • 线程:内核态,线程跑多个协程,栈MB级别。

详细可以参考Golang协程和线程区别 - yuhaohao - 博客园 (cnblogs.com)

1.2 CSP (Communicating Sequential Processes)

更提倡通过通信共享内存而不是通过共享内存实现通信。 后者需要使用互斥量加锁,在一定程度上影响程序的性能。

1.3 Channel

make(chan 元素类型,[缓冲大小])

  • 无缓冲通道 make(chan int)
  • 有缓冲通道 make(chan int,2)

1.4 并发安全 Lock

不加锁限制会影响安全。

1.5 WaitGroup

计数器:开启协程+1;执行结束-1;朱携程阻塞1知道计数器为0。

  • func (wg *WaitGroup) Add(delta int)
  • func (wg *WaitGroup) Done()
  • func (wg *WaitGroup) Wait()

Go包Sync文档

2 依赖管理

2.1 背景

  • 工程项目不可能基于标准库0~1编码搭建
  • 管理依赖库

2.2 Go 依赖管理演进

GOPATH \Rightarrow Go Vendor \Rightarrow Go Module

  • 不同环境(项目)依赖的版本不同
  • 控制依赖库的版本

2.2.1 GOPATH

  • 环境变量 $GOPATH
    • bin 项目编译的二进制文件
    • pkg 项目编译的中间产物,加速编译
    • src 项目源码
  • 项目代码直接依赖src下的代码
  • go get下载最新版本的包到src目录下
  • 弊端
    • 场景:A和B依赖于某一package的不同版本。
    • 问题:无法实现package的多版本控制。

2.2.2 Go Vendor

通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题。

  • 项目目录下增加vendor文件,所有依赖包副本形式放在$ProjectRoot/vendor
  • 依赖寻址方式:vendor \Rightarrow GOPATH
  • 弊端
    • 场景:同属Project A的Package B和Package C同时依赖于同一个Package D的不同版本。
    • 问题:
      • 无法控制依赖的版本。
      • 更新项目又可能出现依赖冲突,导致编译出错。

2.2.3 Go Module

  • 通过go.mod文件管理依赖包版本
  • 通过go get/go mod指令工具管理依赖包

终极目标:定义版本规则和管理项目依赖关系

2.3 依赖管理三要素

  1. 配置文件,描述依赖 go.mod
  2. 中心仓库管理依赖库 Proxy
  3. 本地工具 go get/mod

2.3.1 依赖配置-go.mod

由依赖管理基本单元、原生库、单元依赖三部分组成。

2.3.2 依赖配置-version

  • 语义化版本
    • ${MAJOR}.${MINOR}.${PATCH}
    • V1.3.0 V2.3.0
  • 基于commit伪版本
    • vx.0.0-yymmddhhmmss-12位哈希校验码

2.3.3 依赖配置-indirect

A \Rightarrow B \Rightarrow C

  • A \Rightarrow B 直接依赖
  • A \Rightarrow C 间接依赖

2.3.4 依赖配置-incompatible

  • 主版本2+模块会在模块路径增加/vN后缀。
  • 对于没有go.mod文件并且主版本2+的依赖,会+incompatible

2.3.5 依赖配置-Proxy

2.3.6 依赖分发-变量GOPROXY

2.3.7 工具-go get

go get example.org/pkg

  • @update 默认
  • @none 删除依赖
  • @v1.1.2 tag版本,语义版本
  • @23dfdd5 特定的commit
  • @master 分支的最新commit

2.3.8 工具-go mod

go mod

  • init 初始化,创建go.mod文件
  • download 下载模块到本地缓存
  • tidy 增加需要的依赖,删除不需要的依赖

3 测试

回归测试 \Rightarrow 集成测试 \Rightarrow 单元测试 从左至右,覆盖率逐层变大,成本却逐层降低。

3.1 单元测试

对期望与输入经由测试单元得出的输出进行校对,可以保证质量以及提升效率。

3.1.1 单元测试-规则

  • 所有测试文件以_test.go结尾
  • func TestXxx(*testing.T)
  • 初始化逻辑放到TestMain中

3.1.2 单元测试-运行

go test [flags] [packages]

3.1.3 单元测试-assert

3.1.4 单元测试-覆盖率

  • 如何衡量代码是否经过了足够的测试?
  • 如何评价项目的测试水平?
  • 如何评估项目是否达到了高水准的测试等级?

运行时在命令最后添加--cover可以得到测试的覆盖率。

Tips

  • 一般覆盖率:50%~60%,较高覆盖率80%+。
  • 测试分支相互独立、全面覆盖。
  • 测试单元粒度足够小,函数单一职责。

3.2 单元测试-依赖

外部依赖 \Rightarrow 稳定&幂等

3.3 单元测试-文件处理

3.4 单元测试-Mock

GitHub - bouk/monkey: Monkey patching in Go 快速Mock函数

  • 为一个函数打桩
  • 为一个方法打桩

可以实现测试不依赖于本地文件。

3.5 基准测试

  • 优化代码,需要对当前代码分析
  • 内置的测试框架提供了基准测试的能力

三、实践练习例子

需求

  1. 实现一个展示话题(标题,文字描述)和回帖列表的后端http接口;
  2. 本地文件存储数据

要求

  1. 支持对话题发布回帖。
  2. 回帖id生成需要保证不重复、唯一性。
  3. 新加回帖追加到本地文件,同时需要更新索引,注意Map的并发安全问题。

四、课后个人总结

这节课更多的内容是关于编程及项目方面的思想和方法,Go语言不同于其它语言的新知识,以及部分的操作系统相关知识。本篇文章目前主要内容还是随堂记录,后续会添加更多相关学习内容。

五、引用参考

并行与并发

Golang协程和线程区别 - yuhaohao - 博客园 (cnblogs.com)