真正有用的接口应从调用方实际行为倒推定义:先写业务函数,观察其调用的具体方法,再抽取最小、具体、可组合的接口,命名用-er结尾;禁用interface{}作业务接口,避免nil接口panic,坚持“不定义”的克制。

Go 接口设计不是先画蓝图再填实现,而是从调用方实际需要的行为出发倒推定义——接口越小、越具体,越容易复用、测试和替换。
怎么定义一个真正有用的接口?看调用点再写 interface{}
很多人一上来就定义 UserService 或 DataRepository,塞进 6 个方法,结果一半永远用不上。这违背 Go 的接口哲学。
正确做法是:先写业务函数,观察它真正调用了哪些方法,再把这几行签名抽成接口。
- 如果函数里只用了
r.Read(),那就定义type Reader interface { Read(p []byte) (n int, err error) }——别加Close(),哪怕你心里知道“它肯定要关” - 如果另一个函数同时需要读和写,再组合:
type ReadWriter interface { Reader; Writer },而不是一开始就定义大接口 - 接口名优先用
-er结尾(Reader、Validator、Notifier),这是社区共识,不是风格偏好
为什么不能用 interface{} 当业务接口?它根本不是接口
interface{} 是空接口,不是“通用接口”,它是类型擦除的兜底方案,没有行为契约,无法约束实现,也无法做有意义的 mock。
传 interface{} 进函数,你就失去了所有静态检查能力,运行时 panic 风险陡增。
- 想泛化?定义明确行为的接口,比如
type Stringer interface { String() string },比interface{}+ 类型断言更安全、更易读 -
JSON解析、反射等底层场景才该用interface{};业务逻辑层请把它当“危险品”处理
nil 接口 panic 怎么防?别让 nil 接口逃过初始化
声明一个接口变量但没赋值,它的底层是 (nil, nil),直接调方法就是 panic:nil pointer dereference——这不是 bug,是设计使然。
- 常见于字段未初始化:
type Service struct { db DB },但忘了在构造函数里赋值db - 函数返回接口时,确保所有分支都有非
nil返回:if err != nil { return nil, err }是错的,应返回一个空实现或明确错误 - 单元测试里最容易暴露这个问题:mock 对象没正确注入,一跑就崩
组合接口时,别为了“看起来完整”硬凑方法
看到 io.ReadWriteCloser 就以为“我也该搞个 FetchProcessSaveCloser”?这是典型的大接口陷阱。
- 组合只发生在真实共用场景下。比如 HTTP handler 同时需要
http.ResponseWriter和http.Request的某些能力,才考虑组合 - 如果 A 模块只读、B 模块只写、C 模块只关,那它们就该各持一个小接口,而不是强塞进一个三合一接口
接口设计最常被忽略的,是“不定义”的勇气——少写一个方法,往往比多写十个更难,也更重要。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











