《Go工程实践》课程笔记 | 青训营笔记

107 阅读7分钟

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

Go工程实践

语言进阶

并发 vs 并行

并行是实际的多个核上同时运行程序

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

Goroutine

协程: 用户态,轻量级线程, 其调度由 go 本身负责, 栈在 kb 级别

线程: 内核态,一个线程跑多个协程,栈在 mb 级别

CSP ( Communicating Sequential Process )

sharing memory by communication , 而不是通过共享内存而实现通信

Channel

管道通道 make 创建

以其内部是否有缓冲, 可以将管道划分为 无缓冲通道 和 有缓冲通道:

  • 无缓冲通道,又称 "同步通道",有数据时即会阻塞

并发安全 Lock

sync 包下面很多工具用于并发同步控制,muteX 就是常用锁

WaitGroup

用于协程同步,其原理是维护一个计数器

通过 Add、 Done 、 Wait 三个方法执行功能

依赖管理

依赖的 思想: 站在巨人肩膀上

依赖管理演进

  1. GOPATH
  2. Go Vendor
  3. Go Module

解决的问题:

  • 不同项目(环境)需要依赖的版本不同
  • 控制依赖库的版本

GOPATH

是 Go 语言控制的环境变量,是 Go 默认的工作区

存放三个文件夹:

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

缺点:

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

    不同项目可能依赖同一个 package 的不同版本,然而 src 只有一份

Go Vendor

在项目的目录下,增加一个 Vendor 文件,所有依赖包副本形式放在其中

在编译时,现在 Vendor 下寻找所使用的 package ,找不到再从 GOPATH 中寻找

缺点:

  • 无法控制依赖的版本
  • 更新项目又可能出现依赖冲突,导致编译出错

归根结底,其本质问题是: 依赖的源码文件

Go Module

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

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

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

依赖管理三要素

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

go.mod

内容可以分为三部分:

  1. 依赖管理基本单元

  2. 原生库

  3. 单元依赖

    1. 每个依赖分为 path 和 版本两部分

version

有两个方式:

major 版本之间可以是不兼容的,minor 通常是兼容的,小版本修改; 而 patch 通常是 bug 修复

commit 伪版本 前面是语义化一样的,中间是时间戳,最后是哈希校验码

indirect

没有直接导入的 module,会有这个标记

依赖分发—回源

依赖去哪里下载, 如何下载

缺点:

  • 无法保证构建稳定性

    • 增加、修改、删除软件版本
  • 无法保证依赖可用性

    • 删除软件
  • 增加第三方压力

    • 代码托管平台负载问题

依赖分发—Proxy

是一个服务站点,会缓存软件内容,保证稳定性

依赖分发—变量 GOPROXY

服务站点 URL 列表,direct 代表前面的服务站点找不到的话,会回到 源代码托管站点 找

工具—go get

工具—go mod

tidy 经常用,对 go mod 里的依赖重新检查,减少构建时间

测试

质量就是 “安全”

稳定性重中之重

营业事故一览

测试是避免事故的发生

测试初识

回归测试:如 QA同学在场景下执行测试

集成测试:自动化地对于功能、接口进行测试

单元测试:开发者对单独模块进行测试

因此单元测试一定程度上决定了软件的质量

单元测试

单元测试—规则

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

  • 测试函数以 Test 开头

  • 初始化逻辑放在 TestMain 中执行

    • testing.M 调用 Run 方法执行所有的测试函数

单元测试—覆盖率

如何衡量代码是否经过了足够的测试?

如何评价项目的测试水准?

如何评估项目是否达到了高水平测试等级?

一个重要指标就是:代码覆盖率

go test 指令中,后面加上一个 --cover 可以在输出的时候

单元测试—Tips

单元测试—依赖

单元测试需要达到:

  • 幂等 : 即多次运行得到的结果是一致的
  • 稳定: 单元测试是相互隔离

由于运行中对于文件和 cache 、数据库等的依赖,为了单元测试的两个要求,引入了 mock 机制

mock

mock 的组件有很多,这里用到的是一个开源的包:github.com/bouk/monkey

实现了对 method 对实例的方法进行打桩

打桩:

  • 为一个函数打桩
  • 为一个方法打桩

主要为下面这两个函数!

本质是使用 unsafe 包,运行时将内存中函数地址 替换成 运行时函数地址

基准测试

  • 优化代码,需要对当前代码分析
  • 内置的测试框架提供了基准测试的能力

基准测试—运行

基准测试的函数名以 Benchmark 开头

提供了 ResetTimer 等方法避免不相关代码的影响

项目实战

项目流程主要包括:

  1. 需求设计
  2. 代码开发
  3. 测试运行

需求描述

需求用例

用户交互的主要对象: 话题 和 回帖

这两个对象应该如何存储呢?

ER 图

Topic 和 Post 是一对多的关系

分层结构

将代码分成三个部分

数据层主要面对逻辑层,对逻辑层是透明的——逻辑层不关心具体底层数据的数据存储,只关心有无数据

逻辑层负责: 接收数据层数据,做打包封装,输出一个 Entity,这里就是对应话题页面

视图层对上层负责,对数据做一些封装,可以以一些 API 的形式访问

组件工具

存储层实现

Topic 和 Post 是两个结构体,字段定义如下:

// topic.gotype Topic struct {
    Id         int64     `gorm:"column:id"`
    UserId     int64     `gorm:"column:user_id"`
    Title      string    `gorm:"column:title"`
    Content    string    `gorm:"column:content"`
    CreateTime time.Time `gorm:"column:create_time"`
}
// post.gotype Post struct {
    Id         int64     `gorm:"column:id"`
    ParentId   int64     `gorm:"parent_id"`
    UserId     int64     `gorm:"column:user_id"`
    Content    string    `gorm:"column:content"`
    DiggCount  int32     `gorm:"column:digg_count"`
    CreateTime time.Time `gorm:"column:create_time"`
}

这些都是存储在本地文件中的,因此是以 json 序列化的形式存储在本地

此时需要两个查询:

  1. 从 话题 id 找到 话题
  2. 从 话题 id 找到关联的所有 post

存储库索引

类似数据库 索引 与 元数据 的关系,通过 map 来构建对应关系

数据文件是通过将结果体序列存储到本地文件里

通过扫描文件里的所有行然后读取内容出来

然后通过单例模型生成两个实例,包装这两个 map, 并提供函数方法用于查询

服务层实现

结构体 PageInfo 这个实体用于保存从文件中获取的数据

type PageInfo struct {
    Topic    *repository.Topic
    PostList []*repository.Post
}

整个服务层的逻辑:

  1. 参数校验:进行非法校验
  2. 准备数据:
  3. 组装实体

Controller 层

构建 View 对象

  • Code :用于标识是否读取成功
  • Msg: 具体错误信息 或 表示成功的信息
  • Data: 数据实体

Router

在上面已经把基本框架实现完成了

下面就是通过 gin 搭建 web 框架

流程

构建路由,这部分不太懂

最终运行 server.go 文件后,就可以通过网络请求获取到数据,前端根据获取到的数据来渲染页面