Go工程进阶 | 青训营笔记

78 阅读4分钟

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

本节课主要学习的是Go语言的工程实践内容

分为四个部分:

  • 语言进阶
  • 依赖管理
  • 测试
  • 项目实战

1.语言进阶

1.1 并发设计

并发与并行的区别:

并发是多线程程序在一个核的CPU上运行

image.png

并行是多线程程序在多个核的CPU上运行

image.png

Go语言可以充分发挥多核优势,实现高效运行

1.2 Goroutine

概念:协程

协程:用户态,属于轻量级线程,栈级别MB

对比线程:线程是内核态,一个线程跑多个协程,栈级别KB

怎么开启协程(一个简单的例子):

import (
        "fmt"
        "time"
)
func hello(i int) {
    println("hello goroutine:" + fmt.Sprint(i))
}
    
func helloroutine() {
    for i := 0; i < 5; i++ {
	go func(j int) {
		hello(j)
	}(i)
    }
    time.Sleep(time.Second)
}

CSP(Communicating Sequential Processes):

Go中提倡通过通信共享内存,而不是通过共享内存而实现通信

image.png

Channel

通道是一种引用类型 需要用make进行创建 make(chan 元素类型,[缓冲大小])

无缓冲通道 示例:make(chan int)

有缓存通道 示例: make(chan int,2)

通道容量代表通道能存储多少个数。

package main
func main() {
    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)
    }
}

Sync包:

并发安全Lock

并发不加锁,在对共享内存进行操作时可能会出现问题

WaitGroup:

WaitGroup中暴露了三个方法,Add() \ Done() \ Wait()

计数器 开启协程+1,执行结束-1;主协程阻塞直至计数器为0。

2.依赖管理

Go依赖管理的演进

GOPATH->Go Vender->Go Module

不同环境依赖的版本不同;

控制依赖库的版本;

2.1.1 GOPATH

GOPATH项目代码都依赖于src下的代码,因此存在一个问题:

当后续版本更新后,前面版本的代码可能无法创建成功,即无法实现package的多版本兼容。

2.1.2 GO Vendor

在项目目录下增加Vendor文件,所有依赖包副本都放在vendor文件

在查询依赖时首先去vendor中查找,没有找到再查找gopath路径,这样就解决了多个项目需要同一个package依赖的问题。

Vendor的弊端:

Vendor无法控制以来的版本,当更新项目后可能出现依赖冲突,从而导致编译出错。

2.1.3 GO Module

通过go.mod文件管理依赖包版本

通过go get/go mod指令工具管理依赖包

2.2 依赖管理三要素
  • 配置文件,描述依赖 go.mod

  • 中心仓库管理依赖 proxy

  • 本地工具 go get/mod

2.3 依赖配置

配置Go.mod

image.png

依赖配置 -version

语义化版本:

MAJOR.{MAJOR}.{MINOR}.${PATCH}

V1.3.0

基于commit伪版本:

Vx.0.0-yyyymmddhhmmss-abcdefgh1234

例子:

V0.0.0-20220401081311-c38fb59326b7

上述的indirect关键字:对于没有直接导入的依赖,用Indirect关键字标识。

Incompatible

对于没有go.mod文件且主版本在V2以上的依赖,会加Incompatible后缀。

依赖分发-回源

依赖可以去哪里下载:github、SVN等

问题:

  • 无法保证构建稳定性;
  • 无法保证依赖可用性;
  • 增加第三方压力;

Proxy:

在Github、SVN后加一层Proxy,可以解决上述问题。

变量GOPROXY

GOPROXY=PROXY1,PROXY2,Direct

工具 Go get

Go get

  1. @update 最新版本
  2. @none 删除
  3. @v1.2.2 指定版本
  4. @34dfdd5 特定commit
  5. @master 分支最新的commit

工具 Go mod

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

Download 下载模块到本地缓存

Tidy 增加需要的依赖,删除不需要的依赖

3.go 测试内容

开发->测试->上线

测试分为三种:回归测试、集成测试、单元测试

从上到下,覆盖率逐层变大,成本逐层降低。

单元测试覆盖率一定程度决定了代码的质量。

输入->测试单元->输出,利用输出与期望值进行校对,来实现验证。

意义:通过单元测试可以保证质量同时提升效率。

  • 单元测试-规则:

    所有测试文件都已_test.go结尾

    命名 func Testxxx(*tesing.T)

    初始化逻辑都放到TestMain中

  • 代码覆盖率:利用代码覆盖率来评估代码的测试等级。

    对各代码分支进行测试。

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

    测试分支相对独立、全面覆盖。

    测试单元粒度要求足够小,函数单一职责。

  • 单元测试-依赖

    外部依赖 => 稳定且幂等

  • 单元测试-Mock

    意义:不依赖本地文件,在任何场景下都能实现测试函数。

  • 基准测试:

    命名 Benchmark(b *tesing.B)

    基准测试支持并行

    基准测试的优化 :fastrand(),提升性能,但是牺牲了随机数列一致性。

总结

在本节课中我主要学习了Golang语言的进阶语法知识,以及学习了Go中的并发处理方式和测试相关内容,包括单元测试以及基准测试的测试方法,同时在课上老师还带着我们进行了一个简单项目的设计。课后我会将老师上课讲的代码实际运行一下,同时完成上课时留下的课后练习,然后预习下节课高质量编程的相关知识。