Go 语言进阶 - 工程进阶

Go 语言进阶 - 工程进阶

Go 语言进阶 - 工程进阶

概述

本节课程主要分为四个方面:

  1. 并发编程
  2. 依赖管理
  3. 单元测试
  4. 项目实战

详述

  • 罗列课程中涉及到的概念和相关资料,对于不熟悉的知识点,希望同学们可以提前查询预习,届时跟上直播课程进度。
  • 【必须】课程内容相关代码链接:github.com/Moonlight-Z…

并发编程

属于编程进阶内容,考虑到工程项目的可用性和可靠性,工程实践中经常会用到。

依赖管理

了解Go依赖管理演进的历程,通过课程学习以及课后实践能能够熟练使用go module 管理依赖。

单元测试

项目实战

需求模型来源

青训营话题页forum.juejin.cn/youthcamp/p…

需求

  1. 实现一个展示话题(标题,文字描述)和回帖列表的后端http接口;
  2. 本地文件存储数据

组件及技术点

课程笔记

课程链接:

语言进阶

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

线程:内核态,比较重量级

协程:用户态,线程可以跑多个协程,比较轻量

Goroutine

快速打印:

func hello(i int) {
	println("hello goroutine : " + fmt.Sprint(i))
}

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

最后是使用time.sleep进行阻塞,防止在协程未运行结束前主线程先运行结束了。

Channel

协程通过通信来共享内存

func CalSquare() {
	src := make(chan int)
	dest := make(chan int, 3)
	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)
	}
}

var (
	x    int64
	lock sync.Mutex
)

func addWithLock() {
	for i := 0; i < 2000; i++ {
		lock.Lock()
		x += 1
		lock.Unlock()
	}
}
func addWithoutLock() {
	for i := 0; i < 2000; i++ {
		x += 1
	}
}

func Add() {
	x = 0
	for i := 0; i < 5; i++ {
		go addWithoutLock()
	}
	time.Sleep(time.Second)
	println("WithoutLock:", x)
	x = 0
	for i := 0; i < 5; i++ {
		go addWithLock()
	}
	time.Sleep(time.Second)
	println("WithLock:", x)
}

WaitGroup并发同步

func ManyGoWait() {
	var wg sync.WaitGroup
	wg.Add(5)
	for i := 0; i < 5; i++ {
		go func(j int) {
			defer wg.Done()
			hello(j)
		}(i)
	}
	wg.Wait()
}

依赖管理

GOPATH:环境变量,项目代码直接依赖src下的代码,go get下载最新的包到src目录下

Go Vendor:增加vendor文件,存放依赖包的副本,优先从vendor文件里面查找,但是仍然无法控制依赖的版本

Go Module:go.mod:依赖管理基本单元、原生库、单元依赖

测试

单元测试

  • 所有测试文件以_test.go结尾
  • func TestXxx(*testing.T)
  • 初始化逻辑放到TestMain中
func HelloTom() string {
	return "Tom"
}

func TestHelloTom(t *testing.T) {
	output := HelloTom()
	expectOutput := "Tom"
	assert.Equal(t, expectOutput, output)
}

添加–cover参数可以评价测试代码的覆盖率

Mock测试

一些函数对本地的数据库、文件等有强依赖,在测试的同时找到这些依赖要求过高

可以使用Mock进行测试,在函数执行的时候替换成另外一个函数(打桩),从而规避掉对本地其他的强依赖

func ReadFirstLine() string {
	open, err := os.Open("log")
	defer open.Close()
	if err != nil {
		return ""
	}
	scanner := bufio.NewScanner(open)
	for scanner.Scan() {
		return scanner.Text()
	}
	return ""
}

func ProcessFirstLine() string {
	line := ReadFirstLine()
	destLine := strings.ReplaceAll(line, "11", "00")
	return destLine
}

func TestProcessFirstLine(t *testing.T) {
	firstLine := ProcessFirstLine()
	assert.Equal(t, "line00", firstLine)
}

func TestProcessFirstLineWithMock(t *testing.T) {
	monkey.Patch(ReadFirstLine, func() string {
		return "line110"
	})
	defer monkey.Unpatch(ReadFirstLine)
	line := ProcessFirstLine()
	assert.Equal(t, "line000", line)
}

基准测试

对函数的运行时间进行测试:go test -bench=.

var ServerIndex [10]int

func InitServerIndex() {
	for i := 0; i < 10; i++ {
		ServerIndex[i] = i+100
	}
}

func Select() int {
	return ServerIndex[rand.Intn(10)]
}

func FastSelect() int {
	return ServerIndex[fastrand.Intn(10)]
}

func BenchmarkSelect(b *testing.B) {
	InitServerIndex()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		Select()
	}
}
func BenchmarkSelectParallel(b *testing.B) {
	InitServerIndex()
	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			Select()
		}
	})
}
func BenchmarkFastSelectParallel(b *testing.B) {
	InitServerIndex()
	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			FastSelect()
		}
	})
}

项目实战:社区话题页面

需求

  1. 实现一个展示话题(标题,文字描述)和回帖列表的后端http接口;
  2. 本地文件存储数据

分层结构

  1. 数据层:数据Model,处理外部数据的增删改查
  2. 逻辑层:业务Entity,处理核心业务逻辑输出
  3. 视图层:视图View,处理和外部的交互逻辑

组件及技术点

具体逻辑见代码

课后实践

  1. 支持对话题发布回帖。
  2. 回帖id生成需要保证不重复、唯一性。
  3. 新加回帖追加到本地文件,同时需要更新索引,注意Map的并发安全问题

Go 语言进阶 - 工程进阶
https://zhangzhao219.github.io/2023/01/16/ByteDanceYouthTrainCamp/ByteDanceYouthTrainCamp-Day02/
作者
Zhang Zhao
发布于
2023年1月16日
许可协议