Go语言入门 | 青训营笔记

154 阅读6分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记

channel

golang通过通信共享内存。

func main() {
   src := make(chan int)
   dest := make(chan int, 3)
    
    //暂且称为协程A
   go func() {
      defer close(src)
      for i := 0; i < 10; i++ {
         src <- i
      }
   }()

    //暂且称为协程B
   go func() {
      defer close(dest)
      for i := range src {
         dest <- i * i
      }
   }()

   for i := range dest {
      fmt.Println(i)
   }
}

执行的结果:

0
1
4
9
16
25
36
49
64
81

主协程main中开启了两个协程AB,两个协程之间的通信使用的是无缓冲区的channel,是同步通信的,A往src放入一个元素,阻塞,等待B从src中取出元素。AB是相互阻塞的。 B从src中取出元素,处理之后将处理后的结果放入dest中,如果这个时候dest是满的,B就会阻塞,同理,如果缓冲区是空的,主协程就会阻塞等待数据。

Sync.WaitGroup

  • Add(): 计数器+delta
  • Done():计数器-1
  • Wait(): 主协程阻塞直到计算器为0

依赖管理

GOPATH -> GO Vender -> Go Module

GOPATH

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

项目代码直接依赖src下的代码,go get下载的所有依赖都存放到src目录下面。

存在的弊端:

  • 无法实现包的多版本控制

    GOPATH下面存放所有的依赖包,如果项目A和项目B依赖于不同版本的package,同时该package又没有做到完全的向前兼容,往往会导致一些问题。

Go Vender

image.png

其实就是在每个项目增加一个vendor文件夹,所有依赖副本存放在这里。

寻址方式:首先去vendor下面寻找,没有的话去GOPATH

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

但是,还是无法控制依赖的版本,更新项目可能出现以来冲突,导致编译出错

packageB和packageD同时依赖packageD,但是依赖的是不同的版本,这样就会出现问题。

image.png

以上两种依赖的管理方式,归根结底,都是依赖于源码,不能清晰的标识依赖的版本,就会出现问题

Go Module

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

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

依赖管理三要素:

  • 配置文件,描述依赖 go.mod

  • 中心仓库管理依赖库 Proxy

  • 本地工具 go get/mod

image.png

依赖标识: [Module Path][Version/Pseudo-version]

版本version

image.png

版本语义-时间戳(提交的时间戳)-哈希的编码

indirect

image.png

A->B->C

  • A与B是直接依赖
  • A与C是间接依赖

在执行命令go mod tidy时,Go module 会自动整理go.mod 文件,如果有必要会在部分依赖包的后面增加// indirect注释。一般而言,被添加注释的包肯定是间接依赖的包,而没有添加// indirect注释的包则是直接依赖的包,即明确的出现在某个import语句中。

然而,这里需要着重强调的是:并不是所有的间接依赖都会出现在 go.mod文件中。

间接依赖出现在go.mod文件的情况,可能符合下面所列场景的一种或多种:

  • 直接依赖未启用 Go module
  • 直接依赖go.mod 文件中缺失部分依赖

具体解释参考该链接:【Go 专家编程】go.mod 文件中的indirect准确含义_神以灵的博客-CSDN博客_go indirect

incompatible

引入不兼容的包。

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

【Go 专家编程】go.mod 文件中incompatible包意味着什么 - 恋恋美食的个人空间 - OSCHINA - 中文开源技术交流社区

image.png

C1.3和C1.4同属于V1版本,会选择最低的兼容版本

依赖分发-回源

image.png

go.mod中的依赖其实能对应到代码托管仓库的某个项目的提交,但是如果直接从这里下载容易造成一些问题。

  • 无法保证构建的稳定性

    删除/修改/删除软件版本

  • 无法保证依赖可用性

    删除软件

  • 增加第三方压力

    代码托管平台负载问题

image.png

变量-GOPROXY

GOPROXY="proxy1.cn, proxy2.cn, direct"

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

image.png

go get

image.png

go mod

image.png

测试

单元测试

image.png

规则

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

image.png

  • func TestXxx(*testing.T)

image.png

  • func 初始化逻辑放到了TestMain中

image.png

举例:

image.png

assert包

image.png

怎么评估项目的测试水准呢?

通过代码覆盖率!

image.png

执行的命令上加上--cover参数,就可以输出代码的覆盖率值。

这个66.7%是怎么计算的呢?核心代码只有3行,前两行(score>=60 && return true)在这次的单元测试中被执行,而return false没有执行,那么代码的覆盖率为2/3=66.7%

接下来提高代码的覆盖率,其实就是对代码的每个部分都进行测试。

image.png

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

依赖

image.png

幂等:重复运行,结果是一样的。

Mock测试

开源的mock包:GitHub - bouk/monkey: Monkey patching in Go

安装:go get bou.ke/monkey

一般单元测试重点在于CPU和内存类型的测试,而对IO类型的测试是比较敏感的,针对这种情况,可以使用mock技术。

mock测试,简单来说,就是通过对服务或者函数发送设计好的参数,并且通过构造注入期望返回的结果来方便以上几种测试开发。

有以下情况使用mock比较好:

  • IO类型,本地文件、数据库、网络API、RPC等

  • 依赖的服务还没有开发好,这个时候自己模拟一个服务,加速开发进度,提升效率

  • 压力性能测试的时候屏蔽外部依赖,专注测试本模块

  • 依赖的内部函数非常复杂,要构造数据非常不方便

mock测试是的单元测试的升级,可以把之前无法做的,但是又非常重要的模块进行单元测试,

monkey是一个GO单元测试中十分常用的打桩工具,它在运行时通过汇编语言重写可执行文件,将目标函数或方法的实现跳转到桩实现,其原理类似于热补丁。

monkey库很强大,但是使用时需要注意以下事项:

  • monkey不支持内联函数,在测试的时候需要通过命令行参数-gcflags=-l关闭go语言的内联优化。

  • monkey不是线程安全的,所以不要把它用到并发的单元测试中。

基准测试

基准测试是对计算机系统的性能的测试。

在程序中。基准测试,是一种测试代码性能的方法,比如有一个问题你有很多不同的方案,你想选择一种性能最好的方案,那么就需要用到基准测试。

基准测试主要是通过测试CPU和内存的效率问题,来评估被测试代码的性能,进而找到更好的解决方案。比如连接池的数量不是越多越好,那么哪个值才是最优值呢,这就需要配合基准测试不断调优了。

rand在高并发时如果不太注意底层实现的话,容易造成性能问题,可以是尝试使用fastrand. https://github.com/bytedance/gopkg

项目实战

image.png

抽离出两个实体:

image.png

使用ER图描述实体:

image.png

项目分层结构:

image.png

数据层:Repository, 与外部数据(数据库)打交道,增删改查,数据Model

逻辑层:Service, 处理核心业务逻辑输出,业务Entity

视图层:Controller, 处理和外部的交互逻辑, View