wavelet init

This commit is contained in:
ryan
2026-06-18 15:24:48 +08:00
parent d6a7011885
commit 99738bbc17
714 changed files with 139987 additions and 0 deletions
@@ -0,0 +1,129 @@
# 函数式选项 vs 配置结构体
> **来源**:Google 风格指南, Uber 风格指南
函数式选项和配置结构体解决相同的问题 — 构造函数的可选配置 — 但它们有不同的权衡。根据 API 受众、可扩展性需求和复杂性预算来选择。
## 决策框架
```
需要可选配置?
├─ 内部或仅测试 API?
│ └─ 配置结构体(更简单,更少样板代码)
├─ 具有 3 个以上选项的公共 API?
│ └─ 函数式选项(可扩展,向后兼容)
├─ 选项需要校验或有相互依赖?
│ └─ 函数式选项(在 apply 或构造函数中校验)
├─ 所有选项通常一起指定?
│ └─ 配置结构体(一个字面量,无需 With* 仪式)
└─ 选项可能随时间增长?
└─ 函数式选项(添加 With* 不会破坏调用者)
```
## 配置结构体模式
配置结构体将可选参数分组为传递给构造函数的单个结构体。零值作为默认值,或提供 `DefaultConfig()`。
**好**
```go
type Config struct {
Timeout time.Duration // 零 = 无超时
MaxRetry int // 零 = 无重试
Logger *log.Logger // nil = 丢弃
}
func NewClient(addr string, cfg Config) *Client {
if cfg.Logger == nil {
cfg.Logger = log.New(io.Discard, "", 0)
}
return &Client{addr: addr, cfg: cfg}
}
```
```go
c := NewClient("localhost:8080", Config{
Timeout: 5 * time.Second,
MaxRetry: 3,
})
```
**不好** — 在公共 API 中依赖未导出的配置字段:
```go
type config struct { // 未导出:调用者无法构造
timeout time.Duration
}
func NewClient(addr string, cfg config) *Client { ... }
```
### 当零值不适用时
如果零是一个有效的非默认值(例如,超时为 0 表示"无超时",但期望的默认值是 30s),使用指针字段或哨兵值:
```go
type Config struct {
Timeout *time.Duration // nil = 使用默认值(30s),零 = 无超时
}
```
## 比较
| 方面 | 函数式选项 | 配置结构体 |
|------|-----------|-----------|
| **样板代码** | 高(每个选项需要类型 + apply + With*) | 低(一个结构体) |
| **可扩展性** | 添加 `With*` — 无破坏性变更 | 添加字段 — 无破坏性变更 |
| **向后兼容** | 对公共 API 极好 | 好(新字段获得零值) |
| **默认值** | 内置于构造函数 | 零值或 `DefaultConfig()` |
| **校验** | 在 `apply` 或构造函数循环中 | 在接收到结构体后的构造函数中 |
| **可发现性** | `With*` 函数出现在 godoc 中 | 所有字段在一个结构体中可见 |
| **可测试性** | 比较选项或测试构造函数输出 | 比较结构体字面量 |
| **调用者体验** | 只指定与默认值不同的部分 | 必须构造结构体字面量 |
| **零值歧义** | 无 — 未设置的选项不应用 | 可能需要指针字段 |
## 何时优先使用配置结构体
- **内部 API** — 更少的仪式,在调用处更易读
- **少量选项(1-3 个)** — 函数式选项的开销不值得
- **所有选项通常一起设置** — 可变参数风格没有好处
- **不需要校验** — 简单的字段赋值即可
- **选项是数据而非行为** — 结构体字段自然映射
```go
srv := NewServer(Config{
Port: 8080,
TLSCert: "/path/to/cert.pem",
TLSKey: "/path/to/key.pem",
})
```
## 何时优先使用函数式选项
- **公共/库 API** — 调用者不应跟踪内部配置的演变
- **3 个以上选项**,每个都是可选的
- **复杂默认值** — 默认值计算依赖于其他选项
- **按选项校验** — 在 apply 时拒绝无效值
- **选项可能增长** — 新的 `With*` 函数是纯粹增量的
```go
srv := NewServer(
WithPort(8080),
WithTLS("/path/to/cert.pem", "/path/to/key.pem"),
WithLogger(logger),
)
```
## 混合方法
对于同时需要便利性和可扩展性的 API,接受配置结构体用于常见设置,函数式选项用于高级覆盖:
```go
func NewServer(cfg Config, opts ...Option) *Server {
s := &Server{cfg: cfg}
for _, o := range opts {
o.apply(&s.cfg)
}
return s
}
```
谨慎使用 — 它增加了复杂性。每个 API 优先使用一种方法。