这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
一、本堂课重点内容:
- Goroutine
- Channel
- Sync
- 依赖管理
- 测试
- 实战开发简单web服务器
二、详细知识点介绍:
Channel
GO更推荐通过通信实现共享内存而不是通过共享内存实现通信。 Channel的本质类似一个阻塞队列,分为有缓存和无缓存 无缓存的Channel用来实现协程同步,有缓存则是异步的
WaitGroup
底层维护一个计数器,实现线程同步 有三个函数,分别是Add(delta int)作用是使得计数器加delta Done()使得计数器减一,一般在子协程工作完毕后执行 Wait()阻塞等待直到计数器为0,一般在主协程使用
依赖管理
复杂项目中需要依赖很多库,GO有三种方法实现依赖管理:GOPATH、GO Vendor、Go Module,最常用的是Go Module
GOPATH:环境变量$GOPATH其实就是GO的根目录,里面有三个文件,分别是bin(项目编译的二进制文件)、pkg(项目编译的中间产物,起到加速编译的作用)、src(项目源码), 这种方式下,项目代码直接依赖src下的代码,需要下载更新包需执行go get下载最新版本的包到src目录下。缺点是无法实现package的多版本控制,比如src中某个包升级了,那么其他所有依赖升级包的包都会受到影响,如果不跟着升级的话可能无法正常编译
Go Vender:为了解决GOPATH的弊端诞生了Go Vender。其实就是再$GOPATH下多了一个vender文件夹,存放一些包的副本,编译时会首先从vender文件夹中寻找依赖包,找不到再去src文件夹中寻找,本质上还是依赖源码,无法完美地控制以来的版本,可能出现依赖冲突,导致编译出错。(比如project A依赖包B和包C,而包B依赖包D1,包C依赖包D2,包D1和包D2是不兼容的,那么此时就出现了以来冲突)
Go Module:
版本:版本分为语义化版本和基于commit伪版本,语义化版本格式为 ${MAJOR}.${MINOR}.${PATCH},不同的MAJOR间不兼容,同一个MAJOR下的MINOR相互兼容,PATCH是一些代码的小更新修复。
特殊标识:在go.mod中,间接依赖要用// indirect标识。对于主版本2+的模块会在模块路径后加/vN后缀。对于没有go.mod文件并且主版本2+的依赖,要加+incompatible后缀。
配置依赖规则:配置依赖时选择最低的兼容版本,比如如果你的module,依赖的module A需要require C v1.3,依赖的module B需要require C v1.4,最小版本选择算法将会选择C的v1.4版本用于build。即使后期C有v1.5版本可用,未显式require至go.mod中,仍将使用C v1.4版本用于build。
依赖分发:若直接依赖代码仓库中的代码容易遇到问题,比如代码仓库的代码作者更改或删除了代码,那么可能导致之前能跑的程序因为依赖包的改变就不能跑了,也就是无法保证构建稳定性和依赖可用性,以及增加第三方代码托管平台的负载压力。为了解决以上问题引入Porxy,它可以缓存代码平台上的依赖代码(类似缓存)。
go mod语句:后可接三个关键词init(初始化,创建go mod文件)、download(下载模块到本地缓存)、tidy(增加需要的依赖,删除不需要的依赖)
测试的基本概念
测试分为三种类型
- 回归测试:通过手动测试
- 集成测试:对系统功能的自动化测试
- 单元测试:开发者对单独的函数模块进行测试 从上到下,覆盖率变大,成本却降低,因此单元测试一定程度上决定代码质量
单元测试
规则:所有测试文件以_test.go结尾,测试函数命名为func TestXxx(*testing.T)(类似process即跑业务逻辑的函数),初始化逻辑放到TestMain(*testing.M)(类似主函数)当中。
覆盖率:就是通过跑完所有测试用例所需要运行的代码段占所有代码的比例。在go test命令结尾加--cover即可检测覆盖率。 提高覆盖率的方法:1.测试分支相互独立、全面覆盖 2.测试单元粒度足够小,满足函数单一原则
单纯的单元测试的不足之处:一个大项目需要很多外部依赖,比如DB、Cache、本地文件,在测试过程中可能因为网络等原因导致测试结果不同,而测试需要满足稳定性和幂等性(每次测试结果相同)。
Mock测试
需要测试的项目有许多依赖时可以利用Mock
Mock可以为一个函数或方法打桩。例如开源monkey库中的Patch函数就是打桩操作,可以拜托本地文件的依赖,在任何环境中运行。
基准测试
一句话说,基准测试就是测试性能的。
三、实践练习例子:
本次时间练习的项目架构主要分为三层:repository层、service层、controller层
- repository层:主要负责存储数据,向service层提供数据
- service层:从service层拿到数据后进行处理,执行业务逻辑,最后将得到的结果给controller层
- controller层:处理与外部的交互逻辑
该项目流程就是:本地存放json文件,在repository层读取本地文件,存入结构体中,并用map设置索引,最终用户通过索引查询。service层通过controller层传来的id,在map中查询对应信息,并将信息返回给controller层,controller层简单封装一下信息。
主函数中用了Gin框架,Gin可以快速完成WEB开发,底层其实就是封装了net/http库。
r := gin.Default() //生成一个默认的gin
r.GET("/community/page/get/:id", func(c *gin.Context) { //解析get方法,通过RESTful方式获得id
topicId := c.Param("id") //保存id
data := cotroller.QueryPageInfo(topicId) //向controller层传入数据跑业务逻辑
c.JSON(200, data) //返回json格式
})
err := r.Run()
四、课后个人总结:
Go进行web开发十分迅速,并且有有非常多的测试方法,更重要的是可以非常简单地开发出一个并发程序,但在实践中要注意并发安全
五、引用参考:
gin-gonic.com/zh-cn/docs/… Gin官方文档