[01编程语言 - go] 02 - go语言工程进阶 | 青训营笔记

30 阅读3分钟

这是我参与「第五届青训营」伴学笔记创作活动的第2天。今天的课程主题是go语言的进阶学习,主要包括3块内容:语言进阶、工程版本管理和测试。

1 语言进阶

go非常适用于高并发场景。

1.1 协程 Goroutine

并发:多个线程在1核cpu上运行

并行:多个线程在多核cpu上运行

协程属于用户态,比进程(内核态)更轻量级。

创建协程关键字:go

func ManyGo() {
    for i := 0; i < 5; i++ {
        go func(j int) {
            hello(j)
        }(i)
    }
    time.Sleep(time.Second)
}

1.2 CSP (communicating sequential processes)

Go提倡通过通信共享内存。相对而言,通过共享内存进行多线程访问对效率的影响更大。

通过通信的表现形式为通道,通道为单向所以不存在在通道内访问冲突的问题。

通道使用关键字 make 来创建。

func CalSquare() {
    src := make(chan int)
    dest := make(chan int, 3)
    go func() {
        defer close(src)
        for i := 0; i < 10; i++ {
            src <- i
        }
    }()
    go func() {
        defer close(dest)
        for i := range src {
            dest <- i * i
        }
    }()
    for i := range dest {
        println(i)
    }
}

1.3 Lock和WaitGroup

Sync包主要包含一些并发安全操作和协程间同步操作。

Lock即锁

func AddWithLock() {
    cnt = 0
    for i := 0; i < 5; i++ {
        lock.Lock()
        cnt += 1
        lock.Unlock()
    }
}

WaitGroup主要用于计数。主要包含3个方法:Add(delta)给计数器+delta,Done给计数器-1,Wait等待计数器到0。

func ManyGo() {
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(j int) {
            defer wg.Done()
            hello(j)
        }(i)
    }
    wg.Wait()
}

2 依赖管理

2.1 发展历程

GOPATH -> Go Vendor -> Go Module

GOPATH涉及环境变量定义,核心的3个文件夹为bin,pkg和src。所以依赖都在src下。

弊端:无法实现package的多版本控制

Go Vendor添加了Vendor文件夹,所以依赖包以副本形式存在其中。依赖的寻址先看Vendor再看GOPATH。解决了依赖冲突的问题。

弊端:无法标志依赖版本

Go Module通过go.mod文件管理依赖包版本(1.16版本开始自动开启)。通过go get/go mod指令工具管理依赖包。

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

2.2 依赖管理三要素

配置文件、描述依赖 -> go.mod

中心仓库管理依赖库 -> Proxy

本地工具 -> go get/mod

2.3 go.mod文件解读

第一部分module为依赖管理基本单元。第二部分指出go版本,即原生库,第三行require为单元依赖,包括路径和版本。

module github.com/Moonlight-Zhao/go-project-example

go 1.16

require (
    gopkg.in/gin-gonic/gin.v1 v1.3.0
    github.com/gin-contrib/sse v0.1.0 // indirect
    github.com/gin-gonic/gin v1.3.0 // indirect
}

依赖配置的版本规则:语义化版本和基于commit的伪版本。

语义化版本形式为 ${MAJOR}.${MINOR}.${PATCH}。

indirect关键字表示间接依赖,incompatible用于没有go.mod文件且主版本在v2及以上的依赖(可能存在不兼容情况)。

出现依赖同一个库不同版本时,会自动选择最低的兼容版本。

2.4 Proxy

Proxy用于缓存依赖,避免源码被修改等问题。(没有什么问题是1个proxy解决不了的,如果有,就2个)

变量GOPROXY是一个字符串,存储依赖的路径列表,direct表示源站。

2.5 工具 go get 和 go mod

go get example.org/pkg 后接各种命令。

go mod init 初始化,创建go.mod文件

go mod download 下载模块到本地缓存

go mod tidy 增加需要的依赖,删除不需要的依赖

3 测试

测试的3种主要类型:回归测试(手动)、集成测试(功能)、单元测试(测试覆盖率逐步递增,消耗逐步递减)

3.1 单元测试

3.1.1 单元测试的编写

测试文件以 "_test.go" 结尾。

测试方法以 "func TestXxx(t *testing.T)" 开头。

初始化逻辑放在 TestMain 方法中。

可以引用assert等包进行测试。

3.1.2 单元测试的评估与设计

单元测试的评估:代码覆盖率。可以使用 "go test XXX.go --cover" 查看代码覆盖率(行数比例)。

一般覆盖率50% - 60%,较高覆盖率为80%+。

测试的设计:测试分支相互独立、全面覆盖,测试单元粒度足够小,函数单一职责。

3.1.3 单元测试的依赖

测试的目标:幂等、稳定

3.2 Mock测试

常用测试包:monkey(用于给函数或方法打桩)

3.3 基础测试

主要用于测试代码性能。

4 总结

今天的内容是基于了解语法的扩展。第一部分语法进阶涉及的协程主要是代码深度的提高。第二部分工程项目的配置主要是代码量的增加和文件版本间的统一。第三部分测试是软件开发周期中的重要组成部分。