其他
Go 多类型共享逻辑时:判断并编写泛型函数/类型…
在 Go 中多个类型共享完全相同的逻辑、接口不足以清晰建模或需要可复用数据结构时,用泛型消除重复,并正确选择约束、命名和类型别名。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 48/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 54/100 ×0.15
适用场景
Go 多类型共享逻辑时:判断并编写泛型函通用
解决思路
- 组合约束、使用 type sets,或在泛型与接口之间选择时,读取 `references/CONSTRAINTS.md`。
- 先写具体类型实现;只有当第二个类型出现时才抽象为泛型。
- 遵循「Write code, don't design types.」:先写代码,不要先设计类型。
- 当多个类型共享完全相同的逻辑(如 sorting、filtering、map/reduce)时,用泛型实现该逻辑。
- 如果不用泛型就会依赖 `any` 和大量 `type switch`,改用泛型。
- 构建可复用数据结构(如 concurrent-safe set、ordered map)时,用泛型参数化元素或键值类型。
- 按决策流操作:多个类型是否共享完全相同逻辑?否则用具体类型;是则问是否有有用的共享接口?有则用接口;没有则用泛型。
- 不要为仅用 `int` 实例化的函数写泛型;例如把泛型 `Sum` 改成具体函数 `func SumInts(vals []int) int`。
- 类型参数命名:一般用 `T`,map key 用 `K`,map value 用 `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`。
- 标准约束优先使用 `constraints` 包或 Go 1.21+ 的 `cmp` 包(如 `cmp.Ordered`),不要自己重写。
- 不要为标准库用法包泛型包装:简单场景直接用 `map[T]struct{}`,不要先写 `type Set[T comparable] struct { m map[T]struct{} }`。
- 只有当泛型能消除多个调用点的重复时才使用泛型;单次使用的泛型只是间接层。
- 如果类型参数只用于满足接口,不要用泛型;直接接受接口,例如用 `func Process(r io.Reader) error`,而不是 `func Process[T io.Reader](r T) error`。
- 避免过度约束:例如 `Contains` 只需要 `comparable`,不要写成 `T interface{ ~int | ~string }`。
- 在判断接口是否已经无需泛型地建模共享行为时,参考 `go-interfaces`。
- 在定义新类型、类型别名或选择 type definition 与 type alias 时,参考 `go-declarations`。
- 为泛型 API 写文档注释和可运行示例时,参考 `go-documentation`。
- 为类型参数或约束接口命名时,参考 `go-naming`。
适用边界 · 注意事项
- Go 版本低于 1.18 时;需要 Go 1.21+ 的 `cmp.Ordered` 时若版本低于 1.21。
- 实践中只实例化一种具体类型时。
- 接口已经清晰建模共享行为,或泛型代码比类型特定替代方案更难读时。