Go语言工程实践| 青训营笔记

81 阅读3分钟

语言进阶

  • 并发与并行
    • 并发是多个线程在一个CPU核上运行
    • 并行是多个线程在多个CPU核上运行
  • Goroutine
    • 线程
      • 内核态,栈内存是MB级别
    • 协程
      • 用户态,栈内存是KB级别
      • go语言一次可以创建上万数量的协程
    • 怎样开启?在调用函数时,前面加go关键字
  • CSP (Commnuicating Sequential Processes)
    • 提倡通过通信共享内存,而不是通过共享内存来实现通信
  • Channel
    • 创建

      make(chan 元素类型[, 缓冲大小])
      // 无缓冲通道
      make(chan int)
      // 有缓冲通道
      make(chan int, 2)
      
    • 无缓冲通道和有缓冲通道的区别

      • 无缓冲通道发送和调用会是同步的,也被称为同步通道
  • 并发安全Lock
    • lock sync.Mutex 声明一个锁 通过临界区来实现的
      • lock.Lock() 加锁
      • lock.Unlock() 释放锁
  • sync.WaitGroup(有点类似于Java的Barrier)
    • Add(delta int) 计数器+delta
    • Wait() 阻塞直到计算器为0
    • Done() 计数器-1
    • 用WaitGroup实现一个计数器
      1. 创建WaitGroup
      2. WaitGroup调用 Add(1)
      3. 在需要阻塞的地方调用Wait()
      4. 在完成任务后调用WaitGroup的Done()

依赖管理

  • 为什么要有依赖管理
    • 工程不可能只基于标准库从零开始编写搭建
    • 可以使用优秀的第三方Go包
  • Go依赖管理的演进
    1. GOPATH
    2. Go Vendor
    3. Go Module
  • 依赖关系
    • 不同环境(项目)的版本不同
    • 控制依赖库的版本
  • GOPATH
    • bin 项目编译的二进制文件
    • pkg 项目编译的中间产物,加速编译
    • src 项目源码
    • 代码直接依赖src下的代码
      • 无法实现多版本的依赖控制
    • go get会把包下载到src目录下
  • GoVendor
    • 在项目目录下生成了一个vendor文件
    • 所有的依赖都以副本的形式放在$ProjectRoot/vendor
      • 解决了多版本的依赖控制问题
    • 依赖的寻址方式:先vendor 后GOPATH
    • 同项目中无法解决不兼容的版本依赖
  • Go Module
    • 新版本默认开启,目前最推荐的依赖管理方式
    • 通过go.mod文件管理依赖包
    • 通过go get/go mod指定工具管理依赖包
    • 依赖管理三要素
        1. 配置文件,描述依赖 go.mod
        1. 中心仓库管理依赖库 Proxy
        1. 本地工具 go get/mod
    • 依赖标识 [Moudule Path][Version/Pseudo-version]
    • go.mod格式
      • module xxx/xxx/xxx 声明模块,依赖管理的基本单元

      • go 1.16 声明标准库的版本

      • require

        require(
            xxx/xxx v1.1.2
            xxx/xxx v1.0.0 // indirect
            xxx/xxx v3.3+incompatible
        )
        
      • 版本号

        • 语义化版本 ${MAJOR}.${MINOR}.${PATCH}
          • MAJOR大版本,不兼容
          • MINOR小版本,兼容
          • PATCH问题修复
        • 基于commit的伪版本 v0.0.0-yyyyymmddhhmmss-commitid
      • indirect 声明间接依赖

      • incompatible 标识可能出现不兼容的依赖

    • 间接依赖不同的版本时,会选择最低兼容版本
    • 依赖分发
      • 使用源代码依赖会产生问题
        • 无法保证构建的稳定性
        • 无法保证依赖可用性
        • 增加第三方压力
      • Proxy因此而产生
        • 稳定,可靠
        • 依赖都在Proxy中拉取
      • 如何配置
        • 通过GOPROXY环境变量
        • 是一个用,分割的URL列表,最后一个是direct表示源站
    • go get go get example.org/pkg
      • @update 默认
      • @none 删除依赖
      • @v1.1.2 指定tab版本
      • @commitid 指定特定的commitid
      • @master 指定分支,拉取该分支最新的commit
    • go mod
      • init 初始化,创建go.mod文件
      • download 下载依赖模块到本地缓存
      • tidy 增加需要的依赖,删除不需要的依赖,自动化整理依赖

测试

测试是避免事故的最后一首屏障

  • 测试的划分
    • 回归测试 手动回归一些固定的使用场景
    • 集成测试 系统功能维度做验证
    • 单元测试 开发对开发的模块做功能验证
  • 单元测试
    • 组成:输入、输出、输出期望值
    • 保证质量
    • 提升整体效率,缩短定位问题+修改的周期
    • 规则
      • 文件以_test.go结尾
      • 函数签名为func Testxxxx(t *testing.T)
      • 初始化逻辑写在func TestMain(m *testing.M)
        • 前置初始化
        • code := m.Run()
        • 后置收尾
    • 执行测试
      • go test [flag] [packages]
    • 可以引用一些assert包
    • 单元测试的评估
      • 代码覆盖率
        • go test xxxx.go xxx.go ... --cover 输出代码覆盖率
        • 实际项目中一般要求:50%~60%,要求严格的场景下可以80%
      • 如何提升覆盖率
        • 测试分支相互独立,全面覆盖
        • 测试单元粒度足够小,函数单一职责
  • Mock测试
    • monkey

      • https://github.com/bouk/monkey
    • 打桩,测试时用一个函数替换另一个函数

      monkey.Patch(funcName, func()string{})
      defer monkey.Unpatch(funcName)
      
      • 可以解除对本地文件的依赖
  • 基准测试
    • 需求:优化代码,需要对当前代码分析

    • 和单元测试的规则是一致的

    • 同步写法

      func Benchmarkxxxxx(b *testing.B){
          b.ResetTimer()
      }
      
    • 异步写法

      func BenchmarkxxxxParallel(b *testing.B){
          b.ResetTimer()
          b.RunParallel(func(pb *testing.PB){
              xxx
          })
      }
      
    • 执行基准测试

      • go test -bench=.