完成
This commit is contained in:
967
golang-learning/09-advanced/01-reflection.go
Normal file
967
golang-learning/09-advanced/01-reflection.go
Normal file
@@ -0,0 +1,967 @@
|
||||
/*
|
||||
01-reflection.go - Go 语言反射详解
|
||||
|
||||
学习目标:
|
||||
1. 理解反射的概念和原理
|
||||
2. 掌握 reflect 包的基本使用
|
||||
3. 学会反射的类型和值操作
|
||||
4. 了解反射的应用场景
|
||||
5. 掌握反射的最佳实践和注意事项
|
||||
|
||||
知识点:
|
||||
- 反射的基本概念
|
||||
- reflect.Type 和 reflect.Value
|
||||
- 类型检查和转换
|
||||
- 结构体字段和方法的反射
|
||||
- 反射的性能考虑
|
||||
- 反射的实际应用
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("=== Go 语言反射详解 ===\\n")
|
||||
|
||||
// 演示反射的基本概念
|
||||
demonstrateReflectionBasics()
|
||||
|
||||
// 演示类型反射
|
||||
demonstrateTypeReflection()
|
||||
|
||||
// 演示值反射
|
||||
demonstrateValueReflection()
|
||||
|
||||
// 演示结构体反射
|
||||
demonstrateStructReflection()
|
||||
|
||||
// 演示方法反射
|
||||
demonstrateMethodReflection()
|
||||
|
||||
// 演示反射的实际应用
|
||||
demonstratePracticalApplications()
|
||||
|
||||
// 演示反射的最佳实践
|
||||
demonstrateBestPractices()
|
||||
}
|
||||
|
||||
// demonstrateReflectionBasics 演示反射的基本概念
|
||||
func demonstrateReflectionBasics() {
|
||||
fmt.Println("1. 反射的基本概念:")
|
||||
|
||||
// 反射的基本概念
|
||||
fmt.Printf(" 反射的基本概念:\\n")
|
||||
fmt.Printf(" - 反射是程序在运行时检查自身结构的能力\\n")
|
||||
fmt.Printf(" - Go 通过 reflect 包提供反射功能\\n")
|
||||
fmt.Printf(" - 反射基于接口的动态类型信息\\n")
|
||||
fmt.Printf(" - 主要包括类型反射和值反射\\n")
|
||||
fmt.Printf(" - 反射遵循 Go 的类型系统规则\\n")
|
||||
|
||||
// 反射的核心类型
|
||||
fmt.Printf(" 反射的核心类型:\\n")
|
||||
fmt.Printf(" - reflect.Type: 表示类型信息\\n")
|
||||
fmt.Printf(" - reflect.Value: 表示值信息\\n")
|
||||
fmt.Printf(" - reflect.Kind: 表示基础类型种类\\n")
|
||||
|
||||
// 基本反射示例
|
||||
fmt.Printf(" 基本反射示例:\\n")
|
||||
|
||||
var x interface{} = 42
|
||||
|
||||
// 获取类型信息
|
||||
t := reflect.TypeOf(x)
|
||||
fmt.Printf(" 类型: %v\\n", t)
|
||||
fmt.Printf(" 类型名称: %s\\n", t.Name())
|
||||
fmt.Printf(" 类型种类: %v\\n", t.Kind())
|
||||
|
||||
// 获取值信息
|
||||
v := reflect.ValueOf(x)
|
||||
fmt.Printf(" 值: %v\\n", v)
|
||||
fmt.Printf(" 值的类型: %v\\n", v.Type())
|
||||
fmt.Printf(" 值的种类: %v\\n", v.Kind())
|
||||
fmt.Printf(" 值的接口: %v\\n", v.Interface())
|
||||
|
||||
// 不同类型的反射
|
||||
fmt.Printf(" 不同类型的反射:\\n")
|
||||
|
||||
values := []interface{}{
|
||||
42,
|
||||
"hello",
|
||||
3.14,
|
||||
true,
|
||||
[]int{1, 2, 3},
|
||||
map[string]int{"a": 1},
|
||||
Person{Name: "Alice", Age: 30},
|
||||
}
|
||||
|
||||
for i, val := range values {
|
||||
t := reflect.TypeOf(val)
|
||||
v := reflect.ValueOf(val)
|
||||
fmt.Printf(" 值%d: %v (类型: %v, 种类: %v)\\n",
|
||||
i+1, val, t, v.Kind())
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateTypeReflection 演示类型反射
|
||||
func demonstrateTypeReflection() {
|
||||
fmt.Println("2. 类型反射:")
|
||||
|
||||
// 基本类型反射
|
||||
fmt.Printf(" 基本类型反射:\\n")
|
||||
|
||||
var i int = 42
|
||||
var s string = "hello"
|
||||
var f float64 = 3.14
|
||||
var b bool = true
|
||||
|
||||
types := []interface{}{i, s, f, b}
|
||||
for _, val := range types {
|
||||
t := reflect.TypeOf(val)
|
||||
fmt.Printf(" %v: 名称=%s, 种类=%v, 大小=%d字节\\n",
|
||||
val, t.Name(), t.Kind(), t.Size())
|
||||
}
|
||||
|
||||
// 复合类型反射
|
||||
fmt.Printf(" 复合类型反射:\\n")
|
||||
|
||||
// 切片类型
|
||||
slice := []int{1, 2, 3}
|
||||
sliceType := reflect.TypeOf(slice)
|
||||
fmt.Printf(" 切片类型: %v\\n", sliceType)
|
||||
fmt.Printf(" 元素类型: %v\\n", sliceType.Elem())
|
||||
fmt.Printf(" 是否为切片: %t\\n", sliceType.Kind() == reflect.Slice)
|
||||
|
||||
// 映射类型
|
||||
m := map[string]int{"a": 1, "b": 2}
|
||||
mapType := reflect.TypeOf(m)
|
||||
fmt.Printf(" 映射类型: %v\\n", mapType)
|
||||
fmt.Printf(" 键类型: %v\\n", mapType.Key())
|
||||
fmt.Printf(" 值类型: %v\\n", mapType.Elem())
|
||||
|
||||
// 指针类型
|
||||
var p *int = &i
|
||||
ptrType := reflect.TypeOf(p)
|
||||
fmt.Printf(" 指针类型: %v\\n", ptrType)
|
||||
fmt.Printf(" 指向类型: %v\\n", ptrType.Elem())
|
||||
fmt.Printf(" 是否为指针: %t\\n", ptrType.Kind() == reflect.Ptr)
|
||||
|
||||
// 函数类型
|
||||
fn := func(int, string) bool { return true }
|
||||
fnType := reflect.TypeOf(fn)
|
||||
fmt.Printf(" 函数类型: %v\\n", fnType)
|
||||
fmt.Printf(" 参数个数: %d\\n", fnType.NumIn())
|
||||
fmt.Printf(" 返回值个数: %d\\n", fnType.NumOut())
|
||||
|
||||
for i := 0; i < fnType.NumIn(); i++ {
|
||||
fmt.Printf(" 参数%d类型: %v\\n", i, fnType.In(i))
|
||||
}
|
||||
|
||||
for i := 0; i < fnType.NumOut(); i++ {
|
||||
fmt.Printf(" 返回值%d类型: %v\\n", i, fnType.Out(i))
|
||||
}
|
||||
|
||||
// 通道类型
|
||||
ch := make(chan int)
|
||||
chType := reflect.TypeOf(ch)
|
||||
fmt.Printf(" 通道类型: %v\\n", chType)
|
||||
fmt.Printf(" 通道方向: %v\\n", chType.ChanDir())
|
||||
fmt.Printf(" 元素类型: %v\\n", chType.Elem())
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateValueReflection 演示值反射
|
||||
func demonstrateValueReflection() {
|
||||
fmt.Println("3. 值反射:")
|
||||
|
||||
// 基本值操作
|
||||
fmt.Printf(" 基本值操作:\\n")
|
||||
|
||||
var x interface{} = 42
|
||||
v := reflect.ValueOf(x)
|
||||
|
||||
fmt.Printf(" 原始值: %v\\n", v.Interface())
|
||||
fmt.Printf(" 整数值: %d\\n", v.Int())
|
||||
fmt.Printf(" 是否有效: %t\\n", v.IsValid())
|
||||
fmt.Printf(" 是否为零值: %t\\n", v.IsZero())
|
||||
|
||||
// 值的修改
|
||||
fmt.Printf(" 值的修改:\\n")
|
||||
|
||||
var y int = 100
|
||||
v = reflect.ValueOf(&y) // 需要传递指针才能修改
|
||||
if v.Kind() == reflect.Ptr && v.Elem().CanSet() {
|
||||
v.Elem().SetInt(200)
|
||||
fmt.Printf(" 修改后的值: %d\\n", y)
|
||||
}
|
||||
|
||||
// 字符串值操作
|
||||
var str string = "hello"
|
||||
v = reflect.ValueOf(&str)
|
||||
if v.Kind() == reflect.Ptr && v.Elem().CanSet() {
|
||||
v.Elem().SetString("world")
|
||||
fmt.Printf(" 修改后的字符串: %s\\n", str)
|
||||
}
|
||||
|
||||
// 切片值操作
|
||||
fmt.Printf(" 切片值操作:\\n")
|
||||
|
||||
slice := []int{1, 2, 3}
|
||||
v = reflect.ValueOf(slice)
|
||||
|
||||
fmt.Printf(" 切片长度: %d\\n", v.Len())
|
||||
fmt.Printf(" 切片容量: %d\\n", v.Cap())
|
||||
|
||||
// 访问切片元素
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
elem := v.Index(i)
|
||||
fmt.Printf(" 元素[%d]: %v\\n", i, elem.Interface())
|
||||
}
|
||||
|
||||
// 映射值操作
|
||||
fmt.Printf(" 映射值操作:\\n")
|
||||
|
||||
m := map[string]int{"a": 1, "b": 2, "c": 3}
|
||||
v = reflect.ValueOf(m)
|
||||
|
||||
fmt.Printf(" 映射长度: %d\\n", v.Len())
|
||||
|
||||
// 遍历映射
|
||||
for _, key := range v.MapKeys() {
|
||||
value := v.MapIndex(key)
|
||||
fmt.Printf(" %v: %v\\n", key.Interface(), value.Interface())
|
||||
}
|
||||
|
||||
// 设置映射值
|
||||
v.SetMapIndex(reflect.ValueOf("d"), reflect.ValueOf(4))
|
||||
fmt.Printf(" 添加元素后: %v\\n", m)
|
||||
|
||||
// 创建新值
|
||||
fmt.Printf(" 创建新值:\\n")
|
||||
|
||||
// 创建新的整数值
|
||||
newInt := reflect.New(reflect.TypeOf(0))
|
||||
newInt.Elem().SetInt(999)
|
||||
fmt.Printf(" 新创建的整数: %v\\n", newInt.Elem().Interface())
|
||||
|
||||
// 创建新的切片
|
||||
sliceType := reflect.SliceOf(reflect.TypeOf(0))
|
||||
newSlice := reflect.MakeSlice(sliceType, 3, 5)
|
||||
for i := 0; i < newSlice.Len(); i++ {
|
||||
newSlice.Index(i).SetInt(int64(i * 10))
|
||||
}
|
||||
fmt.Printf(" 新创建的切片: %v\\n", newSlice.Interface())
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateStructReflection 演示结构体反射
|
||||
func demonstrateStructReflection() {
|
||||
fmt.Println("4. 结构体反射:")
|
||||
|
||||
// 结构体类型信息
|
||||
fmt.Printf(" 结构体类型信息:\\n")
|
||||
|
||||
person := Person{
|
||||
Name: "Alice",
|
||||
Age: 30,
|
||||
Email: "alice@example.com",
|
||||
}
|
||||
|
||||
t := reflect.TypeOf(person)
|
||||
v := reflect.ValueOf(person)
|
||||
|
||||
fmt.Printf(" 结构体名称: %s\\n", t.Name())
|
||||
fmt.Printf(" 字段数量: %d\\n", t.NumField())
|
||||
|
||||
// 遍历结构体字段
|
||||
fmt.Printf(" 遍历结构体字段:\\n")
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
value := v.Field(i)
|
||||
|
||||
fmt.Printf(" 字段%d: %s (类型: %v, 值: %v)\\n",
|
||||
i, field.Name, field.Type, value.Interface())
|
||||
|
||||
// 检查字段标签
|
||||
if tag := field.Tag; tag != "" {
|
||||
fmt.Printf(" 标签: %s\\n", tag)
|
||||
if jsonTag := tag.Get("json"); jsonTag != "" {
|
||||
fmt.Printf(" JSON标签: %s\\n", jsonTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 按名称访问字段
|
||||
fmt.Printf(" 按名称访问字段:\\n")
|
||||
|
||||
nameField := v.FieldByName("Name")
|
||||
if nameField.IsValid() {
|
||||
fmt.Printf(" Name字段值: %v\\n", nameField.Interface())
|
||||
}
|
||||
|
||||
// 修改结构体字段
|
||||
fmt.Printf(" 修改结构体字段:\\n")
|
||||
|
||||
// 需要使用指针才能修改
|
||||
v = reflect.ValueOf(&person)
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem() // 获取指针指向的值
|
||||
|
||||
nameField = v.FieldByName("Name")
|
||||
if nameField.CanSet() {
|
||||
nameField.SetString("Bob")
|
||||
fmt.Printf(" 修改后的Name: %s\\n", person.Name)
|
||||
}
|
||||
|
||||
ageField := v.FieldByName("Age")
|
||||
if ageField.CanSet() {
|
||||
ageField.SetInt(35)
|
||||
fmt.Printf(" 修改后的Age: %d\\n", person.Age)
|
||||
}
|
||||
}
|
||||
|
||||
// 嵌套结构体
|
||||
fmt.Printf(" 嵌套结构体:\\n")
|
||||
|
||||
employee := Employee{
|
||||
Person: Person{Name: "Charlie", Age: 28},
|
||||
Title: "Developer",
|
||||
Salary: 80000,
|
||||
}
|
||||
|
||||
t = reflect.TypeOf(employee)
|
||||
v = reflect.ValueOf(employee)
|
||||
|
||||
fmt.Printf(" 嵌套结构体字段数: %d\\n", t.NumField())
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
value := v.Field(i)
|
||||
|
||||
fmt.Printf(" 字段%d: %s (类型: %v)\\n", i, field.Name, field.Type)
|
||||
|
||||
// 如果是嵌套结构体,进一步展开
|
||||
if field.Type.Kind() == reflect.Struct {
|
||||
fmt.Printf(" 嵌套字段:\\n")
|
||||
nestedValue := value
|
||||
nestedType := field.Type
|
||||
|
||||
for j := 0; j < nestedType.NumField(); j++ {
|
||||
nestedField := nestedType.Field(j)
|
||||
nestedFieldValue := nestedValue.Field(j)
|
||||
fmt.Printf(" %s: %v\\n", nestedField.Name, nestedFieldValue.Interface())
|
||||
}
|
||||
} else {
|
||||
fmt.Printf(" 值: %v\\n", value.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateMethodReflection 演示方法反射
|
||||
func demonstrateMethodReflection() {
|
||||
fmt.Println("5. 方法反射:")
|
||||
|
||||
// 方法信息
|
||||
fmt.Printf(" 方法信息:\\n")
|
||||
|
||||
person := &Person{Name: "David", Age: 25}
|
||||
t := reflect.TypeOf(person)
|
||||
v := reflect.ValueOf(person)
|
||||
|
||||
fmt.Printf(" 方法数量: %d\\n", t.NumMethod())
|
||||
|
||||
// 遍历方法
|
||||
for i := 0; i < t.NumMethod(); i++ {
|
||||
method := t.Method(i)
|
||||
fmt.Printf(" 方法%d: %s (类型: %v)\\n", i, method.Name, method.Type)
|
||||
}
|
||||
|
||||
// 调用方法
|
||||
fmt.Printf(" 调用方法:\\n")
|
||||
|
||||
// 按名称获取方法
|
||||
greetMethod := v.MethodByName("Greet")
|
||||
if greetMethod.IsValid() {
|
||||
// 调用无参数方法
|
||||
result := greetMethod.Call(nil)
|
||||
if len(result) > 0 {
|
||||
fmt.Printf(" Greet方法返回: %v\\n", result[0].Interface())
|
||||
}
|
||||
}
|
||||
|
||||
// 调用带参数的方法
|
||||
setAgeMethod := v.MethodByName("SetAge")
|
||||
if setAgeMethod.IsValid() {
|
||||
args := []reflect.Value{reflect.ValueOf(30)}
|
||||
setAgeMethod.Call(args)
|
||||
fmt.Printf(" 调用SetAge后,年龄: %d\\n", person.Age)
|
||||
}
|
||||
|
||||
// 调用带返回值的方法
|
||||
getInfoMethod := v.MethodByName("GetInfo")
|
||||
if getInfoMethod.IsValid() {
|
||||
result := getInfoMethod.Call(nil)
|
||||
if len(result) > 0 {
|
||||
fmt.Printf(" GetInfo方法返回: %v\\n", result[0].Interface())
|
||||
}
|
||||
}
|
||||
|
||||
// 函数值的反射调用
|
||||
fmt.Printf(" 函数值的反射调用:\\n")
|
||||
|
||||
fn := func(a, b int) int {
|
||||
return a + b
|
||||
}
|
||||
|
||||
fnValue := reflect.ValueOf(fn)
|
||||
args := []reflect.Value{
|
||||
reflect.ValueOf(10),
|
||||
reflect.ValueOf(20),
|
||||
}
|
||||
|
||||
result := fnValue.Call(args)
|
||||
fmt.Printf(" 函数调用结果: %v\\n", result[0].Interface())
|
||||
|
||||
// 方法集的检查
|
||||
fmt.Printf(" 方法集的检查:\\n")
|
||||
|
||||
// 检查类型是否实现了某个接口
|
||||
stringerType := reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
|
||||
personType := reflect.TypeOf(person)
|
||||
|
||||
fmt.Printf(" Person是否实现Stringer接口: %t\\n",
|
||||
personType.Implements(stringerType))
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstratePracticalApplications 演示反射的实际应用
|
||||
func demonstratePracticalApplications() {
|
||||
fmt.Println("6. 反射的实际应用:")
|
||||
|
||||
// 应用1: JSON 序列化
|
||||
fmt.Printf(" 应用1 - JSON 序列化:\\n")
|
||||
|
||||
person := Person{
|
||||
Name: "Eve",
|
||||
Age: 32,
|
||||
Email: "eve@example.com",
|
||||
}
|
||||
|
||||
jsonStr := structToJSON(person)
|
||||
fmt.Printf(" JSON序列化结果: %s\\n", jsonStr)
|
||||
|
||||
// 应用2: 结构体复制
|
||||
fmt.Printf(" 应用2 - 结构体复制:\\n")
|
||||
|
||||
original := Person{Name: "Frank", Age: 40}
|
||||
copied := copyStruct(original).(Person)
|
||||
|
||||
fmt.Printf(" 原始结构体: %+v\\n", original)
|
||||
fmt.Printf(" 复制结构体: %+v\\n", copied)
|
||||
|
||||
// 应用3: 结构体比较
|
||||
fmt.Printf(" 应用3 - 结构体比较:\\n")
|
||||
|
||||
person1 := Person{Name: "Grace", Age: 28}
|
||||
person2 := Person{Name: "Grace", Age: 28}
|
||||
person3 := Person{Name: "Henry", Age: 30}
|
||||
|
||||
fmt.Printf(" person1 == person2: %t\\n", deepEqual(person1, person2))
|
||||
fmt.Printf(" person1 == person3: %t\\n", deepEqual(person1, person3))
|
||||
|
||||
// 应用4: 配置映射
|
||||
fmt.Printf(" 应用4 - 配置映射:\\n")
|
||||
|
||||
config := map[string]interface{}{
|
||||
"Name": "Iris",
|
||||
"Age": 26,
|
||||
"Email": "iris@example.com",
|
||||
}
|
||||
|
||||
var configPerson Person
|
||||
mapToStruct(config, &configPerson)
|
||||
fmt.Printf(" 配置映射结果: %+v\\n", configPerson)
|
||||
|
||||
// 应用5: 验证器
|
||||
fmt.Printf(" 应用5 - 验证器:\\n")
|
||||
|
||||
validPerson := Person{Name: "Jack", Age: 25, Email: "jack@example.com"}
|
||||
invalidPerson := Person{Name: "", Age: -5, Email: "invalid"}
|
||||
|
||||
fmt.Printf(" 有效Person验证: %t\\n", validateStruct(validPerson))
|
||||
fmt.Printf(" 无效Person验证: %t\\n", validateStruct(invalidPerson))
|
||||
|
||||
// 应用6: ORM 映射
|
||||
fmt.Printf(" 应用6 - ORM 映射:\\n")
|
||||
|
||||
tableName := getTableName(Person{})
|
||||
columns := getColumns(Person{})
|
||||
|
||||
fmt.Printf(" 表名: %s\\n", tableName)
|
||||
fmt.Printf(" 列名: %v\\n", columns)
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateBestPractices 演示反射的最佳实践
|
||||
func demonstrateBestPractices() {
|
||||
fmt.Println("7. 反射的最佳实践:")
|
||||
|
||||
// 最佳实践原则
|
||||
fmt.Printf(" 最佳实践原则:\\n")
|
||||
fmt.Printf(" 1. 谨慎使用反射,优先考虑类型安全的方案\\n")
|
||||
fmt.Printf(" 2. 缓存反射结果以提高性能\\n")
|
||||
fmt.Printf(" 3. 进行充分的错误检查\\n")
|
||||
fmt.Printf(" 4. 避免在热点路径使用反射\\n")
|
||||
fmt.Printf(" 5. 使用接口而不是反射来实现多态\\n")
|
||||
|
||||
// 性能考虑
|
||||
fmt.Printf(" 性能考虑:\\n")
|
||||
|
||||
// 性能测试示例
|
||||
person := Person{Name: "Test", Age: 30}
|
||||
|
||||
// 直接访问 vs 反射访问
|
||||
start := time.Now()
|
||||
for i := 0; i < 100000; i++ {
|
||||
_ = person.Name // 直接访问
|
||||
}
|
||||
directTime := time.Since(start)
|
||||
|
||||
start = time.Now()
|
||||
v := reflect.ValueOf(person)
|
||||
nameField := v.FieldByName("Name")
|
||||
for i := 0; i < 100000; i++ {
|
||||
_ = nameField.Interface() // 反射访问
|
||||
}
|
||||
reflectTime := time.Since(start)
|
||||
|
||||
fmt.Printf(" 直接访问耗时: %v\\n", directTime)
|
||||
fmt.Printf(" 反射访问耗时: %v\\n", reflectTime)
|
||||
fmt.Printf(" 性能差异: %.2fx\\n", float64(reflectTime)/float64(directTime))
|
||||
|
||||
// 错误处理
|
||||
fmt.Printf(" 错误处理:\\n")
|
||||
|
||||
// 安全的反射操作
|
||||
safeReflectOperation := func(obj interface{}, fieldName string) (interface{}, error) {
|
||||
v := reflect.ValueOf(obj)
|
||||
if v.Kind() != reflect.Struct {
|
||||
return nil, fmt.Errorf("对象不是结构体")
|
||||
}
|
||||
|
||||
field := v.FieldByName(fieldName)
|
||||
if !field.IsValid() {
|
||||
return nil, fmt.Errorf("字段 %s 不存在", fieldName)
|
||||
}
|
||||
|
||||
return field.Interface(), nil
|
||||
}
|
||||
|
||||
// 测试安全操作
|
||||
result, err := safeReflectOperation(person, "Name")
|
||||
if err != nil {
|
||||
fmt.Printf(" 错误: %v\\n", err)
|
||||
} else {
|
||||
fmt.Printf(" 安全获取Name字段: %v\\n", result)
|
||||
}
|
||||
|
||||
result, err = safeReflectOperation(person, "NonExistent")
|
||||
if err != nil {
|
||||
fmt.Printf(" 预期错误: %v\\n", err)
|
||||
}
|
||||
|
||||
// 反射的替代方案
|
||||
fmt.Printf(" 反射的替代方案:\\n")
|
||||
fmt.Printf(" 1. 接口: 使用接口实现多态\\n")
|
||||
fmt.Printf(" 2. 类型断言: 处理已知的有限类型集合\\n")
|
||||
fmt.Printf(" 3. 代码生成: 编译时生成类型安全的代码\\n")
|
||||
fmt.Printf(" 4. 泛型: Go 1.18+ 支持泛型编程\\n")
|
||||
|
||||
// 何时使用反射
|
||||
fmt.Printf(" 何时使用反射:\\n")
|
||||
fmt.Printf(" ✓ 序列化/反序列化库\\n")
|
||||
fmt.Printf(" ✓ ORM 框架\\n")
|
||||
fmt.Printf(" ✓ 配置映射\\n")
|
||||
fmt.Printf(" ✓ 测试框架\\n")
|
||||
fmt.Printf(" ✓ 依赖注入容器\\n")
|
||||
fmt.Printf("\\n")
|
||||
fmt.Printf(" ✗ 简单的类型转换\\n")
|
||||
fmt.Printf(" ✗ 已知类型的操作\\n")
|
||||
fmt.Printf(" ✗ 性能敏感的代码\\n")
|
||||
fmt.Printf(" ✗ 可以用接口解决的问题\\n")
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// ========== 类型定义 ==========
|
||||
|
||||
// Person 人员结构体
|
||||
type Person struct {
|
||||
Name string `json:"name" db:"name" validate:"required"`
|
||||
Age int `json:"age" db:"age" validate:"min=0,max=150"`
|
||||
Email string `json:"email" db:"email" validate:"email"`
|
||||
}
|
||||
|
||||
// Greet 问候方法
|
||||
func (p *Person) Greet() string {
|
||||
return fmt.Sprintf("Hello, I'm %s", p.Name)
|
||||
}
|
||||
|
||||
// SetAge 设置年龄
|
||||
func (p *Person) SetAge(age int) {
|
||||
p.Age = age
|
||||
}
|
||||
|
||||
// GetInfo 获取信息
|
||||
func (p *Person) GetInfo() string {
|
||||
return fmt.Sprintf("Name: %s, Age: %d, Email: %s", p.Name, p.Age, p.Email)
|
||||
}
|
||||
|
||||
// String 实现 Stringer 接口
|
||||
func (p Person) String() string {
|
||||
return fmt.Sprintf("Person{Name: %s, Age: %d}", p.Name, p.Age)
|
||||
}
|
||||
|
||||
// Employee 员工结构体
|
||||
type Employee struct {
|
||||
Person
|
||||
Title string `json:"title" db:"title"`
|
||||
Salary float64 `json:"salary" db:"salary"`
|
||||
}
|
||||
|
||||
// ========== 辅助函数 ==========
|
||||
|
||||
// structToJSON 将结构体转换为 JSON 字符串
|
||||
func structToJSON(obj interface{}) string {
|
||||
v := reflect.ValueOf(obj)
|
||||
t := reflect.TypeOf(obj)
|
||||
|
||||
if v.Kind() != reflect.Struct {
|
||||
return "{}"
|
||||
}
|
||||
|
||||
var parts []string
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
value := v.Field(i)
|
||||
|
||||
// 获取 JSON 标签
|
||||
jsonTag := field.Tag.Get("json")
|
||||
if jsonTag == "" {
|
||||
jsonTag = strings.ToLower(field.Name)
|
||||
}
|
||||
|
||||
// 根据类型格式化值
|
||||
var valueStr string
|
||||
switch value.Kind() {
|
||||
case reflect.String:
|
||||
valueStr = fmt.Sprintf("\\\"%s\\\"", value.String())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
valueStr = fmt.Sprintf("%d", value.Int())
|
||||
case reflect.Float32, reflect.Float64:
|
||||
valueStr = fmt.Sprintf("%.2f", value.Float())
|
||||
case reflect.Bool:
|
||||
valueStr = fmt.Sprintf("%t", value.Bool())
|
||||
default:
|
||||
valueStr = fmt.Sprintf("\\\"%v\\\"", value.Interface())
|
||||
}
|
||||
|
||||
parts = append(parts, fmt.Sprintf("\\\"%s\\\": %s", jsonTag, valueStr))
|
||||
}
|
||||
|
||||
return "{" + strings.Join(parts, ", ") + "}"
|
||||
}
|
||||
|
||||
// copyStruct 复制结构体
|
||||
func copyStruct(src interface{}) interface{} {
|
||||
srcValue := reflect.ValueOf(src)
|
||||
srcType := reflect.TypeOf(src)
|
||||
|
||||
if srcValue.Kind() != reflect.Struct {
|
||||
return src
|
||||
}
|
||||
|
||||
// 创建新的结构体实例
|
||||
newValue := reflect.New(srcType).Elem()
|
||||
|
||||
// 复制所有字段
|
||||
for i := 0; i < srcValue.NumField(); i++ {
|
||||
srcField := srcValue.Field(i)
|
||||
newField := newValue.Field(i)
|
||||
|
||||
if newField.CanSet() {
|
||||
newField.Set(srcField)
|
||||
}
|
||||
}
|
||||
|
||||
return newValue.Interface()
|
||||
}
|
||||
|
||||
// deepEqual 深度比较两个值
|
||||
func deepEqual(a, b interface{}) bool {
|
||||
va := reflect.ValueOf(a)
|
||||
vb := reflect.ValueOf(b)
|
||||
|
||||
if va.Type() != vb.Type() {
|
||||
return false
|
||||
}
|
||||
|
||||
switch va.Kind() {
|
||||
case reflect.Struct:
|
||||
for i := 0; i < va.NumField(); i++ {
|
||||
if !deepEqual(va.Field(i).Interface(), vb.Field(i).Interface()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Slice:
|
||||
if va.Len() != vb.Len() {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < va.Len(); i++ {
|
||||
if !deepEqual(va.Index(i).Interface(), vb.Index(i).Interface()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Map:
|
||||
if va.Len() != vb.Len() {
|
||||
return false
|
||||
}
|
||||
for _, key := range va.MapKeys() {
|
||||
aVal := va.MapIndex(key)
|
||||
bVal := vb.MapIndex(key)
|
||||
if !bVal.IsValid() || !deepEqual(aVal.Interface(), bVal.Interface()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
default:
|
||||
return va.Interface() == vb.Interface()
|
||||
}
|
||||
}
|
||||
|
||||
// mapToStruct 将 map 映射到结构体
|
||||
func mapToStruct(m map[string]interface{}, dst interface{}) error {
|
||||
dstValue := reflect.ValueOf(dst)
|
||||
if dstValue.Kind() != reflect.Ptr || dstValue.Elem().Kind() != reflect.Struct {
|
||||
return fmt.Errorf("dst 必须是结构体指针")
|
||||
}
|
||||
|
||||
dstValue = dstValue.Elem()
|
||||
dstType := dstValue.Type()
|
||||
|
||||
for i := 0; i < dstValue.NumField(); i++ {
|
||||
field := dstType.Field(i)
|
||||
fieldValue := dstValue.Field(i)
|
||||
|
||||
if !fieldValue.CanSet() {
|
||||
continue
|
||||
}
|
||||
|
||||
// 从 map 中获取对应的值
|
||||
if mapValue, ok := m[field.Name]; ok {
|
||||
mapValueReflect := reflect.ValueOf(mapValue)
|
||||
|
||||
// 类型转换
|
||||
if mapValueReflect.Type().ConvertibleTo(fieldValue.Type()) {
|
||||
fieldValue.Set(mapValueReflect.Convert(fieldValue.Type()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateStruct 验证结构体
|
||||
func validateStruct(obj interface{}) bool {
|
||||
v := reflect.ValueOf(obj)
|
||||
t := reflect.TypeOf(obj)
|
||||
|
||||
if v.Kind() != reflect.Struct {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
value := v.Field(i)
|
||||
|
||||
// 获取验证标签
|
||||
validateTag := field.Tag.Get("validate")
|
||||
if validateTag == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// 解析验证规则
|
||||
rules := strings.Split(validateTag, ",")
|
||||
for _, rule := range rules {
|
||||
if !validateField(value, strings.TrimSpace(rule)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// validateField 验证单个字段
|
||||
func validateField(value reflect.Value, rule string) bool {
|
||||
switch rule {
|
||||
case "required":
|
||||
return !value.IsZero()
|
||||
case "email":
|
||||
if value.Kind() == reflect.String {
|
||||
email := value.String()
|
||||
return strings.Contains(email, "@") && strings.Contains(email, ".")
|
||||
}
|
||||
return false
|
||||
default:
|
||||
// 处理 min=0, max=150 等规则
|
||||
if strings.HasPrefix(rule, "min=") {
|
||||
minStr := strings.TrimPrefix(rule, "min=")
|
||||
if min, err := strconv.Atoi(minStr); err == nil {
|
||||
if value.Kind() == reflect.Int {
|
||||
return value.Int() >= int64(min)
|
||||
}
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(rule, "max=") {
|
||||
maxStr := strings.TrimPrefix(rule, "max=")
|
||||
if max, err := strconv.Atoi(maxStr); err == nil {
|
||||
if value.Kind() == reflect.Int {
|
||||
return value.Int() <= int64(max)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// getTableName 获取表名
|
||||
func getTableName(obj interface{}) string {
|
||||
t := reflect.TypeOf(obj)
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
// 将结构体名转换为表名(简单的复数形式)
|
||||
name := t.Name()
|
||||
return strings.ToLower(name) + "s"
|
||||
}
|
||||
|
||||
// getColumns 获取列名
|
||||
func getColumns(obj interface{}) []string {
|
||||
t := reflect.TypeOf(obj)
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
var columns []string
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
|
||||
// 获取数据库标签
|
||||
dbTag := field.Tag.Get("db")
|
||||
if dbTag != "" {
|
||||
columns = append(columns, dbTag)
|
||||
} else {
|
||||
columns = append(columns, strings.ToLower(field.Name))
|
||||
}
|
||||
}
|
||||
|
||||
return columns
|
||||
}
|
||||
|
||||
/*
|
||||
运行这个程序:
|
||||
go run 01-reflection.go
|
||||
|
||||
学习要点:
|
||||
1. 反射是程序在运行时检查自身结构的能力
|
||||
2. Go 通过 reflect 包提供反射功能
|
||||
3. 反射主要包括类型反射和值反射
|
||||
4. 反射可以用于序列化、ORM、配置映射等场景
|
||||
5. 反射有性能开销,应该谨慎使用
|
||||
|
||||
反射的核心概念:
|
||||
1. reflect.Type: 表示类型信息
|
||||
2. reflect.Value: 表示值信息
|
||||
3. reflect.Kind: 表示基础类型种类
|
||||
4. 反射遵循 Go 的类型系统规则
|
||||
5. 反射可以检查和修改值
|
||||
|
||||
类型反射:
|
||||
1. 获取类型信息:reflect.TypeOf()
|
||||
2. 类型名称和种类:Name(), Kind()
|
||||
3. 复合类型:Elem(), Key(), In(), Out()
|
||||
4. 结构体字段:NumField(), Field()
|
||||
5. 方法信息:NumMethod(), Method()
|
||||
|
||||
值反射:
|
||||
1. 获取值信息:reflect.ValueOf()
|
||||
2. 值的访问:Interface(), Int(), String()
|
||||
3. 值的修改:Set(), SetInt(), SetString()
|
||||
4. 集合操作:Len(), Index(), MapIndex()
|
||||
5. 创建新值:New(), MakeSlice(), MakeMap()
|
||||
|
||||
结构体反射:
|
||||
1. 字段访问:Field(), FieldByName()
|
||||
2. 字段信息:Name, Type, Tag
|
||||
3. 字段修改:CanSet(), Set()
|
||||
4. 标签解析:Tag.Get()
|
||||
5. 嵌套结构体处理
|
||||
|
||||
方法反射:
|
||||
1. 方法信息:Method(), MethodByName()
|
||||
2. 方法调用:Call()
|
||||
3. 方法类型:Type, NumIn(), NumOut()
|
||||
4. 接口检查:Implements()
|
||||
5. 方法集分析
|
||||
|
||||
实际应用场景:
|
||||
1. JSON 序列化/反序列化
|
||||
2. ORM 数据库映射
|
||||
3. 配置文件映射
|
||||
4. 结构体验证
|
||||
5. 依赖注入
|
||||
6. 测试框架
|
||||
7. 代码生成工具
|
||||
|
||||
性能考虑:
|
||||
1. 反射比直接访问慢很多
|
||||
2. 缓存反射结果可以提高性能
|
||||
3. 避免在热点路径使用反射
|
||||
4. 考虑使用接口替代反射
|
||||
5. 编译时代码生成是更好的选择
|
||||
|
||||
最佳实践:
|
||||
1. 谨慎使用反射,优先考虑类型安全
|
||||
2. 进行充分的错误检查
|
||||
3. 缓存反射操作的结果
|
||||
4. 使用接口实现多态
|
||||
5. 考虑性能影响
|
||||
6. 提供清晰的文档说明
|
||||
|
||||
注意事项:
|
||||
1. 反射会破坏类型安全
|
||||
2. 反射代码难以理解和维护
|
||||
3. 反射错误只能在运行时发现
|
||||
4. 反射会影响代码的可读性
|
||||
5. 反射操作可能导致 panic
|
||||
*/
|
561
golang-learning/09-advanced/02-generics.go
Normal file
561
golang-learning/09-advanced/02-generics.go
Normal file
@@ -0,0 +1,561 @@
|
||||
/*
|
||||
02-generics.go - Go 语言泛型详解
|
||||
|
||||
学习目标:
|
||||
1. 理解泛型的概念和优势
|
||||
2. 掌握类型参数的定义和使用
|
||||
3. 学会类型约束的应用
|
||||
4. 了解泛型函数和泛型类型
|
||||
5. 掌握泛型的最佳实践
|
||||
|
||||
知识点:
|
||||
- 泛型的基本概念
|
||||
- 类型参数和类型约束
|
||||
- 泛型函数和泛型类型
|
||||
- 内置约束和自定义约束
|
||||
- 类型推断
|
||||
- 泛型的实际应用
|
||||
|
||||
注意:此示例需要 Go 1.18 或更高版本
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("=== Go 语言泛型详解 ===\\n")
|
||||
|
||||
// 演示泛型的基本概念
|
||||
demonstrateGenericsBasics()
|
||||
|
||||
// 演示泛型函数
|
||||
demonstrateGenericFunctions()
|
||||
|
||||
// 演示泛型类型
|
||||
demonstrateGenericTypes()
|
||||
|
||||
// 演示类型约束
|
||||
demonstrateTypeConstraints()
|
||||
|
||||
// 演示内置约束
|
||||
demonstrateBuiltinConstraints()
|
||||
|
||||
// 演示泛型的实际应用
|
||||
demonstratePracticalApplications()
|
||||
|
||||
// 演示泛型的最佳实践
|
||||
demonstrateBestPractices()
|
||||
}
|
||||
|
||||
// demonstrateGenericsBasics 演示泛型的基本概念
|
||||
func demonstrateGenericsBasics() {
|
||||
fmt.Println("1. 泛型的基本概念:")
|
||||
|
||||
// 泛型的基本概念
|
||||
fmt.Printf(" 泛型的基本概念:\\n")
|
||||
fmt.Printf(" - 泛型允许编写可重用的代码\\n")
|
||||
fmt.Printf(" - 类型参数在编译时确定具体类型\\n")
|
||||
fmt.Printf(" - 提供类型安全的抽象\\n")
|
||||
fmt.Printf(" - Go 1.18+ 支持泛型\\n")
|
||||
fmt.Printf(" - 减少代码重复和类型断言\\n")
|
||||
|
||||
// 泛型语法
|
||||
fmt.Printf(" 泛型语法:\\n")
|
||||
fmt.Printf(" 函数泛型: func Name[T any](param T) T\\n")
|
||||
fmt.Printf(" 类型泛型: type Name[T any] struct { field T }\\n")
|
||||
fmt.Printf(" 约束语法: func Name[T Constraint](param T) T\\n")
|
||||
|
||||
// 简单泛型函数示例
|
||||
fmt.Printf(" 简单泛型函数示例:\\n")
|
||||
|
||||
// 使用泛型函数
|
||||
fmt.Printf(" 整数最大值: %d\\n", Max(10, 20))
|
||||
fmt.Printf(" 浮点数最大值: %.2f\\n", Max(3.14, 2.71))
|
||||
fmt.Printf(" 字符串最大值: %s\\n", Max("apple", "banana"))
|
||||
|
||||
// 类型推断
|
||||
fmt.Printf(" 类型推断:\\n")
|
||||
fmt.Printf(" Go 编译器可以自动推断类型参数\\n")
|
||||
|
||||
// 显式指定类型参数
|
||||
result1 := Max[int](5, 8)
|
||||
fmt.Printf(" 显式指定类型: %d\\n", result1)
|
||||
|
||||
// 自动类型推断
|
||||
result2 := Max(5, 8)
|
||||
fmt.Printf(" 自动类型推断: %d\\n", result2)
|
||||
|
||||
// 泛型的优势
|
||||
fmt.Printf(" 泛型的优势:\\n")
|
||||
fmt.Printf(" 1. 类型安全:编译时检查类型\\n")
|
||||
fmt.Printf(" 2. 代码复用:一套代码支持多种类型\\n")
|
||||
fmt.Printf(" 3. 性能优化:避免运行时类型断言\\n")
|
||||
fmt.Printf(" 4. 可读性:减少重复代码\\n")
|
||||
fmt.Printf(" 5. 维护性:统一的逻辑实现\\n")
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateGenericFunctions 演示泛型函数
|
||||
func demonstrateGenericFunctions() {
|
||||
fmt.Println("2. 泛型函数:")
|
||||
|
||||
// 基本泛型函数
|
||||
fmt.Printf(" 基本泛型函数:\\n")
|
||||
|
||||
// 交换函数
|
||||
a, b := 10, 20
|
||||
fmt.Printf(" 交换前: a=%d, b=%d\\n", a, b)
|
||||
a, b = Swap(a, b)
|
||||
fmt.Printf(" 交换后: a=%d, b=%d\\n", a, b)
|
||||
|
||||
str1, str2 := "hello", "world"
|
||||
fmt.Printf(" 交换前: str1=%s, str2=%s\\n", str1, str2)
|
||||
str1, str2 = Swap(str1, str2)
|
||||
fmt.Printf(" 交换后: str1=%s, str2=%s\\n", str1, str2)
|
||||
|
||||
// 查找函数
|
||||
fmt.Printf(" 查找函数:\\n")
|
||||
|
||||
numbers := []int{1, 2, 3, 4, 5}
|
||||
index := Find(numbers, 3)
|
||||
fmt.Printf(" 在 %v 中查找 3: 索引 %d\\n", numbers, index)
|
||||
|
||||
words := []string{"apple", "banana", "cherry"}
|
||||
index = Find(words, "banana")
|
||||
fmt.Printf(" 在 %v 中查找 banana: 索引 %d\\n", words, index)
|
||||
|
||||
// 映射函数
|
||||
fmt.Printf(" 映射函数:\\n")
|
||||
|
||||
// 数字平方
|
||||
squares := Map([]int{1, 2, 3, 4, 5}, func(x int) int {
|
||||
return x * x
|
||||
})
|
||||
fmt.Printf(" 数字平方: %v\\n", squares)
|
||||
|
||||
// 字符串长度
|
||||
lengths := Map([]string{"go", "rust", "python"}, func(s string) int {
|
||||
return len(s)
|
||||
})
|
||||
fmt.Printf(" 字符串长度: %v\\n", lengths)
|
||||
|
||||
// 过滤函数
|
||||
fmt.Printf(" 过滤函数:\\n")
|
||||
|
||||
// 过滤偶数
|
||||
evens := Filter([]int{1, 2, 3, 4, 5, 6}, func(x int) bool {
|
||||
return x%2 == 0
|
||||
})
|
||||
fmt.Printf(" 偶数: %v\\n", evens)
|
||||
|
||||
// 过滤长字符串
|
||||
longWords := Filter([]string{"go", "rust", "python", "javascript"}, func(s string) bool {
|
||||
return len(s) > 4
|
||||
})
|
||||
fmt.Printf(" 长单词: %v\\n", longWords)
|
||||
|
||||
// 归约函数
|
||||
fmt.Printf(" 归约函数:\\n")
|
||||
|
||||
// 求和
|
||||
sum := Reduce([]int{1, 2, 3, 4, 5}, 0, func(acc, x int) int {
|
||||
return acc + x
|
||||
})
|
||||
fmt.Printf(" 数组求和: %d\\n", sum)
|
||||
|
||||
// 字符串连接
|
||||
concat := Reduce([]string{"Hello", " ", "World", "!"}, "", func(acc, s string) string {
|
||||
return acc + s
|
||||
})
|
||||
fmt.Printf(" 字符串连接: %s\\n", concat)
|
||||
|
||||
// 多类型参数函数
|
||||
fmt.Printf(" 多类型参数函数:\\n")
|
||||
|
||||
pairs := Zip([]int{1, 2, 3}, []string{"a", "b", "c"})
|
||||
fmt.Printf(" 配对结果: %v\\n", pairs)
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateGenericTypes 演示泛型类型
|
||||
func demonstrateGenericTypes() {
|
||||
fmt.Println("3. 泛型类型:")
|
||||
|
||||
// 泛型栈
|
||||
fmt.Printf(" 泛型栈:\\n")
|
||||
|
||||
// 整数栈
|
||||
intStack := NewStack[int]()
|
||||
intStack.Push(1)
|
||||
intStack.Push(2)
|
||||
intStack.Push(3)
|
||||
|
||||
fmt.Printf(" 整数栈大小: %d\\n", intStack.Size())
|
||||
|
||||
for !intStack.IsEmpty() {
|
||||
value, _ := intStack.Pop()
|
||||
fmt.Printf(" 弹出: %d\\n", value)
|
||||
}
|
||||
|
||||
// 字符串栈
|
||||
strStack := NewStack[string]()
|
||||
strStack.Push("first")
|
||||
strStack.Push("second")
|
||||
strStack.Push("third")
|
||||
|
||||
fmt.Printf(" 字符串栈内容:\\n")
|
||||
for !strStack.IsEmpty() {
|
||||
value, _ := strStack.Pop()
|
||||
fmt.Printf(" %s\\n", value)
|
||||
}
|
||||
|
||||
// 泛型队列
|
||||
fmt.Printf(" 泛型队列:\\n")
|
||||
|
||||
queue := NewQueue[string]()
|
||||
queue.Enqueue("first")
|
||||
queue.Enqueue("second")
|
||||
queue.Enqueue("third")
|
||||
|
||||
fmt.Printf(" 队列大小: %d\\n", queue.Size())
|
||||
|
||||
for !queue.IsEmpty() {
|
||||
value, _ := queue.Dequeue()
|
||||
fmt.Printf(" 出队: %s\\n", value)
|
||||
}
|
||||
|
||||
// 泛型映射
|
||||
fmt.Printf(" 泛型映射:\\n")
|
||||
|
||||
cache := NewCache[string, int]()
|
||||
cache.Set("apple", 5)
|
||||
cache.Set("banana", 3)
|
||||
cache.Set("cherry", 8)
|
||||
|
||||
if value, ok := cache.Get("apple"); ok {
|
||||
fmt.Printf(" apple 的值: %d\\n", value)
|
||||
}
|
||||
|
||||
fmt.Printf(" 缓存大小: %d\\n", cache.Size())
|
||||
fmt.Printf(" 所有键: %v\\n", cache.Keys())
|
||||
|
||||
// 泛型链表
|
||||
fmt.Printf(" 泛型链表:\\n")
|
||||
|
||||
list := NewLinkedList[int]()
|
||||
list.Add(1)
|
||||
list.Add(2)
|
||||
list.Add(3)
|
||||
|
||||
fmt.Printf(" 链表大小: %d\\n", list.Size())
|
||||
fmt.Printf(" 链表内容: %v\\n", list.ToSlice())
|
||||
|
||||
if list.Contains(2) {
|
||||
fmt.Printf(" 链表包含 2\\n")
|
||||
}
|
||||
|
||||
list.Remove(2)
|
||||
fmt.Printf(" 删除 2 后: %v\\n", list.ToSlice())
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateTypeConstraints 演示类型约束
|
||||
func demonstrateTypeConstraints() {
|
||||
fmt.Println("4. 类型约束:")
|
||||
|
||||
// 基本约束
|
||||
fmt.Printf(" 基本约束:\\n")
|
||||
fmt.Printf(" any: 任意类型\\n")
|
||||
fmt.Printf(" comparable: 可比较类型\\n")
|
||||
|
||||
// 自定义约束
|
||||
fmt.Printf(" 自定义约束:\\n")
|
||||
|
||||
// 数值类型约束
|
||||
fmt.Printf(" 数值类型约束:\\n")
|
||||
fmt.Printf(" 整数求和: %d\\n", Sum([]int{1, 2, 3, 4, 5}))
|
||||
fmt.Printf(" 浮点数求和: %.2f\\n", Sum([]float64{1.1, 2.2, 3.3}))
|
||||
|
||||
// 有序类型约束
|
||||
fmt.Printf(" 有序类型约束:\\n")
|
||||
|
||||
intSlice := []int{3, 1, 4, 1, 5, 9}
|
||||
fmt.Printf(" 排序前: %v\\n", intSlice)
|
||||
SortSlice(intSlice)
|
||||
fmt.Printf(" 排序后: %v\\n", intSlice)
|
||||
|
||||
strSlice := []string{"banana", "apple", "cherry"}
|
||||
fmt.Printf(" 排序前: %v\\n", strSlice)
|
||||
SortSlice(strSlice)
|
||||
fmt.Printf(" 排序后: %v\\n", strSlice)
|
||||
|
||||
// 字符串化约束
|
||||
fmt.Printf(" 字符串化约束:\\n")
|
||||
|
||||
fmt.Printf(" 整数转字符串: %s\\n", ToString(42))
|
||||
fmt.Printf(" 浮点数转字符串: %s\\n", ToString(3.14))
|
||||
fmt.Printf(" 布尔值转字符串: %s\\n", ToString(true))
|
||||
|
||||
// 接口约束
|
||||
fmt.Printf(" 接口约束:\\n")
|
||||
|
||||
shapes := []Shape{
|
||||
Rectangle{Width: 5, Height: 3},
|
||||
Circle{Radius: 4},
|
||||
}
|
||||
|
||||
totalArea := CalculateTotalArea(shapes)
|
||||
fmt.Printf(" 总面积: %.2f\\n", totalArea)
|
||||
|
||||
// 类型集合约束
|
||||
fmt.Printf(" 类型集合约束:\\n")
|
||||
|
||||
fmt.Printf(" 整数绝对值: %d\\n", Abs(-42))
|
||||
fmt.Printf(" 浮点数绝对值: %.2f\\n", Abs(-3.14))
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateBuiltinConstraints 演示内置约束
|
||||
func demonstrateBuiltinConstraints() {
|
||||
fmt.Println("5. 内置约束:")
|
||||
|
||||
// comparable 约束
|
||||
fmt.Printf(" comparable 约束:\\n")
|
||||
|
||||
// 可比较类型的相等检查
|
||||
fmt.Printf(" 整数相等: %t\\n", Equal(5, 5))
|
||||
fmt.Printf(" 字符串相等: %t\\n", Equal("hello", "hello"))
|
||||
fmt.Printf(" 布尔值相等: %t\\n", Equal(true, false))
|
||||
|
||||
// 可比较类型的去重
|
||||
duplicateInts := []int{1, 2, 2, 3, 3, 3, 4}
|
||||
uniqueInts := Unique(duplicateInts)
|
||||
fmt.Printf(" 整数去重: %v -> %v\\n", duplicateInts, uniqueInts)
|
||||
|
||||
duplicateStrs := []string{"a", "b", "b", "c", "c", "c"}
|
||||
uniqueStrs := Unique(duplicateStrs)
|
||||
fmt.Printf(" 字符串去重: %v -> %v\\n", duplicateStrs, uniqueStrs)
|
||||
|
||||
// any 约束
|
||||
fmt.Printf(" any 约束:\\n")
|
||||
|
||||
// 任意类型的容器
|
||||
container := NewContainer[any]()
|
||||
container.Add(42)
|
||||
container.Add("hello")
|
||||
container.Add(3.14)
|
||||
container.Add(true)
|
||||
|
||||
fmt.Printf(" 容器大小: %d\\n", container.Size())
|
||||
fmt.Printf(" 容器内容:\\n")
|
||||
for i := 0; i < container.Size(); i++ {
|
||||
item := container.Get(i)
|
||||
fmt.Printf(" [%d]: %v (%T)\\n", i, item, item)
|
||||
}
|
||||
|
||||
// 类型断言与泛型
|
||||
fmt.Printf(" 类型断言与泛型:\\n")
|
||||
|
||||
values := []any{42, "hello", 3.14, true}
|
||||
|
||||
// 提取特定类型
|
||||
strings := ExtractType[string](values)
|
||||
fmt.Printf(" 提取字符串: %v\\n", strings)
|
||||
|
||||
numbers := ExtractType[int](values)
|
||||
fmt.Printf(" 提取整数: %v\\n", numbers)
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstratePracticalApplications 演示泛型的实际应用
|
||||
func demonstratePracticalApplications() {
|
||||
fmt.Println("6. 泛型的实际应用:")
|
||||
|
||||
// 应用1: 泛型数据结构
|
||||
fmt.Printf(" 应用1 - 泛型数据结构:\\n")
|
||||
|
||||
// 优先队列
|
||||
pq := NewPriorityQueue[int]()
|
||||
pq.Push(3, 3)
|
||||
pq.Push(1, 1)
|
||||
pq.Push(4, 4)
|
||||
pq.Push(2, 2)
|
||||
|
||||
fmt.Printf(" 优先队列出队顺序:\\n")
|
||||
for !pq.IsEmpty() {
|
||||
item, priority := pq.Pop()
|
||||
fmt.Printf(" 项目: %v, 优先级: %d\\n", item, priority)
|
||||
}
|
||||
|
||||
// 应用2: 泛型算法
|
||||
fmt.Printf(" 应用2 - 泛型算法:\\n")
|
||||
|
||||
// 二分查找
|
||||
sortedInts := []int{1, 3, 5, 7, 9, 11, 13}
|
||||
index := BinarySearch(sortedInts, 7)
|
||||
fmt.Printf(" 二分查找 7 在 %v 中的位置: %d\\n", sortedInts, index)
|
||||
|
||||
sortedStrs := []string{"apple", "banana", "cherry", "date"}
|
||||
index = BinarySearch(sortedStrs, "cherry")
|
||||
fmt.Printf(" 二分查找 cherry 在 %v 中的位置: %d\\n", sortedStrs, index)
|
||||
|
||||
// 应用3: 泛型工具函数
|
||||
fmt.Printf(" 应用3 - 泛型工具函数:\\n")
|
||||
|
||||
// 切片操作
|
||||
original := []int{1, 2, 3, 4, 5}
|
||||
|
||||
// 反转
|
||||
reversed := Reverse(original)
|
||||
fmt.Printf(" 反转: %v -> %v\\n", original, reversed)
|
||||
|
||||
// 分块
|
||||
chunks := Chunk(original, 2)
|
||||
fmt.Printf(" 分块(大小2): %v -> %v\\n", original, chunks)
|
||||
|
||||
// 去重并排序
|
||||
unsorted := []int{3, 1, 4, 1, 5, 9, 2, 6, 5}
|
||||
uniqueSorted := UniqueAndSort(unsorted)
|
||||
fmt.Printf(" 去重排序: %v -> %v\\n", unsorted, uniqueSorted)
|
||||
|
||||
// 应用4: 泛型缓存
|
||||
fmt.Printf(" 应用4 - 泛型缓存:\\n")
|
||||
|
||||
lruCache := NewLRUCache[string, string](3)
|
||||
lruCache.Put("a", "apple")
|
||||
lruCache.Put("b", "banana")
|
||||
lruCache.Put("c", "cherry")
|
||||
|
||||
fmt.Printf(" 缓存状态: %v\\n", lruCache.Keys())
|
||||
|
||||
// 访问会更新顺序
|
||||
if value, ok := lruCache.Get("a"); ok {
|
||||
fmt.Printf(" 获取 a: %s\\n", value)
|
||||
}
|
||||
|
||||
// 添加新项会淘汰最久未使用的
|
||||
lruCache.Put("d", "date")
|
||||
fmt.Printf(" 添加 d 后: %v\\n", lruCache.Keys())
|
||||
|
||||
// 应用5: 泛型验证器
|
||||
fmt.Printf(" 应用5 - 泛型验证器:\\n")
|
||||
|
||||
validator := NewValidator[User]()
|
||||
|
||||
// 添加验证规则
|
||||
validator.AddRule(\"name\", func(u User) bool {
|
||||
return len(u.Name) > 0
|
||||
}, \"姓名不能为空\")
|
||||
|
||||
validator.AddRule(\"age\", func(u User) bool {
|
||||
return u.Age >= 0 && u.Age <= 150
|
||||
}, \"年龄必须在0-150之间\")
|
||||
|
||||
validator.AddRule(\"email\", func(u User) bool {
|
||||
return strings.Contains(u.Email, \"@\")
|
||||
}, \"邮箱格式不正确\")
|
||||
|
||||
// 验证用户
|
||||
validUser := User{Name: \"Alice\", Age: 25, Email: \"alice@example.com\"}
|
||||
invalidUser := User{Name: \"\", Age: -5, Email: \"invalid\"}
|
||||
|
||||
if errors := validator.Validate(validUser); len(errors) == 0 {
|
||||
fmt.Printf(" 有效用户验证通过\\n")
|
||||
} else {
|
||||
fmt.Printf(" 有效用户验证失败: %v\\n", errors)
|
||||
}
|
||||
|
||||
if errors := validator.Validate(invalidUser); len(errors) > 0 {
|
||||
fmt.Printf(" 无效用户验证失败: %v\\n", errors)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateBestPractices 演示泛型的最佳实践
|
||||
func demonstrateBestPractices() {
|
||||
fmt.Println("7. 泛型的最佳实践:")
|
||||
|
||||
// 最佳实践原则
|
||||
fmt.Printf(" 最佳实践原则:\\n")
|
||||
fmt.Printf(" 1. 优先使用具体类型,必要时才使用泛型\\n")
|
||||
fmt.Printf(" 2. 使用有意义的类型参数名称\\n")
|
||||
fmt.Printf(" 3. 合理使用类型约束\\n")
|
||||
fmt.Printf(" 4. 避免过度泛型化\\n")
|
||||
fmt.Printf(" 5. 考虑编译时间和代码复杂度\\n")
|
||||
|
||||
// 命名约定
|
||||
fmt.Printf(" 命名约定:\\n")
|
||||
fmt.Printf(" - T: 通用类型参数\\n")
|
||||
fmt.Printf(" - K, V: 键值对类型\\n")
|
||||
fmt.Printf(" - E: 元素类型\\n")
|
||||
fmt.Printf(" - R: 结果类型\\n")
|
||||
fmt.Printf(" - 使用描述性名称: TKey, TValue, TElement\\n")
|
||||
|
||||
// 约束设计
|
||||
fmt.Printf(" 约束设计:\\n")
|
||||
fmt.Printf(" - 使用最小必要约束\\n")
|
||||
fmt.Printf(" - 优先使用内置约束\\n")
|
||||
fmt.Printf(" - 自定义约束要有明确语义\\n")
|
||||
fmt.Printf(" - 避免过于复杂的约束\\n")
|
||||
|
||||
// 性能考虑
|
||||
fmt.Printf(" 性能考虑:\\n")
|
||||
|
||||
// 泛型 vs 接口性能对比
|
||||
fmt.Printf(" 泛型 vs 接口性能对比:\\n")
|
||||
|
||||
// 泛型版本
|
||||
start := time.Now()
|
||||
genericSum := 0
|
||||
for i := 0; i < 1000000; i++ {
|
||||
genericSum = Add(genericSum, 1)
|
||||
}
|
||||
genericTime := time.Since(start)
|
||||
|
||||
// 接口版本
|
||||
start = time.Now()
|
||||
var interfaceSum Addable = IntValue(0)
|
||||
for i := 0; i < 1000000; i++ {
|
||||
interfaceSum = interfaceSum.Add(IntValue(1))
|
||||
}
|
||||
interfaceTime := time.Since(start)
|
||||
|
||||
fmt.Printf(" 泛型版本耗时: %v\\n", genericTime)
|
||||
fmt.Printf(" 接口版本耗时: %v\\n", interfaceTime)
|
||||
fmt.Printf(" 性能提升: %.2fx\\n", float64(interfaceTime)/float64(genericTime))
|
||||
|
||||
// 何时使用泛型
|
||||
fmt.Printf(" 何时使用泛型:\\n")
|
||||
fmt.Printf(" ✓ 数据结构实现\\n")
|
||||
fmt.Printf(" ✓ 算法函数\\n")
|
||||
fmt.Printf(" ✓ 工具函数\\n")
|
||||
fmt.Printf(" ✓ 类型安全的容器\\n")
|
||||
fmt.Printf(" ✓ 减少代码重复\\n")
|
||||
fmt.Printf("\\n")
|
||||
fmt.Printf(" ✗ 简单的业务逻辑\\n")
|
||||
fmt.Printf(" ✗ 只有一种类型的场景\\n")
|
||||
fmt.Printf(" ✗ 过度抽象的设计\\n")
|
||||
fmt.Printf(" ✗ 性能敏感且类型固定的代码\\n")
|
||||
|
||||
// 迁移策略
|
||||
fmt.Printf(" 迁移策略:\\n")
|
||||
fmt.Printf(" 1. 识别重复代码模式\\n")
|
||||
fmt.Printf(" 2. 从简单场景开始\\n")
|
||||
fmt.Printf(" 3. 逐步替换类型断言\\n")
|
||||
fmt.Printf(" 4. 保持向后兼容\\n")
|
||||
fmt.Printf(" 5. 充分测试泛型代码\\n")
|
||||
|
||||
fmt.Println()
|
||||
}
|
765
golang-learning/09-advanced/03-context.go
Normal file
765
golang-learning/09-advanced/03-context.go
Normal file
@@ -0,0 +1,765 @@
|
||||
/*
|
||||
03-context.go - Go 语言 Context 包详解
|
||||
学习目标:
|
||||
1. 理解 Context 的概念和作用
|
||||
2. 掌握 Context 的创建和使用方法
|
||||
3. 学会在并发程序中传递取消信号
|
||||
4. 了解 Context 的超时和截止时间机制
|
||||
5. 掌握 Context 的最佳实践
|
||||
知识点:
|
||||
- Context 接口和实现
|
||||
- WithCancel, WithTimeout, WithDeadline
|
||||
- WithValue 传递请求范围的数据
|
||||
- Context 在 HTTP 服务中的应用
|
||||
- Context 的传播和继承
|
||||
- 避免 Context 的常见陷阱
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("=== Go 语言 Context 包详解 ===\n")
|
||||
|
||||
// 演示 Context 的基本概念
|
||||
demonstrateContextBasics()
|
||||
|
||||
// 演示取消 Context
|
||||
demonstrateCancellation()
|
||||
|
||||
// 演示超时 Context
|
||||
demonstrateTimeout()
|
||||
|
||||
// 演示截止时间 Context
|
||||
demonstrateDeadline()
|
||||
|
||||
// 演示 Context 传递值
|
||||
demonstrateWithValue()
|
||||
|
||||
// 演示 Context 在并发中的应用
|
||||
demonstrateConcurrencyWithContext()
|
||||
|
||||
// 演示 Context 在 HTTP 中的应用
|
||||
demonstrateHTTPContext()
|
||||
|
||||
// 演示 Context 的最佳实践
|
||||
demonstrateContextBestPractices()
|
||||
}
|
||||
|
||||
// demonstrateContextBasics 演示 Context 的基本概念
|
||||
func demonstrateContextBasics() {
|
||||
fmt.Println("1. Context 的基本概念:")
|
||||
|
||||
// Context 的概念
|
||||
fmt.Printf(" Context 的概念:\n")
|
||||
fmt.Printf(" - Context 是 Go 语言中用于传递请求范围数据的标准方式\n")
|
||||
fmt.Printf(" - 提供取消信号、超时控制和请求范围值传递\n")
|
||||
fmt.Printf(" - 在 goroutine 之间传递取消信号和截止时间\n")
|
||||
fmt.Printf(" - 避免 goroutine 泄漏和资源浪费\n")
|
||||
|
||||
// Context 接口
|
||||
fmt.Printf(" Context 接口:\n")
|
||||
fmt.Printf(" type Context interface {\n")
|
||||
fmt.Printf(" Deadline() (deadline time.Time, ok bool)\n")
|
||||
fmt.Printf(" Done() <-chan struct{}\n")
|
||||
fmt.Printf(" Err() error\n")
|
||||
fmt.Printf(" Value(key interface{}) interface{}\n")
|
||||
fmt.Printf(" }\n")
|
||||
|
||||
// 创建根 Context
|
||||
fmt.Printf(" 创建根 Context:\n")
|
||||
|
||||
// Background Context
|
||||
bgCtx := context.Background()
|
||||
fmt.Printf(" Background Context: %v\n", bgCtx)
|
||||
fmt.Printf(" - 通常用作根 Context\n")
|
||||
fmt.Printf(" - 永远不会被取消,没有值,没有截止时间\n")
|
||||
|
||||
// TODO Context
|
||||
todoCtx := context.TODO()
|
||||
fmt.Printf(" TODO Context: %v\n", todoCtx)
|
||||
fmt.Printf(" - 当不确定使用哪个 Context 时使用\n")
|
||||
fmt.Printf(" - 通常在重构时作为占位符\n")
|
||||
|
||||
// Context 的方法
|
||||
fmt.Printf(" Context 的方法:\n")
|
||||
deadline, ok := bgCtx.Deadline()
|
||||
fmt.Printf(" Deadline(): %v, %t (是否有截止时间)\n", deadline, ok)
|
||||
fmt.Printf(" Done(): %v (取消通道)\n", bgCtx.Done())
|
||||
fmt.Printf(" Err(): %v (错误信息)\n", bgCtx.Err())
|
||||
fmt.Printf(" Value(key): %v (获取值)\n", bgCtx.Value("key"))
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateCancellation 演示取消 Context
|
||||
func demonstrateCancellation() {
|
||||
fmt.Println("2. 取消 Context:")
|
||||
|
||||
// WithCancel 的使用
|
||||
fmt.Printf(" WithCancel 的使用:\n")
|
||||
fmt.Printf(" ctx, cancel := context.WithCancel(context.Background())\n")
|
||||
fmt.Printf(" defer cancel() // 确保释放资源\n")
|
||||
|
||||
// 创建可取消的 Context
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// 启动一个 goroutine 监听取消信号
|
||||
fmt.Printf(" 启动监听取消信号的 goroutine:\n")
|
||||
go func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
fmt.Printf(" goroutine 收到取消信号: %v\n", ctx.Err())
|
||||
case <-time.After(5 * time.Second):
|
||||
fmt.Printf(" goroutine 超时退出\n")
|
||||
}
|
||||
}()
|
||||
|
||||
// 模拟一些工作
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
fmt.Printf(" 执行取消操作...\n")
|
||||
cancel() // 发送取消信号
|
||||
|
||||
// 等待 goroutine 处理取消信号
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// 检查 Context 状态
|
||||
fmt.Printf(" Context 状态:\n")
|
||||
fmt.Printf(" Done(): %v\n", ctx.Done() != nil)
|
||||
fmt.Printf(" Err(): %v\n", ctx.Err())
|
||||
|
||||
// 演示取消传播
|
||||
fmt.Printf(" 取消传播:\n")
|
||||
parentCtx, parentCancel := context.WithCancel(context.Background())
|
||||
childCtx, childCancel := context.WithCancel(parentCtx)
|
||||
defer parentCancel()
|
||||
defer childCancel()
|
||||
|
||||
// 取消父 Context
|
||||
parentCancel()
|
||||
|
||||
// 检查子 Context 是否也被取消
|
||||
select {
|
||||
case <-childCtx.Done():
|
||||
fmt.Printf(" 子 Context 也被取消了: %v\n", childCtx.Err())
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
fmt.Printf(" 子 Context 没有被取消\n")
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateTimeout 演示超时 Context
|
||||
func demonstrateTimeout() {
|
||||
fmt.Println("3. 超时 Context:")
|
||||
|
||||
// WithTimeout 的使用
|
||||
fmt.Printf(" WithTimeout 的使用:\n")
|
||||
fmt.Printf(" ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n")
|
||||
fmt.Printf(" defer cancel()\n")
|
||||
|
||||
// 创建超时 Context
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
// 模拟长时间运行的操作
|
||||
fmt.Printf(" 模拟长时间操作:\n")
|
||||
start := time.Now()
|
||||
|
||||
select {
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
fmt.Printf(" 操作完成,耗时: %v\n", time.Since(start))
|
||||
case <-ctx.Done():
|
||||
fmt.Printf(" 操作被超时取消,耗时: %v,错误: %v\n", time.Since(start), ctx.Err())
|
||||
}
|
||||
|
||||
// 演示超时处理函数
|
||||
fmt.Printf(" 超时处理函数示例:\n")
|
||||
result, err := doWorkWithTimeout(300 * time.Millisecond)
|
||||
if err != nil {
|
||||
fmt.Printf(" 工作超时: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" 工作完成: %s\n", result)
|
||||
}
|
||||
|
||||
result, err = doWorkWithTimeout(100 * time.Millisecond)
|
||||
if err != nil {
|
||||
fmt.Printf(" 工作超时: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" 工作完成: %s\n", result)
|
||||
}
|
||||
|
||||
// 演示 HTTP 请求超时
|
||||
fmt.Printf(" HTTP 请求超时示例:\n")
|
||||
err = makeHTTPRequestWithTimeout("https://httpbin.org/delay/1", 500*time.Millisecond)
|
||||
if err != nil {
|
||||
fmt.Printf(" HTTP 请求失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" HTTP 请求成功\n")
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateDeadline 演示截止时间 Context
|
||||
func demonstrateDeadline() {
|
||||
fmt.Println("4. 截止时间 Context:")
|
||||
|
||||
// WithDeadline 的使用
|
||||
fmt.Printf(" WithDeadline 的使用:\n")
|
||||
deadline := time.Now().Add(200 * time.Millisecond)
|
||||
fmt.Printf(" deadline := time.Now().Add(200 * time.Millisecond)\n")
|
||||
fmt.Printf(" ctx, cancel := context.WithDeadline(context.Background(), deadline)\n")
|
||||
|
||||
ctx, cancel := context.WithDeadline(context.Background(), deadline)
|
||||
defer cancel()
|
||||
|
||||
// 检查截止时间
|
||||
ctxDeadline, ok := ctx.Deadline()
|
||||
fmt.Printf(" Context 截止时间:\n")
|
||||
fmt.Printf(" 截止时间: %v\n", ctxDeadline)
|
||||
fmt.Printf(" 有截止时间: %t\n", ok)
|
||||
fmt.Printf(" 距离截止时间: %v\n", time.Until(ctxDeadline))
|
||||
|
||||
// 等待截止时间到达
|
||||
fmt.Printf(" 等待截止时间到达:\n")
|
||||
start := time.Now()
|
||||
<-ctx.Done()
|
||||
fmt.Printf(" Context 在 %v 后被取消,错误: %v\n", time.Since(start), ctx.Err())
|
||||
|
||||
// 演示截止时间检查
|
||||
fmt.Printf(" 截止时间检查示例:\n")
|
||||
checkDeadline(context.Background())
|
||||
|
||||
deadlineCtx, deadlineCancel := context.WithDeadline(context.Background(), time.Now().Add(100*time.Millisecond))
|
||||
defer deadlineCancel()
|
||||
checkDeadline(deadlineCtx)
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateWithValue 演示 Context 传递值
|
||||
func demonstrateWithValue() {
|
||||
fmt.Println("5. Context 传递值:")
|
||||
|
||||
// WithValue 的使用
|
||||
fmt.Printf(" WithValue 的使用:\n")
|
||||
fmt.Printf(" ctx := context.WithValue(context.Background(), \\\"userID\\\", 12345)\n")
|
||||
|
||||
// 创建带值的 Context
|
||||
ctx := context.WithValue(context.Background(), "userID", 12345)
|
||||
ctx = context.WithValue(ctx, "requestID", "req-abc-123")
|
||||
ctx = context.WithValue(ctx, "traceID", "trace-xyz-789")
|
||||
|
||||
// 获取值
|
||||
fmt.Printf(" 获取 Context 中的值:\n")
|
||||
if userID := ctx.Value("userID"); userID != nil {
|
||||
fmt.Printf(" 用户ID: %v\n", userID)
|
||||
}
|
||||
if requestID := ctx.Value("requestID"); requestID != nil {
|
||||
fmt.Printf(" 请求ID: %v\n", requestID)
|
||||
}
|
||||
if traceID := ctx.Value("traceID"); traceID != nil {
|
||||
fmt.Printf(" 追踪ID: %v\n", traceID)
|
||||
}
|
||||
|
||||
// 值不存在的情况
|
||||
if sessionID := ctx.Value("sessionID"); sessionID != nil {
|
||||
fmt.Printf(" 会话ID: %v\n", sessionID)
|
||||
} else {
|
||||
fmt.Printf(" 会话ID: 不存在\n")
|
||||
}
|
||||
|
||||
// 演示类型安全的键
|
||||
fmt.Printf(" 类型安全的键:\n")
|
||||
type contextKey string
|
||||
const (
|
||||
userIDKey contextKey = "userID"
|
||||
requestIDKey contextKey = "requestID"
|
||||
)
|
||||
|
||||
safeCtx := context.WithValue(context.Background(), userIDKey, 67890)
|
||||
safeCtx = context.WithValue(safeCtx, requestIDKey, "req-def-456")
|
||||
|
||||
// 使用类型安全的方式获取值
|
||||
if userID := safeCtx.Value(userIDKey); userID != nil {
|
||||
fmt.Printf(" 安全获取用户ID: %v\n", userID)
|
||||
}
|
||||
|
||||
// 演示值的传播
|
||||
fmt.Printf(" 值的传播:\n")
|
||||
processRequest(ctx)
|
||||
|
||||
// 演示值的最佳实践
|
||||
fmt.Printf(" 值的最佳实践:\n")
|
||||
fmt.Printf(" 1. 只存储请求范围的数据\n")
|
||||
fmt.Printf(" 2. 使用类型安全的键\n")
|
||||
fmt.Printf(" 3. 不要存储可选参数\n")
|
||||
fmt.Printf(" 4. 键应该是不可导出的\n")
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateConcurrencyWithContext 演示 Context 在并发中的应用
|
||||
func demonstrateConcurrencyWithContext() {
|
||||
fmt.Println("6. Context 在并发中的应用:")
|
||||
|
||||
// 演示工作池模式
|
||||
fmt.Printf(" 工作池模式:\n")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 创建工作通道
|
||||
jobs := make(chan int, 10)
|
||||
results := make(chan string, 10)
|
||||
|
||||
// 启动工作者
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 3; i++ {
|
||||
wg.Add(1)
|
||||
go worker(ctx, i, jobs, results, &wg)
|
||||
}
|
||||
|
||||
// 发送工作
|
||||
go func() {
|
||||
for i := 1; i <= 8; i++ {
|
||||
select {
|
||||
case jobs <- i:
|
||||
fmt.Printf(" 发送工作 %d\n", i)
|
||||
case <-ctx.Done():
|
||||
fmt.Printf(" 停止发送工作: %v\n", ctx.Err())
|
||||
close(jobs)
|
||||
return
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
close(jobs)
|
||||
}()
|
||||
|
||||
// 收集结果
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(results)
|
||||
}()
|
||||
|
||||
// 打印结果
|
||||
fmt.Printf(" 工作结果:\n")
|
||||
for result := range results {
|
||||
fmt.Printf(" %s\n", result)
|
||||
}
|
||||
|
||||
// 演示扇出扇入模式
|
||||
fmt.Printf(" 扇出扇入模式:\n")
|
||||
fanOutFanIn()
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateHTTPContext 演示 Context 在 HTTP 中的应用
|
||||
func demonstrateHTTPContext() {
|
||||
fmt.Println("7. Context 在 HTTP 中的应用:")
|
||||
|
||||
// HTTP 请求中的 Context
|
||||
fmt.Printf(" HTTP 请求中的 Context:\n")
|
||||
fmt.Printf(" - 每个 HTTP 请求都有一个关联的 Context\n")
|
||||
fmt.Printf(" - 可以通过 r.Context() 获取\n")
|
||||
fmt.Printf(" - 请求取消时 Context 也会被取消\n")
|
||||
|
||||
// 模拟 HTTP 处理器
|
||||
fmt.Printf(" HTTP 处理器示例:\n")
|
||||
|
||||
// 创建模拟请求
|
||||
req, _ := http.NewRequest("GET", "/api/data", nil)
|
||||
|
||||
// 添加超时
|
||||
ctx, cancel := context.WithTimeout(req.Context(), 1*time.Second)
|
||||
defer cancel()
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
// 模拟处理请求
|
||||
fmt.Printf(" 处理请求...\n")
|
||||
handleRequest(req)
|
||||
|
||||
// 演示中间件模式
|
||||
fmt.Printf(" 中间件模式:\n")
|
||||
fmt.Printf(" func middleware(next http.Handler) http.Handler {\n")
|
||||
fmt.Printf(" return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n")
|
||||
fmt.Printf(" ctx := context.WithValue(r.Context(), \\\"requestID\\\", generateID())\n")
|
||||
fmt.Printf(" next.ServeHTTP(w, r.WithContext(ctx))\n")
|
||||
fmt.Printf(" })\n")
|
||||
fmt.Printf(" }\n")
|
||||
|
||||
// 演示数据库查询超时
|
||||
fmt.Printf(" 数据库查询超时:\n")
|
||||
queryCtx, queryCancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||
defer queryCancel()
|
||||
|
||||
err := queryDatabase(queryCtx, "SELECT * FROM users WHERE id = ?", 123)
|
||||
if err != nil {
|
||||
fmt.Printf(" 数据库查询失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" 数据库查询成功\n")
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateContextBestPractices 演示 Context 的最佳实践
|
||||
func demonstrateContextBestPractices() {
|
||||
fmt.Println("8. Context 的最佳实践:")
|
||||
|
||||
// 最佳实践列表
|
||||
fmt.Printf(" Context 的最佳实践:\n")
|
||||
fmt.Printf(" 1. 不要将 Context 存储在结构体中\n")
|
||||
fmt.Printf(" 2. Context 应该作为函数的第一个参数\n")
|
||||
fmt.Printf(" 3. 不要传递 nil Context,使用 context.TODO()\n")
|
||||
fmt.Printf(" 4. Context.Value 只用于请求范围的数据\n")
|
||||
fmt.Printf(" 5. 使用 defer cancel() 确保资源释放\n")
|
||||
fmt.Printf(" 6. 不要忽略 Context 的取消信号\n")
|
||||
fmt.Printf(" 7. 使用类型安全的键\n")
|
||||
fmt.Printf(" 8. 避免在 Context 中存储可选参数\n")
|
||||
|
||||
// 正确的函数签名
|
||||
fmt.Printf(" 正确的函数签名:\n")
|
||||
fmt.Printf(" func DoSomething(ctx context.Context, arg string) error\n")
|
||||
fmt.Printf(" func (s *Service) Process(ctx context.Context, data []byte) (*Result, error)\n")
|
||||
|
||||
// 错误的用法
|
||||
fmt.Printf(" 避免的错误用法:\n")
|
||||
fmt.Printf(" // 错误:不要在结构体中存储 Context\n")
|
||||
fmt.Printf(" type Server struct {\n")
|
||||
fmt.Printf(" ctx context.Context // 错误\n")
|
||||
fmt.Printf(" }\n")
|
||||
fmt.Printf(" \n")
|
||||
fmt.Printf(" // 错误:不要传递 nil Context\n")
|
||||
fmt.Printf(" DoSomething(nil, \\\"data\\\") // 错误\n")
|
||||
|
||||
// 正确的用法
|
||||
fmt.Printf(" 正确的用法示例:\n")
|
||||
ctx := context.Background()
|
||||
|
||||
// 正确的取消处理
|
||||
processWithCancel(ctx)
|
||||
|
||||
// 正确的超时处理
|
||||
processWithTimeout(ctx)
|
||||
|
||||
// 正确的值传递
|
||||
processWithValue(ctx)
|
||||
|
||||
// Context 的性能考虑
|
||||
fmt.Printf(" 性能考虑:\n")
|
||||
fmt.Printf(" 1. Context.Value 的查找是 O(n) 的\n")
|
||||
fmt.Printf(" 2. 避免在热路径中频繁创建 Context\n")
|
||||
fmt.Printf(" 3. 合理使用 Context 的层次结构\n")
|
||||
fmt.Printf(" 4. 及时调用 cancel 函数释放资源\n")
|
||||
|
||||
// 常见陷阱
|
||||
fmt.Printf(" 常见陷阱:\n")
|
||||
fmt.Printf(" 1. 忘记调用 cancel 函数导致资源泄漏\n")
|
||||
fmt.Printf(" 2. 在循环中创建过多的 Context\n")
|
||||
fmt.Printf(" 3. 将 Context 用作可选参数的容器\n")
|
||||
fmt.Printf(" 4. 不检查 Context 的取消信号\n")
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// ========== 辅助函数 ==========
|
||||
|
||||
// doWorkWithTimeout 带超时的工作函数
|
||||
func doWorkWithTimeout(timeout time.Duration) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
// 模拟工作
|
||||
select {
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
return "工作完成", nil
|
||||
case <-ctx.Done():
|
||||
return "", ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// makeHTTPRequestWithTimeout 带超时的 HTTP 请求
|
||||
func makeHTTPRequestWithTimeout(url string, timeout time.Duration) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 模拟 HTTP 请求
|
||||
select {
|
||||
case <-time.After(300 * time.Millisecond):
|
||||
return nil // 请求成功
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// checkDeadline 检查截止时间
|
||||
func checkDeadline(ctx context.Context) {
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
fmt.Printf(" 有截止时间: %v,剩余时间: %v\n", deadline, time.Until(deadline))
|
||||
} else {
|
||||
fmt.Printf(" 没有截止时间\n")
|
||||
}
|
||||
}
|
||||
|
||||
// processRequest 处理请求
|
||||
func processRequest(ctx context.Context) {
|
||||
if userID := ctx.Value("userID"); userID != nil {
|
||||
fmt.Printf(" 处理用户 %v 的请求\n", userID)
|
||||
}
|
||||
if requestID := ctx.Value("requestID"); requestID != nil {
|
||||
fmt.Printf(" 请求ID: %v\n", requestID)
|
||||
}
|
||||
}
|
||||
|
||||
// worker 工作者函数
|
||||
func worker(ctx context.Context, id int, jobs <-chan int, results chan<- string, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case job, ok := <-jobs:
|
||||
if !ok {
|
||||
fmt.Printf(" 工作者 %d 退出(通道关闭)\n", id)
|
||||
return
|
||||
}
|
||||
// 模拟工作
|
||||
select {
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
results <- fmt.Sprintf("工作者 %d 完成工作 %d", id, job)
|
||||
case <-ctx.Done():
|
||||
fmt.Printf(" 工作者 %d 被取消: %v\n", id, ctx.Err())
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
fmt.Printf(" 工作者 %d 被取消: %v\n", id, ctx.Err())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fanOutFanIn 扇出扇入模式
|
||||
func fanOutFanIn() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 输入通道
|
||||
input := make(chan int, 5)
|
||||
|
||||
// 启动多个处理器(扇出)
|
||||
c1 := process(ctx, input)
|
||||
c2 := process(ctx, input)
|
||||
c3 := process(ctx, input)
|
||||
|
||||
// 合并结果(扇入)
|
||||
output := merge(ctx, c1, c2, c3)
|
||||
|
||||
// 发送数据
|
||||
go func() {
|
||||
defer close(input)
|
||||
for i := 1; i <= 6; i++ {
|
||||
select {
|
||||
case input <- i:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// 收集结果
|
||||
for result := range output {
|
||||
fmt.Printf(" 扇出扇入结果: %s\n", result)
|
||||
}
|
||||
}
|
||||
|
||||
// process 处理函数
|
||||
func process(ctx context.Context, input <-chan int) <-chan string {
|
||||
output := make(chan string)
|
||||
go func() {
|
||||
defer close(output)
|
||||
for {
|
||||
select {
|
||||
case n, ok := <-input:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case output <- fmt.Sprintf("处理 %d", n*n):
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
return output
|
||||
}
|
||||
|
||||
// merge 合并多个通道
|
||||
func merge(ctx context.Context, channels ...<-chan string) <-chan string {
|
||||
var wg sync.WaitGroup
|
||||
output := make(chan string)
|
||||
|
||||
// 为每个输入通道启动一个 goroutine
|
||||
multiplex := func(c <-chan string) {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case s, ok := <-c:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case output <- s:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wg.Add(len(channels))
|
||||
for _, c := range channels {
|
||||
go multiplex(c)
|
||||
}
|
||||
|
||||
// 等待所有 goroutine 完成
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(output)
|
||||
}()
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
// handleRequest 处理 HTTP 请求
|
||||
func handleRequest(req *http.Request) {
|
||||
ctx := req.Context()
|
||||
|
||||
// 模拟处理
|
||||
select {
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
fmt.Printf(" 请求处理完成\n")
|
||||
case <-ctx.Done():
|
||||
fmt.Printf(" 请求被取消: %v\n", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
// queryDatabase 查询数据库
|
||||
func queryDatabase(ctx context.Context, query string, args ...interface{}) error {
|
||||
// 模拟数据库查询
|
||||
select {
|
||||
case <-time.After(300 * time.Millisecond):
|
||||
return nil // 查询成功
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// processWithCancel 带取消的处理
|
||||
func processWithCancel(ctx context.Context) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
// 模拟处理
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
cancel() // 取消操作
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
fmt.Printf(" 处理完成\n")
|
||||
case <-ctx.Done():
|
||||
fmt.Printf(" 处理被取消\n")
|
||||
}
|
||||
}
|
||||
|
||||
// processWithTimeout 带超时的处理
|
||||
func processWithTimeout(ctx context.Context) {
|
||||
ctx, cancel := context.WithTimeout(ctx, 50*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
select {
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
fmt.Printf(" 处理完成\n")
|
||||
case <-ctx.Done():
|
||||
fmt.Printf(" 处理超时\n")
|
||||
}
|
||||
}
|
||||
|
||||
// processWithValue 带值传递的处理
|
||||
func processWithValue(ctx context.Context) {
|
||||
ctx = context.WithValue(ctx, "operation", "process")
|
||||
|
||||
if op := ctx.Value("operation"); op != nil {
|
||||
fmt.Printf(" 执行操作: %v\n", op)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
运行这个程序:
|
||||
go run 03-context.go
|
||||
|
||||
Context 的核心概念:
|
||||
1. Context 是 Go 语言中处理请求范围数据的标准方式
|
||||
2. 提供取消信号、超时控制和值传递功能
|
||||
3. 在 goroutine 之间传递取消信号和截止时间
|
||||
4. 避免 goroutine 泄漏和资源浪费
|
||||
|
||||
Context 的主要类型:
|
||||
1. context.Background(): 根 Context,通常用作起点
|
||||
2. context.TODO(): 当不确定使用哪个 Context 时使用
|
||||
3. context.WithCancel(): 创建可取消的 Context
|
||||
4. context.WithTimeout(): 创建带超时的 Context
|
||||
5. context.WithDeadline(): 创建带截止时间的 Context
|
||||
6. context.WithValue(): 创建带值的 Context
|
||||
|
||||
Context 的最佳实践:
|
||||
1. 不要将 Context 存储在结构体中
|
||||
2. Context 应该作为函数的第一个参数
|
||||
3. 不要传递 nil Context,使用 context.TODO()
|
||||
4. Context.Value 只用于请求范围的数据
|
||||
5. 使用 defer cancel() 确保资源释放
|
||||
6. 不要忽略 Context 的取消信号
|
||||
7. 使用类型安全的键
|
||||
8. 避免在 Context 中存储可选参数
|
||||
|
||||
Context 的应用场景:
|
||||
1. HTTP 请求处理
|
||||
2. 数据库查询超时
|
||||
3. 并发任务协调
|
||||
4. 微服务调用链
|
||||
5. 长时间运行的任务控制
|
||||
|
||||
注意事项:
|
||||
1. Context 是并发安全的
|
||||
2. Context 的取消会传播到所有子 Context
|
||||
3. Context.Value 的查找是 O(n) 的
|
||||
4. 及时调用 cancel 函数释放资源
|
||||
5. 不要在 Context 中存储可选参数
|
||||
|
||||
常见错误:
|
||||
1. 忘记调用 cancel 函数
|
||||
2. 将 Context 存储在结构体中
|
||||
3. 传递 nil Context
|
||||
4. 不检查 Context 的取消信号
|
||||
5. 在 Context 中存储过多数据
|
||||
*/
|
919
golang-learning/09-advanced/04-testing.go
Normal file
919
golang-learning/09-advanced/04-testing.go
Normal file
@@ -0,0 +1,919 @@
|
||||
/*
|
||||
04-testing.go - Go 语言测试详解
|
||||
学习目标:
|
||||
1. 理解 Go 语言测试的基本概念
|
||||
2. 掌握单元测试的编写方法
|
||||
3. 学会基准测试和性能分析
|
||||
4. 了解测试覆盖率和测试工具
|
||||
5. 掌握测试的最佳实践
|
||||
知识点:
|
||||
- 测试函数的命名和签名
|
||||
- testing 包的使用
|
||||
- 表驱动测试模式
|
||||
- 基准测试和内存分析
|
||||
- 测试覆盖率
|
||||
- 测试工具和技巧
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 这个文件演示了如何编写各种类型的测试
|
||||
// 注意:实际的测试文件应该以 _test.go 结尾
|
||||
|
||||
func main() {
|
||||
fmt.Println("=== Go 语言测试详解 ===\n")
|
||||
|
||||
// 演示测试的基本概念
|
||||
demonstrateTestingBasics()
|
||||
|
||||
// 演示单元测试
|
||||
demonstrateUnitTesting()
|
||||
|
||||
// 演示表驱动测试
|
||||
demonstrateTableDrivenTests()
|
||||
|
||||
// 演示基准测试
|
||||
demonstrateBenchmarkTesting()
|
||||
|
||||
// 演示示例测试
|
||||
demonstrateExampleTesting()
|
||||
|
||||
// 演示测试工具和技巧
|
||||
demonstrateTestingTools()
|
||||
|
||||
// 演示测试的最佳实践
|
||||
demonstrateTestingBestPractices()
|
||||
}
|
||||
|
||||
// demonstrateTestingBasics 演示测试的基本概念
|
||||
func demonstrateTestingBasics() {
|
||||
fmt.Println("1. 测试的基本概念:")
|
||||
|
||||
// 测试的重要性
|
||||
fmt.Printf(" 测试的重要性:\n")
|
||||
fmt.Printf(" - 验证代码的正确性\n")
|
||||
fmt.Printf(" - 防止回归错误\n")
|
||||
fmt.Printf(" - 提高代码质量\n")
|
||||
fmt.Printf(" - 便于重构和维护\n")
|
||||
fmt.Printf(" - 作为代码的文档\n")
|
||||
|
||||
// Go 测试的特点
|
||||
fmt.Printf(" Go 测试的特点:\n")
|
||||
fmt.Printf(" - 内置测试框架\n")
|
||||
fmt.Printf(" - 简单的测试语法\n")
|
||||
fmt.Printf(" - 强大的工具支持\n")
|
||||
fmt.Printf(" - 并行测试支持\n")
|
||||
fmt.Printf(" - 基准测试集成\n")
|
||||
|
||||
// 测试文件的组织
|
||||
fmt.Printf(" 测试文件的组织:\n")
|
||||
fmt.Printf(" 源文件: calculator.go\n")
|
||||
fmt.Printf(" 测试文件: calculator_test.go\n")
|
||||
fmt.Printf(" \n")
|
||||
fmt.Printf(" 源文件: utils.go\n")
|
||||
fmt.Printf(" 测试文件: utils_test.go\n")
|
||||
|
||||
// 测试函数的类型
|
||||
fmt.Printf(" 测试函数的类型:\n")
|
||||
fmt.Printf(" 1. 单元测试: func TestXxx(*testing.T)\n")
|
||||
fmt.Printf(" 2. 基准测试: func BenchmarkXxx(*testing.B)\n")
|
||||
fmt.Printf(" 3. 示例测试: func ExampleXxx()\n")
|
||||
fmt.Printf(" 4. 模糊测试: func FuzzXxx(*testing.F) (Go 1.18+)\n")
|
||||
|
||||
// 运行测试的命令
|
||||
fmt.Printf(" 运行测试的命令:\n")
|
||||
fmt.Printf(" go test # 运行当前包的测试\n")
|
||||
fmt.Printf(" go test ./... # 运行所有子包的测试\n")
|
||||
fmt.Printf(" go test -v # 详细输出\n")
|
||||
fmt.Printf(" go test -run TestAdd # 运行特定测试\n")
|
||||
fmt.Printf(" go test -bench=. # 运行基准测试\n")
|
||||
fmt.Printf(" go test -cover # 显示覆盖率\n")
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateUnitTesting 演示单元测试
|
||||
func demonstrateUnitTesting() {
|
||||
fmt.Println("2. 单元测试:")
|
||||
|
||||
// 基本测试函数
|
||||
fmt.Printf(" 基本测试函数:\n")
|
||||
fmt.Printf(" func TestAdd(t *testing.T) {\n")
|
||||
fmt.Printf(" result := Add(2, 3)\n")
|
||||
fmt.Printf(" expected := 5\n")
|
||||
fmt.Printf(" if result != expected {\n")
|
||||
fmt.Printf(" t.Errorf(\\\"Add(2, 3) = %%d; want %%d\\\", result, expected)\n")
|
||||
fmt.Printf(" }\n")
|
||||
fmt.Printf(" }\n")
|
||||
|
||||
// 模拟运行测试
|
||||
fmt.Printf(" 模拟运行测试:\n")
|
||||
mockT := &MockT{}
|
||||
|
||||
// 测试 Add 函数
|
||||
fmt.Printf(" 测试 Add 函数:\n")
|
||||
TestAdd(mockT)
|
||||
if mockT.failed {
|
||||
fmt.Printf(" ❌ 测试失败: %s\n", mockT.errorMsg)
|
||||
} else {
|
||||
fmt.Printf(" ✅ 测试通过\n")
|
||||
}
|
||||
|
||||
// 测试 Divide 函数
|
||||
fmt.Printf(" 测试 Divide 函数:\n")
|
||||
mockT = &MockT{}
|
||||
TestDivide(mockT)
|
||||
if mockT.failed {
|
||||
fmt.Printf(" ❌ 测试失败: %s\n", mockT.errorMsg)
|
||||
} else {
|
||||
fmt.Printf(" ✅ 测试通过\n")
|
||||
}
|
||||
|
||||
// 测试 IsPrime 函数
|
||||
fmt.Printf(" 测试 IsPrime 函数:\n")
|
||||
mockT = &MockT{}
|
||||
TestIsPrime(mockT)
|
||||
if mockT.failed {
|
||||
fmt.Printf(" ❌ 测试失败: %s\n", mockT.errorMsg)
|
||||
} else {
|
||||
fmt.Printf(" ✅ 测试通过\n")
|
||||
}
|
||||
|
||||
// testing.T 的方法
|
||||
fmt.Printf(" testing.T 的方法:\n")
|
||||
fmt.Printf(" t.Error(args ...) # 记录错误但继续测试\n")
|
||||
fmt.Printf(" t.Errorf(format, args ...) # 格式化错误信息\n")
|
||||
fmt.Printf(" t.Fatal(args ...) # 记录错误并停止测试\n")
|
||||
fmt.Printf(" t.Fatalf(format, args ...) # 格式化致命错误\n")
|
||||
fmt.Printf(" t.Log(args ...) # 记录日志信息\n")
|
||||
fmt.Printf(" t.Logf(format, args ...) # 格式化日志信息\n")
|
||||
fmt.Printf(" t.Skip(args ...) # 跳过测试\n")
|
||||
fmt.Printf(" t.Helper() # 标记为辅助函数\n")
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateTableDrivenTests 演示表驱动测试
|
||||
func demonstrateTableDrivenTests() {
|
||||
fmt.Println("3. 表驱动测试:")
|
||||
|
||||
// 表驱动测试的优势
|
||||
fmt.Printf(" 表驱动测试的优势:\n")
|
||||
fmt.Printf(" - 减少重复代码\n")
|
||||
fmt.Printf(" - 易于添加新的测试用例\n")
|
||||
fmt.Printf(" - 提高测试的可维护性\n")
|
||||
fmt.Printf(" - 清晰的测试数据结构\n")
|
||||
|
||||
// 表驱动测试示例
|
||||
fmt.Printf(" 表驱动测试示例:\n")
|
||||
fmt.Printf(" func TestAddTable(t *testing.T) {\n")
|
||||
fmt.Printf(" tests := []struct {\n")
|
||||
fmt.Printf(" name string\n")
|
||||
fmt.Printf(" a, b int\n")
|
||||
fmt.Printf(" expected int\n")
|
||||
fmt.Printf(" }{\n")
|
||||
fmt.Printf(" {\\\"positive\\\", 2, 3, 5},\n")
|
||||
fmt.Printf(" {\\\"negative\\\", -1, -2, -3},\n")
|
||||
fmt.Printf(" {\\\"zero\\\", 0, 5, 5},\n")
|
||||
fmt.Printf(" }\n")
|
||||
fmt.Printf(" \n")
|
||||
fmt.Printf(" for _, tt := range tests {\n")
|
||||
fmt.Printf(" t.Run(tt.name, func(t *testing.T) {\n")
|
||||
fmt.Printf(" result := Add(tt.a, tt.b)\n")
|
||||
fmt.Printf(" if result != tt.expected {\n")
|
||||
fmt.Printf(" t.Errorf(\\\"got %%d, want %%d\\\", result, tt.expected)\n")
|
||||
fmt.Printf(" }\n")
|
||||
fmt.Printf(" })\n")
|
||||
fmt.Printf(" }\n")
|
||||
fmt.Printf(" }\n")
|
||||
|
||||
// 模拟运行表驱动测试
|
||||
fmt.Printf(" 模拟运行表驱动测试:\n")
|
||||
mockT := &MockT{}
|
||||
TestAddTable(mockT)
|
||||
|
||||
// 字符串处理的表驱动测试
|
||||
fmt.Printf(" 字符串处理的表驱动测试:\n")
|
||||
mockT = &MockT{}
|
||||
TestStringOperations(mockT)
|
||||
|
||||
// 错误处理的表驱动测试
|
||||
fmt.Printf(" 错误处理的表驱动测试:\n")
|
||||
mockT = &MockT{}
|
||||
TestValidateInput(mockT)
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateBenchmarkTesting 演示基准测试
|
||||
func demonstrateBenchmarkTesting() {
|
||||
fmt.Println("4. 基准测试:")
|
||||
|
||||
// 基准测试的概念
|
||||
fmt.Printf(" 基准测试的概念:\n")
|
||||
fmt.Printf(" - 测量代码的性能\n")
|
||||
fmt.Printf(" - 比较不同实现的效率\n")
|
||||
fmt.Printf(" - 识别性能瓶颈\n")
|
||||
fmt.Printf(" - 验证优化效果\n")
|
||||
|
||||
// 基准测试函数
|
||||
fmt.Printf(" 基准测试函数:\n")
|
||||
fmt.Printf(" func BenchmarkStringConcat(b *testing.B) {\n")
|
||||
fmt.Printf(" for i := 0; i < b.N; i++ {\n")
|
||||
fmt.Printf(" _ = \\\"hello\\\" + \\\"world\\\"\n")
|
||||
fmt.Printf(" }\n")
|
||||
fmt.Printf(" }\n")
|
||||
|
||||
// 模拟运行基准测试
|
||||
fmt.Printf(" 模拟运行基准测试:\n")
|
||||
|
||||
// 字符串连接基准测试
|
||||
fmt.Printf(" 字符串连接性能比较:\n")
|
||||
runBenchmark("StringConcat", BenchmarkStringConcat)
|
||||
runBenchmark("StringBuffer", BenchmarkStringBuffer)
|
||||
runBenchmark("StringBuilder", BenchmarkStringBuilder)
|
||||
|
||||
// 排序算法基准测试
|
||||
fmt.Printf(" 排序算法性能比较:\n")
|
||||
runBenchmark("BubbleSort", BenchmarkBubbleSort)
|
||||
runBenchmark("QuickSort", BenchmarkQuickSort)
|
||||
runBenchmark("StandardSort", BenchmarkStandardSort)
|
||||
|
||||
// 基准测试的运行参数
|
||||
fmt.Printf(" 基准测试的运行参数:\n")
|
||||
fmt.Printf(" go test -bench=. # 运行所有基准测试\n")
|
||||
fmt.Printf(" go test -bench=BenchmarkAdd # 运行特定基准测试\n")
|
||||
fmt.Printf(" go test -bench=. -benchmem # 显示内存分配\n")
|
||||
fmt.Printf(" go test -bench=. -benchtime=10s # 设置运行时间\n")
|
||||
fmt.Printf(" go test -bench=. -count=5 # 运行多次\n")
|
||||
|
||||
// 基准测试结果解读
|
||||
fmt.Printf(" 基准测试结果解读:\n")
|
||||
fmt.Printf(" BenchmarkAdd-8 1000000000 0.50 ns/op 0 B/op 0 allocs/op\n")
|
||||
fmt.Printf(" │ │ │ │ │\n")
|
||||
fmt.Printf(" │ │ │ │ └─ 每次操作的分配次数\n")
|
||||
fmt.Printf(" │ │ │ └─ 每次操作分配的字节数\n")
|
||||
fmt.Printf(" │ │ └─ 每次操作的平均时间\n")
|
||||
fmt.Printf(" │ └─ 执行次数\n")
|
||||
fmt.Printf(" └─ 基准测试名称和CPU核心数\n")
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateExampleTesting 演示示例测试
|
||||
func demonstrateExampleTesting() {
|
||||
fmt.Println("5. 示例测试:")
|
||||
|
||||
// 示例测试的概念
|
||||
fmt.Printf(" 示例测试的概念:\n")
|
||||
fmt.Printf(" - 提供函数使用示例\n")
|
||||
fmt.Printf(" - 验证输出结果\n")
|
||||
fmt.Printf(" - 生成文档\n")
|
||||
fmt.Printf(" - 确保示例代码正确\n")
|
||||
|
||||
// 示例测试函数
|
||||
fmt.Printf(" 示例测试函数:\n")
|
||||
fmt.Printf(" func ExampleAdd() {\n")
|
||||
fmt.Printf(" result := Add(2, 3)\n")
|
||||
fmt.Printf(" fmt.Println(result)\n")
|
||||
fmt.Printf(" // Output: 5\n")
|
||||
fmt.Printf(" }\n")
|
||||
|
||||
// 运行示例测试
|
||||
fmt.Printf(" 运行示例测试:\n")
|
||||
fmt.Printf(" Add 函数示例:\n")
|
||||
ExampleAdd()
|
||||
|
||||
fmt.Printf(" Divide 函数示例:\n")
|
||||
ExampleDivide()
|
||||
|
||||
fmt.Printf(" StringReverse 函数示例:\n")
|
||||
ExampleStringReverse()
|
||||
|
||||
// 示例测试的特点
|
||||
fmt.Printf(" 示例测试的特点:\n")
|
||||
fmt.Printf(" 1. 函数名以 Example 开头\n")
|
||||
fmt.Printf(" 2. 使用 // Output: 注释指定期望输出\n")
|
||||
fmt.Printf(" 3. 可以使用 // Unordered output: 处理无序输出\n")
|
||||
fmt.Printf(" 4. 会在 go doc 中显示\n")
|
||||
fmt.Printf(" 5. 可以通过 go test 验证\n")
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateTestingTools 演示测试工具和技巧
|
||||
func demonstrateTestingTools() {
|
||||
fmt.Println("6. 测试工具和技巧:")
|
||||
|
||||
// 测试覆盖率
|
||||
fmt.Printf(" 测试覆盖率:\n")
|
||||
fmt.Printf(" go test -cover # 显示覆盖率\n")
|
||||
fmt.Printf(" go test -coverprofile=cover.out # 生成覆盖率文件\n")
|
||||
fmt.Printf(" go tool cover -html=cover.out # 生成HTML报告\n")
|
||||
fmt.Printf(" go tool cover -func=cover.out # 显示函数覆盖率\n")
|
||||
|
||||
// 测试辅助函数
|
||||
fmt.Printf(" 测试辅助函数:\n")
|
||||
fmt.Printf(" func assertEqual(t *testing.T, got, want interface{}) {\n")
|
||||
fmt.Printf(" t.Helper() // 标记为辅助函数\n")
|
||||
fmt.Printf(" if got != want {\n")
|
||||
fmt.Printf(" t.Errorf(\\\"got %%v, want %%v\\\", got, want)\n")
|
||||
fmt.Printf(" }\n")
|
||||
fmt.Printf(" }\n")
|
||||
|
||||
// 模拟使用辅助函数
|
||||
fmt.Printf(" 使用辅助函数:\n")
|
||||
mockT := &MockT{}
|
||||
TestWithHelpers(mockT)
|
||||
if mockT.failed {
|
||||
fmt.Printf(" ❌ 测试失败: %s\n", mockT.errorMsg)
|
||||
} else {
|
||||
fmt.Printf(" ✅ 测试通过\n")
|
||||
}
|
||||
|
||||
// 子测试
|
||||
fmt.Printf(" 子测试:\n")
|
||||
fmt.Printf(" t.Run(\\\"subtest\\\", func(t *testing.T) {\n")
|
||||
fmt.Printf(" // 子测试代码\n")
|
||||
fmt.Printf(" })\n")
|
||||
|
||||
// 并行测试
|
||||
fmt.Printf(" 并行测试:\n")
|
||||
fmt.Printf(" func TestParallel(t *testing.T) {\n")
|
||||
fmt.Printf(" t.Parallel() // 标记为并行测试\n")
|
||||
fmt.Printf(" // 测试代码\n")
|
||||
fmt.Printf(" }\n")
|
||||
|
||||
// 测试设置和清理
|
||||
fmt.Printf(" 测试设置和清理:\n")
|
||||
fmt.Printf(" func TestMain(m *testing.M) {\n")
|
||||
fmt.Printf(" setup() // 全局设置\n")
|
||||
fmt.Printf(" code := m.Run()\n")
|
||||
fmt.Printf(" teardown() // 全局清理\n")
|
||||
fmt.Printf(" os.Exit(code)\n")
|
||||
fmt.Printf(" }\n")
|
||||
|
||||
// 跳过测试
|
||||
fmt.Printf(" 跳过测试:\n")
|
||||
fmt.Printf(" if testing.Short() {\n")
|
||||
fmt.Printf(" t.Skip(\\\"跳过长时间运行的测试\\\")\n")
|
||||
fmt.Printf(" }\n")
|
||||
|
||||
// 临时目录
|
||||
fmt.Printf(" 临时目录:\n")
|
||||
fmt.Printf(" tmpDir := t.TempDir() // 创建临时目录\n")
|
||||
fmt.Printf(" // 测试结束后自动清理\n")
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateTestingBestPractices 演示测试的最佳实践
|
||||
func demonstrateTestingBestPractices() {
|
||||
fmt.Println("7. 测试的最佳实践:")
|
||||
|
||||
// 测试命名
|
||||
fmt.Printf(" 测试命名:\n")
|
||||
fmt.Printf(" 1. 使用描述性的测试名称\n")
|
||||
fmt.Printf(" 2. 包含被测试的功能和场景\n")
|
||||
fmt.Printf(" 3. 使用一致的命名约定\n")
|
||||
fmt.Printf(" \n")
|
||||
fmt.Printf(" 好的命名:\n")
|
||||
fmt.Printf(" TestAdd_PositiveNumbers\n")
|
||||
fmt.Printf(" TestDivide_ByZero_ReturnsError\n")
|
||||
fmt.Printf(" TestUser_Create_ValidInput\n")
|
||||
|
||||
// 测试组织
|
||||
fmt.Printf(" 测试组织:\n")
|
||||
fmt.Printf(" 1. 每个源文件对应一个测试文件\n")
|
||||
fmt.Printf(" 2. 按功能分组测试\n")
|
||||
fmt.Printf(" 3. 使用子测试组织复杂场景\n")
|
||||
fmt.Printf(" 4. 保持测试的独立性\n")
|
||||
|
||||
// 测试数据
|
||||
fmt.Printf(" 测试数据:\n")
|
||||
fmt.Printf(" 1. 使用表驱动测试处理多个用例\n")
|
||||
fmt.Printf(" 2. 包含边界条件和异常情况\n")
|
||||
fmt.Printf(" 3. 使用有意义的测试数据\n")
|
||||
fmt.Printf(" 4. 避免硬编码的魔法数字\n")
|
||||
|
||||
// 断言
|
||||
fmt.Printf(" 断言:\n")
|
||||
fmt.Printf(" 1. 使用清晰的错误消息\n")
|
||||
fmt.Printf(" 2. 一个测试只验证一个行为\n")
|
||||
fmt.Printf(" 3. 优先使用 Errorf 而不是 Error\n")
|
||||
fmt.Printf(" 4. 在必要时使用 Fatal 停止测试\n")
|
||||
|
||||
// 测试覆盖率
|
||||
fmt.Printf(" 测试覆盖率:\n")
|
||||
fmt.Printf(" 1. 追求高覆盖率但不盲目追求100%%\n")
|
||||
fmt.Printf(" 2. 关注重要的业务逻辑\n")
|
||||
fmt.Printf(" 3. 测试边界条件和错误路径\n")
|
||||
fmt.Printf(" 4. 使用覆盖率工具识别遗漏\n")
|
||||
|
||||
// 性能考虑
|
||||
fmt.Printf(" 性能考虑:\n")
|
||||
fmt.Printf(" 1. 避免在测试中进行复杂计算\n")
|
||||
fmt.Printf(" 2. 使用并行测试提高速度\n")
|
||||
fmt.Printf(" 3. 合理使用测试缓存\n")
|
||||
fmt.Printf(" 4. 避免不必要的外部依赖\n")
|
||||
|
||||
// 测试维护
|
||||
fmt.Printf(" 测试维护:\n")
|
||||
fmt.Printf(" 1. 保持测试代码的简洁\n")
|
||||
fmt.Printf(" 2. 定期重构测试代码\n")
|
||||
fmt.Printf(" 3. 及时更新过时的测试\n")
|
||||
fmt.Printf(" 4. 删除无用的测试\n")
|
||||
|
||||
// 常见陷阱
|
||||
fmt.Printf(" 常见陷阱:\n")
|
||||
fmt.Printf(" 1. 测试依赖外部状态\n")
|
||||
fmt.Printf(" 2. 测试之间相互依赖\n")
|
||||
fmt.Printf(" 3. 忽略错误处理的测试\n")
|
||||
fmt.Printf(" 4. 过度复杂的测试逻辑\n")
|
||||
fmt.Printf(" 5. 不稳定的测试(flaky tests)\n")
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// ========== 被测试的函数 ==========
|
||||
|
||||
// Add 计算两个整数的和
|
||||
func Add(a, b int) int {
|
||||
return a + b
|
||||
}
|
||||
|
||||
// Divide 计算两个数的商
|
||||
func Divide(a, b float64) (float64, error) {
|
||||
if b == 0 {
|
||||
return 0, fmt.Errorf("division by zero")
|
||||
}
|
||||
return a / b, nil
|
||||
}
|
||||
|
||||
// IsPrime 判断是否为质数
|
||||
func IsPrime(n int) bool {
|
||||
if n < 2 {
|
||||
return false
|
||||
}
|
||||
for i := 2; i*i <= n; i++ {
|
||||
if n%i == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// StringReverse 反转字符串
|
||||
func StringReverse(s string) string {
|
||||
runes := []rune(s)
|
||||
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
|
||||
runes[i], runes[j] = runes[j], runes[i]
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
// Capitalize 将字符串首字母大写
|
||||
func Capitalize(s string) string {
|
||||
if len(s) == 0 {
|
||||
return s
|
||||
}
|
||||
return strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
|
||||
}
|
||||
|
||||
// ValidateInput 验证输入
|
||||
func ValidateInput(input string) error {
|
||||
if len(input) == 0 {
|
||||
return fmt.Errorf("input cannot be empty")
|
||||
}
|
||||
if len(input) > 100 {
|
||||
return fmt.Errorf("input too long")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ========== 模拟的 testing.T 和 testing.B ==========
|
||||
|
||||
// MockT 模拟 testing.T 类型
|
||||
type MockT struct {
|
||||
failed bool
|
||||
errorMsg string
|
||||
logs []string
|
||||
}
|
||||
|
||||
func (m *MockT) Error(args ...interface{}) {
|
||||
m.failed = true
|
||||
m.errorMsg = fmt.Sprint(args...)
|
||||
}
|
||||
|
||||
func (m *MockT) Errorf(format string, args ...interface{}) {
|
||||
m.failed = true
|
||||
m.errorMsg = fmt.Sprintf(format, args...)
|
||||
}
|
||||
|
||||
func (m *MockT) Fatal(args ...interface{}) {
|
||||
m.failed = true
|
||||
m.errorMsg = fmt.Sprint(args...)
|
||||
}
|
||||
|
||||
func (m *MockT) Fatalf(format string, args ...interface{}) {
|
||||
m.failed = true
|
||||
m.errorMsg = fmt.Sprintf(format, args...)
|
||||
}
|
||||
|
||||
func (m *MockT) Log(args ...interface{}) {
|
||||
m.logs = append(m.logs, fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (m *MockT) Logf(format string, args ...interface{}) {
|
||||
m.logs = append(m.logs, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (m *MockT) Helper() {
|
||||
// 标记为辅助函数
|
||||
}
|
||||
|
||||
func (m *MockT) Run(name string, f func(*MockT)) bool {
|
||||
fmt.Printf(" 运行子测试: %s\n", name)
|
||||
subT := &MockT{}
|
||||
f(subT)
|
||||
if subT.failed {
|
||||
fmt.Printf(" ❌ 失败: %s\n", subT.errorMsg)
|
||||
return false
|
||||
} else {
|
||||
fmt.Printf(" ✅ 通过\n")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// MockB 模拟 testing.B 类型
|
||||
type MockB struct {
|
||||
N int
|
||||
}
|
||||
|
||||
func (b *MockB) ResetTimer() {}
|
||||
func (b *MockB) ReportAllocs() {}
|
||||
|
||||
// ========== 测试函数示例 ==========
|
||||
|
||||
// TestAdd 测试 Add 函数
|
||||
func TestAdd(t *MockT) {
|
||||
result := Add(2, 3)
|
||||
expected := 5
|
||||
if result != expected {
|
||||
t.Errorf("Add(2, 3) = %d; want %d", result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDivide 测试 Divide 函数
|
||||
func TestDivide(t *MockT) {
|
||||
// 正常情况
|
||||
result, err := Divide(10, 2)
|
||||
if err != nil {
|
||||
t.Errorf("Divide(10, 2) returned error: %v", err)
|
||||
return
|
||||
}
|
||||
if result != 5 {
|
||||
t.Errorf("Divide(10, 2) = %f; want 5", result)
|
||||
}
|
||||
|
||||
// 除零情况
|
||||
_, err = Divide(10, 0)
|
||||
if err == nil {
|
||||
t.Error("Divide(10, 0) should return error")
|
||||
}
|
||||
}
|
||||
|
||||
// TestIsPrime 测试 IsPrime 函数
|
||||
func TestIsPrime(t *MockT) {
|
||||
tests := []struct {
|
||||
n int
|
||||
expected bool
|
||||
}{
|
||||
{2, true},
|
||||
{3, true},
|
||||
{4, false},
|
||||
{17, true},
|
||||
{25, false},
|
||||
{1, false},
|
||||
{0, false},
|
||||
{-1, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
result := IsPrime(tt.n)
|
||||
if result != tt.expected {
|
||||
t.Errorf("IsPrime(%d) = %t; want %t", tt.n, result, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestAddTable 表驱动测试示例
|
||||
func TestAddTable(t *MockT) {
|
||||
tests := []struct {
|
||||
name string
|
||||
a, b int
|
||||
expected int
|
||||
}{
|
||||
{"positive numbers", 2, 3, 5},
|
||||
{"negative numbers", -1, -2, -3},
|
||||
{"mixed numbers", -1, 2, 1},
|
||||
{"zero", 0, 5, 5},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(subT *MockT) {
|
||||
result := Add(tt.a, tt.b)
|
||||
if result != tt.expected {
|
||||
subT.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestStringOperations 字符串操作测试
|
||||
func TestStringOperations(t *MockT) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"normal string", "hello", "Hello"},
|
||||
{"empty string", "", ""},
|
||||
{"single char", "a", "A"},
|
||||
{"already capitalized", "Hello", "Hello"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(subT *MockT) {
|
||||
result := Capitalize(tt.input)
|
||||
if result != tt.expected {
|
||||
subT.Errorf("Capitalize(%q) = %q; want %q", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidateInput 输入验证测试
|
||||
func TestValidateInput(t *MockT) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
wantError bool
|
||||
}{
|
||||
{"valid input", "hello", false},
|
||||
{"empty input", "", true},
|
||||
{"too long input", strings.Repeat("a", 101), true},
|
||||
{"max length input", strings.Repeat("a", 100), false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(subT *MockT) {
|
||||
err := ValidateInput(tt.input)
|
||||
if (err != nil) != tt.wantError {
|
||||
subT.Errorf("ValidateInput(%q) error = %v; wantError %t", tt.input, err, tt.wantError)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestWithHelpers 使用辅助函数的测试
|
||||
func TestWithHelpers(t *MockT) {
|
||||
assertEqual := func(t *MockT, got, want interface{}) {
|
||||
t.Helper()
|
||||
if got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// 使用辅助函数进行断言
|
||||
assertEqual(t, Add(2, 3), 5)
|
||||
assertEqual(t, Capitalize("hello"), "Hello")
|
||||
|
||||
result, err := Divide(10, 2)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
assertEqual(t, result, 5.0)
|
||||
}
|
||||
|
||||
// ========== 基准测试函数 ==========
|
||||
|
||||
// BenchmarkStringConcat 字符串连接基准测试
|
||||
func BenchmarkStringConcat(b *MockB) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = "hello" + "world" + "golang" + "benchmark"
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkStringBuffer 字符串缓冲区基准测试
|
||||
func BenchmarkStringBuffer(b *MockB) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
var buffer strings.Builder
|
||||
buffer.WriteString("hello")
|
||||
buffer.WriteString("world")
|
||||
buffer.WriteString("golang")
|
||||
buffer.WriteString("benchmark")
|
||||
_ = buffer.String()
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkStringBuilder 字符串构建器基准测试
|
||||
func BenchmarkStringBuilder(b *MockB) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
parts := []string{"hello", "world", "golang", "benchmark"}
|
||||
_ = strings.Join(parts, "")
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkBubbleSort 冒泡排序基准测试
|
||||
func BenchmarkBubbleSort(b *MockB) {
|
||||
data := []int{64, 34, 25, 12, 22, 11, 90}
|
||||
for i := 0; i < b.N; i++ {
|
||||
bubbleSort(append([]int(nil), data...))
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkQuickSort 快速排序基准测试
|
||||
func BenchmarkQuickSort(b *MockB) {
|
||||
data := []int{64, 34, 25, 12, 22, 11, 90}
|
||||
for i := 0; i < b.N; i++ {
|
||||
quickSort(append([]int(nil), data...))
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkStandardSort 标准排序基准测试
|
||||
func BenchmarkStandardSort(b *MockB) {
|
||||
data := []int{64, 34, 25, 12, 22, 11, 90}
|
||||
for i := 0; i < b.N; i++ {
|
||||
sort.Ints(append([]int(nil), data...))
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 示例测试函数 ==========
|
||||
|
||||
// ExampleAdd Add 函数的示例
|
||||
func ExampleAdd() {
|
||||
result := Add(2, 3)
|
||||
fmt.Println(result)
|
||||
// Output: 5
|
||||
}
|
||||
|
||||
// ExampleDivide Divide 函数的示例
|
||||
func ExampleDivide() {
|
||||
result, err := Divide(10, 2)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%.1f", result)
|
||||
// Output: 5.0
|
||||
}
|
||||
|
||||
// ExampleStringReverse StringReverse 函数的示例
|
||||
func ExampleStringReverse() {
|
||||
result := StringReverse("hello")
|
||||
fmt.Println(result)
|
||||
// Output: olleh
|
||||
}
|
||||
|
||||
// ========== 辅助函数 ==========
|
||||
|
||||
// runBenchmark 运行基准测试
|
||||
func runBenchmark(name string, benchFunc func(*MockB)) {
|
||||
b := &MockB{N: 1000000}
|
||||
start := time.Now()
|
||||
benchFunc(b)
|
||||
duration := time.Since(start)
|
||||
nsPerOp := duration.Nanoseconds() / int64(b.N)
|
||||
fmt.Printf(" %s: %d ns/op\n", name, nsPerOp)
|
||||
}
|
||||
|
||||
// bubbleSort 冒泡排序
|
||||
func bubbleSort(arr []int) {
|
||||
n := len(arr)
|
||||
for i := 0; i < n-1; i++ {
|
||||
for j := 0; j < n-i-1; j++ {
|
||||
if arr[j] > arr[j+1] {
|
||||
arr[j], arr[j+1] = arr[j+1], arr[j]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// quickSort 快速排序
|
||||
func quickSort(arr []int) {
|
||||
if len(arr) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
pivot := partition(arr)
|
||||
quickSort(arr[:pivot])
|
||||
quickSort(arr[pivot+1:])
|
||||
}
|
||||
|
||||
// partition 分区函数
|
||||
func partition(arr []int) int {
|
||||
pivot := arr[len(arr)-1]
|
||||
i := -1
|
||||
|
||||
for j := 0; j < len(arr)-1; j++ {
|
||||
if arr[j] <= pivot {
|
||||
i++
|
||||
arr[i], arr[j] = arr[j], arr[i]
|
||||
}
|
||||
}
|
||||
|
||||
arr[i+1], arr[len(arr)-1] = arr[len(arr)-1], arr[i+1]
|
||||
return i + 1
|
||||
}
|
||||
|
||||
/*
|
||||
运行这个程序:
|
||||
go run 04-testing.go
|
||||
|
||||
实际测试文件示例(calculator_test.go):
|
||||
package calculator
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
result := Add(2, 3)
|
||||
expected := 5
|
||||
if result != expected {
|
||||
t.Errorf("Add(2, 3) = %d; want %d", result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddTable(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
a, b int
|
||||
expected int
|
||||
}{
|
||||
{"positive numbers", 2, 3, 5},
|
||||
{"negative numbers", -1, -2, -3},
|
||||
{"mixed numbers", -1, 2, 1},
|
||||
{"zero", 0, 5, 5},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := Add(tt.a, tt.b)
|
||||
if result != tt.expected {
|
||||
t.Errorf("got %d, want %d", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStringConcat(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = "hello" + "world"
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleAdd() {
|
||||
result := Add(2, 3)
|
||||
fmt.Println(result)
|
||||
// Output: 5
|
||||
}
|
||||
|
||||
运行测试命令:
|
||||
go test # 运行当前包的所有测试
|
||||
go test -v # 详细输出
|
||||
go test -run TestAdd # 运行特定测试
|
||||
go test -bench=. # 运行基准测试
|
||||
go test -cover # 显示覆盖率
|
||||
go test ./... # 运行所有子包的测试
|
||||
|
||||
测试的核心概念:
|
||||
1. 单元测试:验证单个函数或方法的正确性
|
||||
2. 表驱动测试:使用测试数据表驱动测试执行
|
||||
3. 基准测试:测量代码的性能和内存使用
|
||||
4. 示例测试:提供函数使用示例并验证输出
|
||||
|
||||
测试的最佳实践:
|
||||
1. 使用描述性的测试名称
|
||||
2. 保持测试的独立性
|
||||
3. 测试边界条件和错误情况
|
||||
4. 使用表驱动测试处理多个用例
|
||||
5. 编写清晰的错误消息
|
||||
6. 保持高的测试覆盖率
|
||||
7. 定期重构和维护测试代码
|
||||
|
||||
注意事项:
|
||||
1. 测试文件必须以 _test.go 结尾
|
||||
2. 测试函数必须以 Test 开头
|
||||
3. 基准测试函数必须以 Benchmark 开头
|
||||
4. 示例测试函数必须以 Example 开头
|
||||
5. 使用 t.Helper() 标记辅助函数
|
||||
6. 合理使用并行测试提高效率
|
||||
*/
|
@@ -1,15 +1,24 @@
|
||||
# 第九章:高级特性
|
||||
|
||||
本章将学习 Go 语言的一些高级特性,包括反射、泛型、上下文和测试。
|
||||
本章将学习 Go 语言的高级特性,这些特性能帮助你编写更强大和灵活的程序。
|
||||
|
||||
## 学习目标
|
||||
- 了解反射的基本概念和用法
|
||||
- 理解泛型的语法和应用
|
||||
- 掌握 context 包的使用
|
||||
- 学会编写单元测试
|
||||
|
||||
- 掌握反射的基本使用和应用场景
|
||||
- 理解泛型的概念和语法
|
||||
- 学会使用 context 包进行上下文管理
|
||||
- 掌握单元测试的编写方法
|
||||
|
||||
## 文件列表
|
||||
- `01-reflection.go` - 反射
|
||||
- `02-generics.go` - 泛型
|
||||
- `03-context.go` - Context
|
||||
- `04-testing.go` - 测试
|
||||
|
||||
- `01-reflection.go` - 反射机制
|
||||
- `02-generics.go` - 泛型编程
|
||||
- `03-context.go` - 上下文管理
|
||||
- `04-testing.go` - 单元测试
|
||||
|
||||
## 学习建议
|
||||
|
||||
1. 反射是强大但复杂的特性,需要谨慎使用
|
||||
2. 泛型是 Go 1.18+ 的新特性,能提高代码复用性
|
||||
3. Context 是并发编程中的重要工具
|
||||
4. 测试是保证代码质量的重要手段
|
||||
|
Reference in New Issue
Block a user