关于 go-generics
面向 Go 开发者的泛型技能,帮助判断何时以及如何使用类型参数。它建议先编写具体代码,仅在共享逻辑确有需要时再泛化。
主要功能
- 01判断使用泛型还是接口
- 02使用联合与底层类型组合约束
- 03类型参数命名规范
- 04常见问题与标准约束
适用场景
- 01判断重复逻辑是否适合泛型
- 02为可复用 Go 函数设计约束
- 03检查泛型代码是否引入不必要复杂度
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ cxuu/ golang-skills --skill go-generics安装后在 Claude Code 中输入/go-generics确认已加载。
附带文件references/CONSTRAINTS.md
详细介绍
Go 泛型与类型参数
兼容性:泛型要求 Go 1.18+。
资源路由
references/CONSTRAINTS.md- 在编写约束、使用类型集或在泛型与接口之间进行选择时阅读。
何时使用泛型
从具体类型开始。只有在出现第二种类型时才进行泛化。
优先使用泛型的情况
- 多种类型共享相同的逻辑(排序、筛选、映射/归约)
- 否则你会依赖
any和过多的类型切换 - 你正在构建可复用的数据结构(并发安全的集合、有序映射)
避免使用泛型的情况
- 实际上只会实例化一种类型
- 接口已经能清晰地建模共享行为
- 泛型代码比特定类型的替代方案更难阅读
“编写代码,不要设计类型。”——Robert Griesemer 和 Ian Lance Taylor
决策流程
Do multiple types share identical logic?
├─ No → Use concrete types
├─ Yes → Do they share a useful interface?
│ ├─ Yes → Use an interface
│ └─ No → Use generics
不佳:
// Premature generics: only ever called with int
func Sum[T constraints.Integer | constraints.Float](vals []T) T {
var total T
for _, v := range vals {
total += v
}
return total
}
较好:
func SumInts(vals []int) int {
var total int
for _, v := range vals {
total += v
}
return total
}
类型参数命名
| 名称 | 常见用途 |
|---|---|
T | 通用类型参数 |
K | 映射键类型 |
V | 映射值类型 |
E | 元素/项类型 |
对于复杂约束,可以使用简短且具有描述性的名称:
func Marshal[Opts encoding.MarshalOptions](v any, opts Opts) ([]byte, error)
类型别名与类型定义
类型别名(type Old = new.Name)很少使用——仅用于包迁移
或渐进式 API 重构。
约束组合
使用 ~(底层类型)和 |(并集)组合约束:
type Numeric interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~float32 | ~float64
}
func Sum[T Numeric](vals []T) T {
var total T
for _, v := range vals {
total += v
}
return total
}
对于 cmp.Ordered 这类标准约束,请使用 constraints 包或 cmp 包(Go 1.21+),而不是自行编写。
常见陷阱
不要封装标准库类型
// Bad: generic wrapper adds complexity without value
type Set[T comparable] struct {
m map[T]struct{}
}
// Better: use map[T]struct{} directly when the usage is simple
seen := map[string]struct{}{}
只有在多个调用点消除重复时,泛型才值得其带来的复杂性。只使用一次的泛型不过是多了一层间接。
不要为了满足接口而使用泛型
// Bad: T is only used to satisfy an interface — just use the interface
func Process[T io.Reader](r T) error { ... }
// Good: accept the interface directly
func Process(r io.Reader) error { ... }
避免过度约束
// Bad: constraint is more restrictive than needed
func Contains[T interface{ ~int | ~string }](slice []T, target T) bool { ... }
// Good: comparable is sufficient
func Contains[T comparable](slice []T, target T) bool { ... }
快速参考
| 主题 | 指引 |
|---|---|
| 何时使用泛型 | 仅当多种类型共享相同逻辑且接口无法满足需求时 |
| 起点 | 先编写具体代码;之后再泛化 |
| 命名 | 单个大写字母(T、K、V、E) |
| 类型别名 | 同一类型的另一个名称;仅用于迁移 |
| 约束组合 | 使用 ~ 表示底层类型,使用 ` |
| 常见陷阱 | 不要将只使用一次的代码泛型化,也不要在接口足以满足需求时使用泛型 |
相关技能
- 接口与泛型:在判断接口是否已能在不使用泛型的情况下建模共享行为时,请参阅 go-interfaces
- 类型声明:在定义新类型、类型别名,或在类型定义与类型别名之间进行选择时,请参阅 go-declarations
- 编写泛型 API 文档:在为泛型函数编写文档注释和可运行示例时,请参阅 go-documentation
- 类型参数命名:在为类型参数或约束接口选择名称时,请参阅 go-naming
评论
支持 Markdown