Skip to content

2.6 实战与最佳实践 ​

泛型是强大的工具,但不是所有场景都适合。本章总结实战经验和常见陷阱。

什么时候用泛型 ​

✅ 适合泛型的场景 ​

go
// 1. 通用数据结构 — 容器类型
type Stack[T any] struct { ... }
type Cache[K comparable, V any] struct { ... }

// 2. 通用算法 — 操作独立于类型
func Sort[T cmp.Ordered](s []T) { ... }
func Filter[T any](s []T, fn func(T) bool) []T { ... }

// 3. 类型安全的工具函数
func Ptr[T any](v T) *T { return &v }            // 取指针
func Must[T any](v T, err error) T { ... }       // 错误转 panic
func Coalesce[T comparable](vals ...T) T { ... } // 第一个非零值

// 4. 消除 interface{} 和类型断言
// Before: interface{} + 运行时类型断言
// After: 编译时类型安全的泛型

❌ 不适合泛型的场景 ​

go
// 1. 只有一两个调用点 — 泛型的复杂度不值得
// ❌ 过度抽象
func DoWork[T any](data T) { ... }
// ✅ 直接写具体类型
func DoWork(data MyType) { ... }

// 2. 接口已经够用 — 不需要泛型
type Reader interface { Read([]byte) (int, error) }
func Process(r Reader) { ... }  // 接口就够了,不需要泛型

// 3. 方法不能有额外的类型参数
type Container[T any] struct { ... }
// ❌ 不能这样写
func (c Container[T]) Convert[U any]() Container[U] { ... }

// 4. 需要动态分发 — 泛型是编译时的,运行时多态用接口

类型推断的陷阱 ​

go
func Identity[T any](v T) T { return v }

// ✅ 大多数情况推断正常
Identity(42)          // T = int
Identity("hello")     // T = string

// ❌ 无参数无法推断
// func New[T any]() T { ... }
// New()  // 编译错误:无法推断 T
// New[int]()  // 必须显式指定

// ❌ 部分类型参数无法推断
func Convert[From, To any](v From) To { ... }
// Convert("123")  // 编译错误:无法推断 To
// Convert[string, int]("123")  // 必须显式指定

nil 与零值处理 ​

go
// 泛型零值的标准写法
func Zero[T any]() T {
    var zero T
    return zero
}

// nil 只对指针、接口、map、slice、channel、func 有意义
func IsNil[T any](v T) bool {
    // 泛型函数中不能直接写 v == nil(T 不一定是可 nil 类型)
    // 用反射或类型断言解决
    return any(v) == nil
}

性能考量 ​

go
// Go 泛型采用 GC shape 而非完整的单态化
// 相同底层类型的泛型实例共享一份代码

// 泛型开销主要来自:
// 1. 间接调用 — 通过字典传递类型信息
// 2. 额外的函数调用层

// 基准测试建议:
// go test -bench=. -benchmem -gcflags=-d=checkptr=0

// 结论:泛型的运行时开销极低(通常 < 5%),
// 在大多数场景下可以忽略不计

常见模式 ​

模式1:Must — 错误转 panic ​

go
func Must[T any](v T, err error) T {
    if err != nil {
        panic(err)
    }
    return v
}

// 使用
data := Must(os.ReadFile("config.yaml"))
url := Must(url.Parse("https://example.com"))

模式2:Ptr / Val — 指针转换 ​

go
func Ptr[T any](v T) *T  { return &v }
func Val[T any](p *T) T  { return *p }  // 配合指针使用

// 使用
config := Config{Timeout: Ptr(30), Name: Ptr("myapp")}

模式3:Coalesce — 第一个非零值 ​

go
func Coalesce[T comparable](vals ...T) T {
    var zero T
    for _, v := range vals {
        if v != zero {
            return v
        }
    }
    return zero
}

// 使用
name := Coalesce(inputName, configName, "default")

模式4:Result 类型 — 带错误的值 ​

go
type Result[T any] struct {
    Value T
    Err   error
}

func (r Result[T]) Unwrap() (T, error) {
    return r.Value, r.Err
}

