Go接口应聚焦行为契约而非实现细节,只含公开方法签名,优先使用小而专的接口组合,避免大而全或过度嵌套,依调用场景决定粒度,支持隐式实现与渐进演化。

接口定义只暴露行为,不暴露实现细节
Go 接口不是为了“统一返回类型”或“掩盖具体结构”,而是为了表达「能做什么」。比如 io.Reader 只规定 Read([]byte) (int, error),它不管你是从文件、网络还是内存读——这正是调用方无需关心的实现细节。
- 别在接口里塞字段、常量或私有方法;接口里只能有公开方法签名
- 避免定义
ApiResponse这类大而全的接口,它会把 HTTP 状态码、header、body 逻辑混进业务层 - 如果某个方法只在特定实现里有意义(比如
Reset()对bytes.Buffer有用,但对net.Conn没意义),就不要放进共用接口
函数参数用接口,返回值尽量用具体类型
接收 io.Reader 比接收 *bytes.Buffer 更灵活;但返回 io.ReadCloser 会让调用方被迫处理 Close(),哪怕底层根本不需要关闭(比如一个纯内存字节流)。
- 参数侧:优先用小接口(如
Reader、Writer、Stringer),降低依赖强度 - 返回侧:若调用方大概率要调用多个方法(如读完还要关闭),再考虑返回组合接口(如
io.ReadCloser) - 返回具体类型(如
*http.Client)反而更安全——调用方清楚它能干什么,也不用猜是否要Close()
组合小接口,而不是堆砌大接口
Go 的接口嵌入是能力叠加,不是继承。你写 type ReadWriter interface{ Reader; Writer },不是为了“让类型同时有读和写”,而是为了明确声明「这个函数需要同时读写能力」。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 组合名要有语义,比如
LogFlusher(Logger+Flusher),而不是UserAPI - 避免三层以上嵌套(A 嵌入 B,B 嵌入 C),否则责任边界模糊,难以判断哪个方法属于哪层契约
- 一个组合接口通常只服务于一个明确上下文,比如中间件链初始化、资源池构建,而不是到处通用
接口演化比预设更重要
Go 没有版本兼容包袱,接口可以随时加方法。旧实现不满足新接口?补上就行。所以别为“以后可能加”提前塞一堆空方法或 TODO 占位符。
立即学习“go语言免费学习笔记(深入)”;
- 新增方法时,检查已有实现是否真需要它;如果只有个别类型需要,说明该方法本就不该放在这个接口里
- 拆分比扩增更常见:当
DataProcessor出现Read/Validate/Compress三个方法,优先考虑拆成Reader+Validator+Compressor - 隐式实现意味着你永远可以悄悄加类型——只要它实现了对应方法,就自动进入生态,不用改任何已有接口定义
最容易被忽略的是:接口的粒度不是由“看起来整齐”决定的,而是由谁调用、在什么场景下使用决定的。一个函数只用到 Read(),就只依赖 io.Reader;硬塞进 io.ReadWriteSeeker 不仅增加实现负担,还泄露了不必要的能力。

















