Go进阶依赖与工程实践 | 青训营笔记

53 阅读3分钟

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

1 并发与并行

1、Goroutine

进程&协程

go关键字:为一个函数开启协程

time.Sleep():保证子协程在执行完之前主线程退出

func hello(i int) {
   //fmt.Printf("hello goroutine: %v", i)
   println("hello goroutine:" + fmt.Sprint(i))
}

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

2、CSP

提倡:通过通信共享内存

共享内存:通过互斥量对内存进行加锁,获取临界区的权限

3、Channel

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

  • 无缓冲通道:make(chan int) ——发送与接收同步
  • 有缓冲通道:make(chan int, 2)
image-20230116145845201
func CalSquare() {
	src := make(chan int)
	dest := make(chan int, 3)
	// 子协程发送0-9数字
	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)
	}
}

4、并发安全 Lock

5、WaitGroup

Add(delta int) ——计数器+delta

Done() ——计数器-1

Wait()——

2 依赖管理

1、Go依赖管理演进

GOPATH => Go Vender => Go Module

1、GOPATH

  • bin:项目编译的二进制文件
  • pck:项目编译的中间产物,加速编译
  • src:项目源码

项目代码直接以来src下的代码

缺点:无法实现package的多版本控制

2、Go Vendor

项目目录下增加vender文件,所有依赖包以副本形式放在$ProjectRoot/vender

依赖寻址方式:vender=>GOPATH

解决了多个项目需要同一个package依赖的冲突问题

缺点:无法控制依赖的版本;更新项目有可能出现依赖冲突,导致编译出错

3、Go Module

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

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

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

2、依赖管理三要素:

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

1、依赖配置-go.mod

module example/project/app  // 依赖管理基本单元
go 1.16 // 原生库
require (
	example/lib1 v1.0.2 // 单元依赖
	......
)

2、依赖配置-version

  • 语义化版本

    ${MARJOR}.{MINOR}.${PATCH}

    eg.V1.3.0

major大版本

minor新增函数或功能

patch代码块的修复

  • 基于commit位版本

版本号-时间戳-12位哈希码前缀

vX.0.0.0-yyyymmddhhmmss-abcdefgh1234

3、依赖配置

indirect:间接依赖

incompatible:按照不同的模块来处理同一个项目不同版本的依赖

4、依赖图

X项目依赖了A、B两个项目,且A、B分别依赖了C项目的v1.3、v1.4,最终编译时所使用的C项目的版本为v1.4

选择最低的兼容版本

5、依赖分发-回源

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

6、Proxy实现稳定、可靠的依赖

配置:GOPROXY="https://proxy1.cn,https://proxy2.cn.direct"

服务站点URL列表,"direct"表示源站

Proxy1 => Proxy2 => Direct

7、工具-go get

go get example.org/pkg@update默认

@none-删除依赖

@v1.1.2-tag版本,语义版本

@master-分支最新commit

8、工具-go mod

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

download - 下载模块到本地缓存

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

3 测试

回归测试、集成测试、单元测试

1、单元测试

  • 所有测试文件以_test.go结尾

    publist_post_test.go

  • func TestXxx(*testing.T)

    func TestPublishPost(t *testing.T) {}

  • 初始化逻辑放到TestMain中

    func TestMain(m *testing.M) {
        // 测试前:数据装载、配置初始化等前置工作
        code := m.Run()
        // 测试后:释放资源等收尾工作
    }
    

代码覆盖率 :衡量代码是否经过了足够的测试

一般覆盖率:50%~60%,较高覆盖率80%+

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

测试单元粒度足够小,函数单一指责

外部依赖 => 稳定&幂等

稳定:相互隔离,能在任何时间、环境运行测试

幂等:每次运行都与前一次产生一样的结果

2、Mock测试

开源mock的测试库monkey,可以实现对实例的方法进行mock

为函数打桩

3、基准测试:测试一段程序的运行性能及耗费CPU的程度

优化代码,需要对当前代码分析

内置的测试框架提供了基准测试的能力

4 项目实践

社区话题:话题详情、或铁列表、支持回帖、点赞、回帖回复

1、需求描述:社区话题页面

  • 展示话题(标题,文字描述)和回帖列表
  • 仅实现一个本地web服务
  • 话题和回帖数据用文件存储

2、需求用力

User:Topic,PostList

3、ER图

4、分层结构

  • repository数据层:数据Model,外部数据的增删改查
  • service逻辑层:业务Entity,处理核心业务逻辑输出
  • controller视图层:视图view,处理和外部的交互逻辑

5、组件工具

  • Gin高性能go web框架

  • Go Mod

    go mod init

    go

    get