func Ok[T any](v T) Result[T]  { return Result[T]{Value: v} }
func Err[T any](err error) Result[T] { return Result[T]{Err: err} }

实战案例:通用缓存层 ​

go
type Cache[K comparable, V any] struct {
    store    map[K]cacheEntry[V]
    ttl      time.Duration
    maxSize  int
    mu       sync.RWMutex
}

type cacheEntry[V any] struct {
    value    V
    expireAt time.Time
}

func NewCache[K comparable, V any](ttl time.Duration, maxSize int) *Cache[K, V] {
    return &Cache[K, V]{
        store:   make(map[K]cacheEntry[V]),
        ttl:     ttl,
        maxSize: maxSize,
    }
}

func (c *Cache[K, V]) Get(key K) (V, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()

    entry, ok := c.store[key]
    if !ok || time.Now().After(entry.expireAt) {
        var zero V
        return zero, false
    }
    return entry.value, true
}

func (c *Cache[K, V]) Set(key K, value V) {
    c.mu.Lock()
    defer c.mu.Unlock()

    if len(c.store) >= c.maxSize {
        c.evict()
    }
    c.store[key] = cacheEntry[V]{
        value:    value,
        expireAt: time.Now().Add(c.ttl),
    }
}

func (c *Cache[K, V]) evict() {
    // 简单随机淘汰(生产环境用 LRU)
    for k := range c.store {
        delete(c.store, k)
        return
    }
}

// 使用 — 一行定义,类型安全
var userCache = NewCache[int, *User](5*time.Minute, 1000)
var configCache = NewCache[string, Config](1*time.Hour, 100)

实战案例:验证框架 ​

go
type Validator[T any] func(T) error

func Validate[T any](v T, validators ...Validator[T]) error {
    for _, vd := range validators {
        if err := vd(v); err != nil {
            return err
        }
    }
    return nil
}

// 组合子
func NotEmpty[T ~string]() Validator[T] {
    return func(v T) error {
        if strings.TrimSpace(string(v)) == "" {
            return fmt.Errorf("不能为空")
        }
        return nil
    }
}

func Min[T Number](min T) Validator[T] {
    return func(v T) error {
        if v < min {
            return fmt.Errorf("不能小于 %v", min)
        }
        return nil
    }
}

func Max[T Number](max T) Validator[T] {
    return func(v T) error {
        if v > max {
            return fmt.Errorf("不能大于 %v", max)
        }
        return nil
    }
}

// And 组合
func And[T any](validators ...Validator[T]) Validator[T] {
    return func(v T) error {
        for _, vd := range validators {
            if err := vd(v); err != nil {
                return err
            }
        }
        return nil
    }
}

// 使用
type CreateUserReq struct {
    Name string
    Age  int
}

var createUserValidator = Validator[CreateUserReq](func(r CreateUserReq) error {
    return Validate(r,
        func(r CreateUserReq) error { return Validate(r.Name, NotEmpty[string]()) },
        func(r CreateUserReq) error { return Validate(r.Age, Min(0), Max(150)) },
    )
})

泛型使用决策树 ​

需要处理多种类型吗?
├── 否 → 直接写具体类型
└── 是
    ├── 需要抽象行为(多态)?
    │   ├── 是 → 用接口(interface)
    │   └── 否
    │       ├── 数据结构/容器?
    │       │   └── 是 → 用泛型 ✅
    │       └── 算法/工具函数?
    │           └── 是 → 用泛型 ✅
    └── 不确定 → 先用具体类型,确有必要时再泛化

要点总结 ​

  1. 泛型最适合容器和算法 — 类型参数表示"不管什么类型,逻辑都一样"
  2. 接口适合行为抽象 — "不管什么类型,只要满足这个接口就行"
  3. 不要为了泛型而泛型 — 只有一两个调用点就不值得抽象
  4. var zero T 是获取泛型零值的标准写法
  5. Go 泛型无运行时类型反射 — switch v.(type) 仍然需要 any
  6. 泛型性能开销极小,不要在没测量的情况下过早优化
  7. 复杂泛型先用具体类型实现,跑通后再泛化