Files
OpenFlare/.agents/skills/go-interfaces/SKILL.md
T
2026-08-04 11:40:32 +08:00

152 lines
5.5 KiB
Markdown

---
name: go-interfaces
description: Use when defining or implementing Go interfaces, designing abstractions, creating mockable boundaries for testing, or composing types through embedding. Also use when deciding whether to accept an interface or return a concrete type, or using type assertions or type switches, even if the user doesn't explicitly mention interfaces. Does not cover generics-based polymorphism (see go-generics).
license: Apache-2.0
metadata:
sources: "Effective Go, Google Style Guide, Uber Style Guide"
allowed-tools: Bash(bash:*)
---
# Go 接口与组合
## 可用脚本
- **`scripts/check-interface-compliance.sh`**——查找缺少编译时合规性检查(`var _ I = (*T)(nil)`)的导出接口。运行 `bash scripts/check-interface-compliance.sh --help` 查看选项。
---
## 接受接口,返回具体类型
接口属于**消费**值的包,而不是**实现**值的包。从构造函数返回具体类型(通常是指针或结构体),这样可以在不重构的情况下添加新方法。
```go
// 好:消费者定义自己需要的接口
package consumer
type Thinger interface { Thing() bool }
func Foo(t Thinger) string { ... }
```
```go
// 好:生产者返回具体类型
package producer
type Thinger struct{ ... }
func (t Thinger) Thing() bool { ... }
func NewThinger() Thinger { return Thinger{ ... } }
```
```go
// 不好:生产者定义并返回自己的接口
package producer
type Thinger interface { Thing() bool }
type defaultThinger struct{ ... }
func NewThinger() Thinger { return defaultThinger{ ... } }
```
**不要在接口被使用之前定义它。** 如果没有现实的使用示例,很难判断接口是否真的有必要。
---
## 通用性:隐藏实现,暴露接口
如果一个类型仅用于实现某个接口,且没有该接口之外的导出方法,则从构造函数返回接口以隐藏实现:
```go
func NewHash() hash.Hash32 {
return &myHash{} // 未导出的类型
}
```
好处:实现可以在不影响调用者的情况下更改,替换算法只需更改构造函数调用。
---
## 类型断言:Comma-Ok 模式
不进行检查的话,失败的断言会导致运行时 panic。始终使用 comma-ok 模式进行安全测试:
```go
str, ok := value.(string)
if ok {
fmt.Printf("string value is: %q\n", str)
}
```
检查值是否实现了某个接口:
```go
if _, ok := val.(json.Marshaler); ok {
fmt.Printf("value %v implements json.Marshaler\n", val)
}
```
---
## 类型切换
重用变量名是惯用做法(`t := t.(type)`)——变量在每个 case 分支中拥有正确的类型。当 case 列出多个类型(`case int, int64:`)时,变量拥有接口类型。
---
## 嵌入
避免在公开结构体中嵌入类型——内部类型的完整方法集将成为你公开 API 的一部分。改用未导出的字段。
> 在使用结构体嵌入进行组合、重写嵌入方法、解决名称冲突、应用 HandlerFunc 适配器模式或决定是否在公开 API 类型中使用嵌入时,阅读 [references/EMBEDDING.md](references/EMBEDDING.md)。
---
## 接口满足检查
使用空标识符赋值在编译时验证类型是否实现了接口:
```go
var _ json.Marshaler = (*RawMessage)(nil)
```
如果 `*RawMessage` 没有实现 `json.Marshaler`,这会导致编译错误。
在以下情况下使用此模式:
- 没有能自动验证接口的静态转换
- 类型必须满足接口才能正确运行(例如自定义 JSON 序列化)
- 接口更改应该导致编译失败,而不是静默降级
**不要**为每个接口都添加这些检查——仅在没有其他静态转换能捕获错误时才使用。
> **验证**:在定义接口或实现后,运行 `bash scripts/check-interface-compliance.sh` 验证所有具体类型都有编译时的 `var _ I = (*T)(nil)` 检查。
---
## 接收者类型
如果不确定,使用指针接收者。不要在单个类型上混合接收者类型——如果任何方法需要指针,则所有方法都使用指针。仅在小型不可变类型(`Point`、`time.Time`)或基本类型上使用值接收者。
> 在为新类型决定使用指针接收者还是值接收者时,特别是对于包含 sync 原语或大型结构体的类型,阅读 [references/RECEIVER-TYPE.md](references/RECEIVER-TYPE.md)。
---
## 快速参考
| 概念 | 模式 | 说明 |
|------|------|------|
| 消费者拥有接口 | 在使用处定义接口 | 不在实现包中 |
| 安全类型断言 | `v, ok := x.(Type)` | 返回零值 + false |
| 类型切换 | `switch v := x.(type)` | 变量在每个 case 中拥有正确类型 |
| 接口嵌入 | `type RW interface { Reader; Writer }` | 方法的并集 |
| 结构体嵌入 | `type S struct { *T }` | 提升 T 的方法 |
| 接口检查 | `var _ I = (*T)(nil)` | 编译时验证 |
| 通用性 | 从构造函数返回接口 | 隐藏实现 |
---
## 相关技能
- **接口命名**:在为接口命名(`-er` 后缀约定)或选择接收者名称时,参见 [go-naming](../go-naming/SKILL.md)
- **错误类型**:在实现 `error` 接口、自定义错误类型或 `errors.As` 匹配时,参见 [go-error-handling](../go-error-handling/SKILL.md)
- **泛型 vs 接口**:在决定是否需要泛型或接口是否已足够时,参见 [go-generics](../go-generics/SKILL.md)
- **函数选项**:在使用基于接口的 Option 模式实现灵活构造函数时,参见 [go-functional-options](../go-functional-options/SKILL.md)
- **编译时检查**:在 API 边界添加 `var _ I = (*T)(nil)` 满足检查时,参见 [go-defensive](../go-defensive/SKILL.md)