接口应精准而非冗余,须满足多实现复用、按需拆分、契约命名、调用方定义、避免过早抽象五大原则。

接口不是越多越好,而是越准越好。盲目声明接口只会增加维护成本、掩盖真实依赖、阻碍重构——尤其当接口方法与实际使用场景脱节时,它就从抽象工具变成枷锁。
接口声明前先问:这个接口真会被多个实现复用吗?
Go 的接口价值在于“被多个类型实现”,而非“为未来预留”。如果一个接口目前只有唯一实现,且短期内无替换计划(比如数据库驱动、加密算法、HTTP 客户端),那它大概率不该存在。
- 常见错误现象:
type UserService interface { GetByID(id int) (*User, error); Create(u *User) error }—— 但整个项目里只有*pgUserService实现它,且所有调用方都直接 new 这个结构体 - 真正需要接口的场景:测试时需 mock(如
http.Client)、插件机制(如日志后端)、策略切换(如不同支付网关) - 判断依据:看是否已有或明确规划 ≥2 个不共享底层逻辑的实现;否则先用具体类型,等第二实现出现再提取接口
避免“胖接口”:只暴露调用方真正需要的方法
一个接口包含 8 个方法,但某个函数只用其中 2 个——这不是解耦,是耦合放大。调用方被迫依赖它不需要的行为,导致实现方无法安全删减方法。
- 典型错误:
type Storer interface { Save(), Load(), Delete(), List(), Migrate(), Backup(), Restore(), HealthCheck() },而 handler 层只调用Save()和Load() - 正确做法:按使用方视角拆分,比如
type Saver interface { Save(...) error }、type Loader interface { Load(...) (... , error) } - 组合更安全:
type DataStore interface { Saver; Loader },比单一大接口更灵活,也避免实现方被强制实现无用方法
接口命名必须反映契约,而非实现细节
以 type UserRepo interface 命名,本质是把数据库访问细节暴露给上层;而 type UserFinder interface 或 type UserProvider interface 则聚焦行为意图,允许后续替换成缓存、API 或内存 map。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
立即学习“go语言免费学习笔记(深入)”;
- 坏名字:
UserDB、PgUserRepo、MySQLAdapter—— 绑定实现,违背接口抽象初衷 - 好名字:
UserGetter、UserInfoSource、AuthenticatedUser—— 描述“能做什么”,不描述“怎么做的” - 验证方式:把接口变量名换成具体类型,读起来是否自然?
var u UserGetter合理;var u PgUserRepo就暴露了不该暴露的信息
接口应随调用方生长,而非随实现方膨胀
接口定义位置决定它的生命周期。放在实现包内(如 pkg/user/pg.go 里定义 UserRepo),会导致其他包为使用它而反向依赖该实现;放在调用方包或独立 contract 包里,才能确保接口由需求驱动。
- 错误结构:
pkg/user/pg/定义UserRepo,pkg/handler/import 它 —— handler 被 pg 绑死 - 推荐结构:
pkg/contract/定义UserGetter,pkg/user/pg/实现它,pkg/handler/只依赖contract - 关键点:接口属于“使用者”,不属于“实现者”。谁调用,谁定义(或共同约定)
最常被忽略的一点:接口一旦导出,就成为公共 API 的一部分。删改它等于破坏兼容性,哪怕只是去掉一个没人用的方法。宁可多写几行类型断言或具体类型转换,也不要为“看起来整洁”而提前抽象。真正的灵活性来自克制,而不是覆盖。

















