Go 1.18 出泛型的时候,团队讨论过要不要用,当时的结论是观望。去年初我们把最低版本升到 1.21 之后,终于把泛型放进了代码规范,允许使用。一年下来,谈谈真实感受。
泛型真正帮到我们的地方,比想象中窄。 一年下来高频使用的场景其实就三类:
- 容器类工具函数。
Map、Filter、Contains这些,以前用interface{}+ 反射写,又慢又不安全,现在干净多了。 - 类型安全的并发原语。我们自己封了个
sync.Map的泛型包装,key 和 value 的类型在编译期就确定,用起来踏实。 - 数据访问层的通用分页查询,一个
func Query[T any](...)替掉了以前代码生成器吐出来的几十行重复代码。
但有些地方我们明确禁止用泛型:
- 业务结构体不搞泛型。
Service[T]、Repo[T]这种"框架感"的东西一旦放出来,业务代码的可读性会断崖式下跌。领域模型就该是具体的,UserRepo比Repo[User]好读得多。 - 能用接口解决的不要用泛型。 泛型和接口最大的区别是:接口是运行时多态,泛型是编译期展开。大部分业务场景需要的是前者。
一个观察:团队里泛型使用量最大的,是以前用 interface{} 最多的那批代码。这说明泛型在 Go 里解决的主要不是"表达力"问题,而是"以前只能靠反射硬扛"的那部分代码的类型安全问题。从这个角度看,泛型是改良,不是革命。
最后记录一个实用主义的心得:升级到 1.21 之后 min/max 内置函数直接删掉了我们自己写的工具函数,这种小快乐积累起来,也是升级的意义。