Go
0x01 准备
-
Go 语言是一种静态的、强类型、编译型、并发型、并具有垃圾回收功能的编程语言
-
Go 编译器编译流程
graph LR 词法解析-->语法解析 -->抽象语法树构建 -->类型检查 -->变量捕获 -->函数内联 -->逃逸分析 -->闭包重写 -->遍历并编译函数 -->SSA生成 -->机器码生成 -
Go 编译器安装(Windows)
- 在 Go 语言官网下载
.msi
安装包 - 安装完成后配置用户环境变量
- 变量名:
GOPATH
- 变量值:
C:\Program Files\Go
(默认路径)
- 变量名:
- 使用命令
go env
确认是否安装成功- 修改代理地址:
go env -w GOPROXY=https://goproxy.cn
- 修改代理地址:
- 在 Go 语言官网下载
-
创建 GoLand 项目
- 新建项目(选择不含
(GOPATH)
的项目)- 设置 GOROOT(使用命令
go env GOROOT
命令查看) - 设置环境:
GOPROXY=https://goproxy.cn,direct
- 设置 GOROOT(使用命令
- 在设置 -> GOPATH 中设置项目和模块的 GOPATH 全部为空,并选中以下两个选项
- 使用系统环境中定义的 GOPATH
- 为整个 GOPATH 编制索引
- 在项目根目录下使用命令
go get -u github.com/gin-gonic/gin
- 在 go.mod 中添加
require github.com/gin-gonic/gin latest
- 在项目根目录下使用命令
go mod tidy
- 新建项目(选择不含
0x02 基础语法
(1)Hello World
-
创建文件 main.go 并写入以下代码:
package main import "fmt" func main() { fmt.Println("Hello World") }
-
package main
:package 类型可以理解为工作空间,便于为程序归类,达到模块化,有以下分类- 可执行包:产生可执行文件
- main 包代表可以被编译和执行,必须有 main 函数作为入口点
- 依赖包:功能特定,可重复使用
- 可执行包:产生可执行文件
-
import "fmt"
:导入标准库 -
func
:声明函数
(2)内置数据类型
- 有符号整型:
int
/int8
/int16
/int32
/int64
- 无符号整型:
uint
/uint8
/uint16
/uint32
/uint64
/uintptr
- 浮点数型:
float32
/float64
- 复数型:
complex64
/complex128
real
:获取实部image
:获取虚部
- 布尔型:
bool
- 字节型:
byte
- UTF-8 编码值:
rune
- 字符串型:
string
(3)变量与常量
-
变量:执行环境中的事件存储区域
-
变量的声明与赋值
-
一般情况:
var name type = expression
func main() { var hw string = "Hello World" fmt.Println(hw) }
-
可以在单个语句中声明并赋值多个不同类型的变量
func main() { var i, j, k = 1, true, "123" fmt.Println(i, j, k) }
-
-
短类型:
name := expression
func main() { hw := "Hello World" fmt.Println(hw) }
-
-
变量仅在被定义的作用域内有效
- 作用域:Universe > Package > File > Function
(4)表达式与运算符
- 表达式:编程语言中的句法实体,可以对其进行计算以确定其值。表达式可以是单个或多个变量或常量,也可以是变量、函数、操作符的组合
- 运算符
- 算术运算符:
+
/-
/*
//
/%
- 关系运算符:
==
/!=
/>
/<
/>=
/<=
- 逻辑运算符:
&&
/||
/!
- 赋值运算符:
=
/+=
/-=
/*=
//=
/%=
/&=
/^=
/|=
- 位运算符:
&
/^
/|
/>>
/<<
- 算术运算符:
- 注释
- 单行注释:
//
- 多行注释:
/**/
- 单行注释:
(5)程序控制结构
a. 顺序结构
- 从上到下依次执行各个语句
b. 选择结构
-
if 语句
-
两路分支结构
if exp { // code1 } else { // code2 }
-
多路分支结构
if exp1 { // code1 } else if exp2 { // code2 } else { // code3 }
-
-
switch 语句
-
按值
switch var1 { case val1: // code case val2, val3: // code default: // code }
-
按条件
switch { case exp1: // code case exp2: // code default: // code }
-
c. 循环结构
-
标准
for 表达式1; 表达式2; 表达式3 { // code }
如:
for i := 0; i < 10; i++ { fmt.Println(i) }
-
只有条件判断,如
for i < 100 { fmt.Println(i) i = i * 2 }
-
无限循环,如
for { fmt.Println("HW") }
-
range,如
values := []int{1, 3, 56, 2} for i, v := range values { fmt.Println(i, v) }
-
其他循环控制语句
- break:跳出循环
- continue:跳过本次循环
- label:标识某个指定位置,常见于与 continue 搭配执行跳转
(6)复合类型
a. 数组
-
一维数组:
var arr = []int{}
-
二维数组:
var arr = [][]int{}
-
声明数组:
var arr1 [10]int var arr2 = [4]int{1, 2, 3, 4} var arr3 = [12]int{1, 5: 4, 6, 10: 100, 15} var arr4 = [...]int{1, 2, 3}
b. 切片
-
声明切片
var slice1 []int var slice2 = []int{1, 2, 3} var slice3 = []int{1, 5: 4, 6, 10: 100, 15} var slice4 = make([]int, 5) var slice5 == make([]int, 5, 7)
-
切片添加
var slice []int fmt.Println(slice) slice = append(slice, 1, 2, 3) fmt.Println(slice)
-
切片截取
foo := make([]int, 5) foo[3] = 3 foo[4] = 4 fmt.Println(foo) bar := foo[1:4] fmt.Println(bar) bar[1] = 1 fmt.Println(bar)
c. 哈希表
-
哈希值通过哈希函数映射到表
hash = f(key) index = hash % array_size
-
哈希冲突:不同的哈希值经过哈希函数计算结果相同
- 解决方法:拉链法、开放寻址法 、
-
使用
-
定义
var hash map[T]T var hash = make(map[T]T, NUMBER)
如
var hash = map[string]int{ "a": 1, "b": 2 }
-
赋值
hash[key] = value
如
hash["c"] = 3
-
访问
i, v := hash[key]
如
i, v := hash["a"]
-
-
哈希的 key 必须可比较
- 切片、函数、map 以及包含上述三种数据之一的结构体、数组均不能比较
d. 结构体
-
结构体的声明与定义
type Nat struct { n int d int } // or var a = Nat{ 1, 2, } // or b := Nat{ d: 2, n: 1, }
如
var person struct { name string age int }
-
赋值
person.name = "SRIGT" person.age = 18
-
匿名结构体,如
person := struct { name string age int }{ name: "SRIGT", age: 18, }
0x03 语言特性
(1)函数
- 功能抽象
- 隐藏信息
- 提高清晰度
- 关注点分离
- 代码复用
a. 函数声明
-
一般声明
func square(x int) int { return x * x }
- 关键字
func
+函数名square
+函数参数及参数类型x int
+函数返回值int
+函数体{}
- 关键字
-
省略类型
func add(x, y, z int) int { return x + y + z }
-
预留参数
func function(x int, _ int)
-
多返回值
func add(x, y int) (int, int, int) { return x, y, x+y }
-
可变参数
func add(numbers ...int) int { var sum int for _, value := range numbers { sum += value } return sum }
b. 递归
-
以阶乘计算为例
func f(n int) int { if n == 1 { return 1 } return n * f(n-1) }
c. 高阶函数
-
函数作为参数,举例:设置函数可以计算给定的两个值之间所有整数的和、平方和
func calculate(handle func(int) int, a, b int) int { sum := 0 for i := a; i < b; i++ { sum = sum + handle(i) } return sum } func main() { fmt.Println(calculate(func(a int) int { return a }, 1, 5)) fmt.Println(calculate(func(a int) int { return a * a }, 1, 5)) }
-
函数作为返回值,举例:
package main import ( "fmt" "net/http" ) func function(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World") } func main() { server := http.Server{Addr: "127.0.0.1:8080"} http.HandleFunc("/", function) server.ListenAndServe() }
- 运行并访问本地 8080 端口
-
函数作为值,举例:
func function1(i int) int { return i } func function2(i int) { function1(i) }
-
闭包:可以让函数访问另外一个函数的参数和变量,举例:
func function() { x := 0 increment := func() int { x++ return x } fmt.Println(increment()) // output: 1 }
(2)defer 函数
-
defer 函数一般用于资源的释放和异常的捕捉
-
用法
defer func(...){ // code }()
-
优势:资源释放更方便更容易
-
特性
-
延迟执行
-
参数预计算
func main() { a := 1 defer func(b int) { fmt.Println(b) // output: 2 }(a + 1) a = 3 }
-
LIFO 执行顺序
for i := 1; i < 5; i++ { defer fmt.Println(i) }
-
-
最佳实践:服务器
package main import ( "fmt" "log" "net/http" "time" ) type MyHandler struct{} func (m MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { before := time.Now() defer func() { after := time.Now() fmt.Println(after.After(before)) }() fmt.Fprintf(w, "Hello world") } func main() { handler := MyHandler{} server := http.Server{ Addr: "127.0.0.1:8080", Handler: recoverHandler(handler), } server.ListenAndServe() } func recoverHandler(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { log.Printf("panic: %+v", err) http.Error(w, http.StatusText(500), 500) } }() next.ServeHTTP(w, r) } return http.HandlerFunc(fn) }
(3)panic
-
用法
func executePanic() { defer fmt.Println("defer function") panic("panic function") fmt.Println("println") } func main() { executePanic() fmt.Println("main println") }
-
recover 函数——捕获异常
func executePanic() { defer func() { if err := recover(); err != nil { fmt.Println("Panic: ", err) } }() panic("panic function") fmt.Println("println") }
(4)接口
a. 概述
- 概述
- 在计算机科学中,接口是一个共享边界,计算机系统的独立组件之间可以在该共享边界上交换信息,比如在软件,硬件,外围设备,人员之间
- 在面向对象的编程语言中,接口是指相互独立的两个对象之间交流的方式
- 特性
- 可以为任何自定义的类型添加方法
- 没有任何形式的基于类型的继承
- 隐式声明
- 实现扁平化、面向组合的设计模式
b. 使用
-
声明与定义
type InterfaceName interface { function() // ... }
-
实现
// 声明接口 type Shape interface { perimeter() float64 area() float64 } // 声明结构体 type Rectangle struct { a, b float64 } // 周长计算 func (r Rectangle) perimeter() float64 { return (r.a + r.b) * 2 } // 面积计算 func (r Rectangle) area() float64 { return r.a * r.b }
-
动态调用
func main() { var shape Shape shape = Rectangle{3, 4} fmt.Println(shape.perimeter()) fmt.Println(shape.area()) }
-
类型断言
func main() { var shape Shape shape = Rectangle{3, 4} rect, ok := shape.(Rectangle) if !ok { fmt.Printf("s.(Rectangle) is not ok") } fmt.Println(rect.perimeter()) fmt.Println(rect.area()) }
-
空接口
type Empty interface{}
-
空接口的类型断言
func MyPrintln(arg interface{}) { switch arg.(type) { case string: fmt.Println("string") // ... } }
-
(5)反射
-
概述
- 在计算机科学中,反射是一种能力,可以让程序动态地检查或者修改它的结构
-
使用场景
- 检查函数的参数个数
- 检查变量的类型
- 根据方法名和函数名动态调用
- 一般是框架或是基础服务的一部分
-
使用方式
-
Value 与 Type
import ( "fmt" "reflect" ) func main() { var num float64 = 1.2345 fmt.Println("type: ", reflect.TypeOf(num)) fmt.Println("type is float 64: ", reflect.TypeOf(num).Kind() == reflect.Float64) fmt.Println("value: ", reflect.ValueOf(num)) }
- 可以将
reflect.Value
看作反射的值,reflect.Type
看作反射的实际类型
- 可以将
-
反射转换为接口
func main() { var num float64 = 1.2345 pointer := reflect.ValueOf(&num) value := reflect.ValueOf(num) fmt.Println(pointer.Interface()) fmt.Println(value.Interface()) convertPointer := pointer.Interface().(*float64) convertValue := value.Interface().(float64) fmt.Println(convertPointer) fmt.Println(convertValue) }
graph LR A(Interface)--TypeOf-->B(Type) A--ValueOf-->C(Value) C--Type-->B C--Interface-->A -
反射转换为实际值
func main() { a := reflect.ValueOf(123).Int() fmt.Println(a) b := reflect.ValueOf("String").String() fmt.Println(b) c := reflect.ValueOf(1.23).Float() fmt.Println(c) num := 123 d := reflect.ValueOf(&num).Elem().Int() fmt.Println(d) }
-
Elem()
间接访问func main() { var x int = 123 value := reflect.ValueOf(&x) pointer := value.Elem() pointer.SetInt(456) fmt.Println(x) }
-
修改反射的值
func main() { var num float64 = 1.23 pointer := reflect.ValueOf(&num) newValue := pointer.Elem() fmt.Println(newValue.Type()) fmt.Println(newValue.CanSet()) newValue.SetFloat(123) fmt.Println(num) }
-
结构体
type User struct { Id int Name string Age int } func (u User) ReflectCallFunc() { fmt.Println("ReflectCallFunc") } func main() { user := User{1, "SRIGT", 18} DoFiledAndMethod(user) } // 通过接口来获取任意参数 func DoFiledAndMethod(input interface{}) { getType := reflect.TypeOf(input) getValue := reflect.ValueOf(input) // 获取方法字段 // 1. 获取 interface 的 reflect.Type,通过 NumField 遍历 // 2. 通过 reflect.Type 的 Field 获取其他 Field // 3. 通过 Field 的 interface 获取对应的 Value for i := 0; i < getType.NumField(); i++ { field := getType.Field(i) value := getValue.Field(i).Interface() fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value) } for i := 0; i < getType.NumMethod(); i++ { method := getType.Method(i) fmt.Printf("%s: %v\n", method.Name, method.Type) } methodValue := getValue.MethodByName("ReflectCallFunc") args := make([]reflect.Value, 0) methodValue.Call(args) }
-
函数
type T struct { A, b int } func (t T) AddSubThenScale(n int) (int, int) { return n * (t.A + t.b), n * (t.A - t.b) } func main() { t := T{3, 1} vt := reflect.ValueOf(t) vm := vt.MethodByName("AddSubThenScale") results := vm.Call([]reflect.Value{reflect.ValueOf(3)}) fmt.Println(results[0].Int(), results[1].Int()) // 12 6 neg := func(x int) int { return -x } vf := reflect.ValueOf(neg) fmt.Println(vf.Call(results[:1])[0].Int()) // -12 fmt.Println(vf.Call([]reflect.Value{ vt.FieldByName("A"), })[0].Int()) // -3 }
-
其他类型
func main() { ta := reflect.ArrayOf(5, reflect.TypeOf(123)) fmt.Println(ta) tc := reflect.ChanOf(reflect.SendDir, ta) fmt.Println(tc) tp := reflect.PtrTo(ta) fmt.Println(tp) ts := reflect.SliceOf(tp) fmt.Println(ts) tm := reflect.MapOf(ta, tc) fmt.Println(tm) tf := reflect.FuncOf([]reflect.Type{ta}, []reflect.Type{tp, tc}, false) fmt.Println(tf) tt := reflect.StructOf([]reflect.StructField{ { Name: "Age", Type: reflect.TypeOf("abc") }, }) fmt.Println(tt) fmt.Println(tt.NumField()) }
-
0x04 并发编程
(1)协程
a. 概述
-
进程、线程、协程
-
进程 Process 是正在执行的程序的实例,包含程序代码、数据、文件和系统资源等;是操作系统资源分配的基本单位,每个进程都有各自独立的地址空间、文件描述符表等资源
-
线程 Thread 是操作系统抽象出轻量级的资源;一个进程中包括多个线程,每个线程都共享进程的资源,并且都有各自独立的栈空间等资源
-
协程构建于线程之上,即一个线程可以对应多个协程
- 调度方式上,协程依附于线程进行调度
- 上下文切换速度上,协程比线程更快
- 调度策略上,协程一般是协作式,相对优于线程的抢占式
- 栈的大小上,协程栈默认 2 KB,比线程要小
-
-
GMP 模型
-
协程 G + 线程 M + 逻辑处理器 P
flowchart LR subgraph 全局运行队列 A((G)) & B((G)) end subgraph 本地运行队列 C((G)) & D((G)) end 本地运行队列-->全局运行队列 -->本地运行队列 -->P -->M
-
-
并发与并行
- 并发发生在单核 CPU 中,通过上下文切换,进行事务切换,达成并发
- 并行发生在多核 CPU 中,多个事务同时在不同核中运行,达成并行
b. 应用
-
协程使用
func main() { links := []string{ "https://www.baidu.com/", "https://www.bilibili.com/", } for _, link := range links { go function(link) } } func function(link string) { _, err := http.Get(link) if err != nil { fmt.Println(link, " might be down") return } fmt.Println(link, " is up") }
-
主协程与子协程
- 主协程在程序启动后,自动创建
- 子协程创建用
go
关键字
(2)并发安全
-
数据争用:指两个协程同时访问相同的内存空间,并且至少有一个是写操作的情况
-
race 工具
go test -race mypkg
go run -race mysrc.go
go build -race mycmd
go install -race mypkg
-
并发安全的手段
-
原子锁
import "sync/atomic" var count int64 = 0 func add() { atomic.AddInt64(&count, 1) } func main() { go add() go add() }
-
互斥锁
import "sync" var count int64 = 0 var m sync.Mutex func add() { m.Lock() count++ m.Unlock() } func main() { go add() go add() }
-
读写锁
import "sync" type Stat struct { counters map[string]int64 mutex sync.RWMutex } func (s *Stat) getCounters(name string) int64 { s.mutex.RLock() defer s.mutex.RUnlock() return s.counters[name] } func (s *Stat) setCounters(name string) { s.mutex.Lock() defer s.mutex.Unlock() s.counters[name]++ }
-
WaitGroup
import ( "fmt" "sync" "time" ) func worker(id int) { fmt.Printf("Worker %d Starting\n", id) time.Sleep(time.Second) fmt.Printf("Worker %d done\n", id) } func main() { var wg sync.WaitGroup for i := 1; i <= 5; i++ { wg.Add(1) i := i go func() { defer wg.Done() worker(i) }() } wg.Wait() }
-
Once
import ( "sync" "database/sql" "fmt" ) var once sync.Once func DbOnce() (*sql.DB, error) { once.Do(func() { fmt.Println("Am called") db, dbErr := sql.Open("mysql", "root:test@tcp(127.0.0.1:3306)/test") if dbErr != nil { return } dbErr = db.Ping() }) return db, dbErr }
-
Cond
-
(3)通道
-
概述
-
通道是一种特殊的数据类型,用于实现不同 goroutine 之间的通信和同步
flowchart LR A(Main Routine)<-->Channel<-->B(Child go routine)
-
-
使用
-
通道声明与初始化
chan int chan <- float64 <-chan string var c = make(chan int)
-
通道读写
func main() { var c = make(chan int) go func() { c <- 5 }() fmt.Println(<-c) }
-
返回值
func main() { var c = make(chan int) go func() { for { data, ok := <-c fmt.Println(data, ok) if !ok { break } } }() }
-
-
通道的堵塞
func main() { ch := make(chan int) ch <- 1 fmt.Println(<-ch) }
-
通道带缓冲
func main() { // 创建一个带缓冲通道,容量为 2 ch := make(chan int, 2) // 向通道发送两个值 ch <- 1 ch <- 2 // 从通道读取值 fmt.Println(<-ch) fmt.Println(<-ch) }
-
通道作为参数
func worker(id int, c chan int) { for n := range c { fmt.Println(id, n) } }
-
通道作为返回值
func workerCreate(id int) chan int { c := make(chan int) go worker(id, c) return c }
-
通道关闭
func main() { var c = make(chan int) go func() { data, ok := <-c fmt.Println(data, ok) }() go func() { data, ok := <-c fmt.Println(data, ok) }() close(c) time.Sleep(1 * time.Second) }
-
-
最佳实践
func main() { links := []string{ "https://www.baidu.com/", "https://www.bilibili.com/", } c := make(chan string) for _, link := range links { go function(link, c) } for link := range c { link := link go func(link string) { time.Sleep(2 * time.Second) function(link, c) }(link) } } func function(link string, c chan string) { _, err := http.Get(link) if err != nil { fmt.Println(link, " might be down") c <- link return } fmt.Println(link, " is up") c <- link }
(4)并发模式
-
Ping-Pong
func main() { var Ball int table := make(chan int) go player(table) go player(table) table <- Ball time.Sleep(1 * time.Second) <-table } func player(table chan int) { for { ball := <-table ball++ time.Sleep(100 * time.Millisecond) table <- ball } }
-
Fan-in
func main() { ch := make(chan string) go search(ch, "SRIGT") for i := range ch { fmt.Println(i) } } func search(ch chan string, msg string) { var i int for { // 模拟找到关键字 ch <- fmt.Sprintf("get %s %d\n", msg, i) i++ time.Sleep(100 * time.Millisecond) } }
-
Fan-out
func main() { var wg sync.WaitGroup wg.Add(36) go pool(&wg, 36, 50) wg.Wait() } func worker(tasksChan <-chan int, wg *sync.WaitGroup) { defer wg.Done() for { task, ok := <-tasksChan if !ok { return } time.Sleep(time.Duration(task) * time.Millisecond) fmt.Println(task) } } func pool(wg *sync.WaitGroup, workers, tasks int) { taskChan := make(chan int) for i := 0; i < workers; i++ { go worker(taskChan, wg) } for i := 0; i < tasks; i++ { taskChan <- i } close(taskChan) }
-
Sub-worker
const ( WORKER = 5 SUBWORKERS = 3 TASKS = 20 SUBTASKS = 10 ) func main() { var wg sync.WaitGroup wg.Add(WORKER) go pool(&wg, WORKER, TASKS) wg.Wait() } func subworker(subtasks chan int) { for { task, ok := <-subtasks if !ok { return } time.Sleep(time.Duration(task) * time.Millisecond) fmt.Println(task) } } func worker(tasks <-chan int, wg *sync.WaitGroup) { defer wg.Done() for { task, ok := <-tasks if !ok { return } subtasks := make(chan int) for i := 0; i < SUBWORKERS; i++ { go subworker(subtasks) } for i := 0; i < SUBTASKS; i++ { taskI := task * i subtasks <- taskI } close(subtasks) } } func pool(wg *sync.WaitGroup, workers, tasks int) { taskChan := make(chan int) for i := 0; i < workers; i++ { go worker(taskChan, wg) } for i := 0; i < tasks; i++ { taskChan <- i } close(taskChan) }
-
Pipeline
func main() { multiply := func(value, multiplier int) int { return value * multiplier } add := func(value, additive int) int { return value + additive } ints := []int{1, 2, 3, 4} for _, v := range ints { fmt.Println(multiply(add(multiply(v, 2), 1), 2)) } }
0x05 项目组织
(1)依赖管理
- 常见问题:多依赖、多重依赖、依赖冲突、循环依赖
- 使用 Go Modules 替代 GOPATH
- Go Modules 特点
- 可以查找并下载所有依赖包
- 更新依赖包
- 本地文件管理依赖项、可重复构建
- 解决包冲突、选择最兼容的包
- 允许引用两个不同版本的第三方包
(2)Go Modules 实践
- Go Modules 初始化:使用指令
go mod init [module name]
,执行后会自动生成 go.mod 文件 - 引入第三方模块:在 main.go 中使用
import "[module name]"
语句导入其他第三方包 - 下载第三方模块:使用命令
go get [module name]
下载第三方包 - 使用第三方模块:在其他代码中直接调用
- 手动更新第三方模块:在 go.mod 文件中修改包的版本,并使用命令
go mod download [module name]
- replace 指令:替代,在 go.mod 中输入
replace [module name] v1.0.1 => [module name] v1.0.0
- retract 指令:撤回,在 go.mod 中输入
retract [module name] v1.0.0
0x06 生态
(1)测试
-
单元测试(unit testing)
-
单元测试是对单个函数或代码块进行测试的过程,它测试函数或代码块的输入和输出是否符合预期
-
Go语言中内置的 testing 包提供了基本的单元测试支持
-
举例
-
add.go
package add func Add(a, b int) int { return a + b }
-
add_test.go
package add import "testing" func TestAdd(t *testing.T) { sum := Add(1, 2) if sum == 3 { t.Log("Successed to add") } else { t.Fatal("Failed to add") } }
-
-
-
表格测试(table-driven testing)
- 表格驱动测试使用循环遍历的方式遍历多组测试数据
- 使用表格驱动测试可以方便地执行一组或多组测试,同时可以减少测试代码的冗余
-
子测试(subtests)
- 常与表格测试结合
t.Run()
- 常与表格测试结合
-
基准测试(benchmark testing)
-
基准测试用于测量程序在已知负载下的性能,其他目的在于确定某个程序、算法或者硬件系统的
性能指标 -
举例:
type Sumifier interface{ Add(a, b int) int } type Sumer struct{ id int32 } func (math Sumer) Add(a, b int) int { return a + b } type SumerPointer struct{ id int32 } func (math SumerPointer) Add(a, b int) int { return a + b } func BenchmarkDirect(b *testing.B) { adder := Sumer{id: 6754} b.ResetTimer() for i := 0; i < b.N; i++ { adder.Add(10, 12) } } func BenchmarkInterface(b *testing.B) { adder := Sumer{id: 6754} b.ResetTimer() for i := 0; i < b.N; i++ { Sumifier(adder).Add(10, 12) } } func BenchmarkInterfacePointer(b *testing.B) { adder := &SumerPointer{id: 6754} b.ResetTimer() for i := 0; i < b.N; i++ { Sumifier(adder).Add(10, 12) } }
-
-
代码覆盖率测试(code coverage testing)
- 单元覆盖率:
go test -cover ./name
- 可视化方法浏览:
go test -coverprofile=opt.out
go tool cover -html=opt.out
- 可视化方法浏览:
- 单元覆盖率:
-
模糊测试(fuzz testing)
- 模糊测试是一种黑盒测试技术,它会使用各种自动化工具生成大量随机的输入数据,然后将这些输入数据传递给被测程序进行测试,以此来检测被测程序中是否存在潜在的漏洞或错误
(2)调试
- 常见的调试方法和技术
- 打印调试信息
- 调试器
- 远程调试
- 性能分析工具
- PPROF:Go 语言的性能分析利器,用于对指标或特征进行分析,可以帮助定位程序中的错误并优化
- TRACE:可以捕获一段时间内程序的运行状态和事件,并以可视化的方式呈现出来
- Delve 调试器
- 安装 Delve:
go install github.com/go-delve/delve/cmd/dlv@latest
- 编译程序:
go build -gcflags=all="-N -l" main.go
- 安装 Delve:
- GoLand 进行本地 / 远程调试
(3)标准库
-
Go标准库由官方维护,涵盖了大多数通用场景和现代开发所需的核心部分,旨在为Go语言提供丰富而有力的基础功能和工具
-
字符串和文本处理
名称 说明 fmt 文本格式化函数 strings 字符串操作 strconv 类型转换 bytes 字节切片操作 unicode Unicode 编解码和分类 regexp 正则表达式支持 html 操作和生成 HTML 文件 index 文本索引相关的操作 -
数学与加密
名称 说明 math 数学函数 crypto 密码学 hash 哈希函数 compress gzip 等压缩算法 -
IO 和文件处理
名称 说明 io 输入输出原语 bufio 带缓存的输入输出 ioutil IO 实用函数 os 操作系统和文件系统操作 path/filepath 文件路径操作 archive 压缩和解压缩文件 -
网络与协议
名称 说明 net 网络 IO 函数 net/http HTTP 客户端和服务端 net/rpc RPC 客户端和服务端 encoding JSON 等编解码 -
并发和同步
名称 说明 sync 同步原语 context 控制协程退出 -
数据结构和算法
名称 说明 container 常用的容器和数据结构 sort 排序 math/rand 随机数生成 -
工具
名称 说明 testing 代码测试 log 日志记录 time 时间和计时器 pprof 性能分析 trace 追踪程序执行 go/ast 将 Go 源码解析为抽象语法树 errors 错误处理 unsafe 指针操作与访问未导出的成员 runtime 运行时 debug 调试信息 flag 命令行选项解析 image 支持各种图像格式和编解码 reflect 反射操作 database 数据库驱动
(4)第三方库
- Web 框架:Gin、Echo、Beego
- 数据库:GORM、Xorm、mongo-go-driver、gocql
- 缓存:go-redis、goCache、groupcache
- 测试:testify、gomonkey
- 配置:Cobra、viper、configor
- 图片处理:imaging、gocv、govatar
- 序列化:protobuf、go-json、msgpack
- 微服务:go-micro、grpc-go、go-zero、go-kit
- 网络编程:websocket、mqtt、rpcx
(5)泛型
a. 概述
- 泛型编程的核心思想是从具体的、高效的运算中抽象出通用的运算,这些运算可以适用于不同形式的数据,从而能够适用于各种各样的场景
- Go 1.18 引入泛型
- 特性
- 只存在于编译时
- 避免代码的重复,更简洁的代码和 API
- 编译速度略微下降
- 有类型的约束
- 拓展了接口的能力
b. 使用
-
泛型声明
// 切片 type Slice1 [T int|float64|string] []T type Slice2 []int type Slice3 []float64 type Slice4 []string // Map type Map1 [KEY int | string, VALUE string | float64] map[KEY]VALUE type Map2 map[int]string type Map3 map[int]float64 type Map4 map[string]string // 结构体 type Struct1 [T string|int|float64] struct { Title string Content T } type Struct2 struct { Title string Content string } type Struct3 struct { Title string Content int } type Struct4 struct { Title string Content float64 }
-
泛型约束
-
约束基于接口,是类型的集合
-
~T
表示一个类型集,包括所有基础类型为 T 的类型 -
并集:
type Ordered interface { Integer | Float | ~string }
-
交集:
type m interface { int | ~float64 float 64 }
-
预置类型
any
:预定义类型,空接口的别名comparable
:预定义类型(==
、!==
)constrains.Ordered
:预定义类型(<
、<=
、>=
、>
)
-
-
泛型实例化
-
类型
// 切片 type Slice1 [T int|float64|string] []T var MySlice Slice1[int] = []int{1, 2, 3} // Map type Map1 [KEY int | string, VALUE string | float64] map[KEY]VALUE var MyMap Map1[int, string] = map[int]string{ 1: "hello", 2: "world", } // 结构体 type Struct1 [T string|int|float64] struct { Title string Content T } var MyStruct = Struct1[string]{ Title "hello" Content "world" }
-
函数
func function[V comparable](vs ...V) bool { if len(vs) == 0 { return true } v := vs[0] for _, x := range vs[1:] { if v != x { return false } } return true } func main() { fmt.Println(function[string]("function", "Function")) fmt.Println(function[int](123, 456)) fmt.Println(function[bool]()) }
-
自动类型推断
func main() { fmt.Println(function("function", "Function")) }
-
-
-End-