SDK 设计与开发者体验
何时使用
当你需要设计开发者爱用的 SDK 时使用——原生感的 API、引导式错误消息、低摩擦体验。本技能涵盖通过卓越的开发者体验驱动采纳,而非激进营销。触发词:"SDK设计"、"SDK design"、"开发者体验"、"DX设计"、"SDK开发"、"client library设计"、"SDK用户体验"、"SDK DX"……
最好的 SDK 营销就是让开发者赞不绝口的 SDK。当你的 SDK 让开发者感到高效和胜任时,他们会成为你的拥护者。当它让他们受挫时,再多的营销也救不了你。
概述
SDK 开发者体验(DX)涵盖开发者使用你的库时的一切感受:
- 发现:他们能多容易地找到并安装它?
- 学习:他们能多快理解如何使用它?
- 使用:他们日常使用有多高效?
- 调试:他们能多容易地修复问题?
- 升级:他们能多无痛地采用新版本?
优秀的 SDK DX 是竞争优势。开发者选择让他们感觉聪明的工具。
开始之前
阅读 developer-audience-context 技能以了解:
- 你的目标开发者使用什么语言和框架?
- 最常见的 IDE/编辑器配置是什么?
- 他们在你的问题领域有多少经验?
- 他们用过哪些竞品 SDK?喜欢/不喜欢什么?
SDK 设计决策应该源于对用户的深入理解。
API 设计原则
原则 1:为常见场景优化
最高频的用例应该需要最少的代码。
好的设计:
# Common case: send a simple message
client.messages.send("Hello world", to="+1234567890")
# Full control when needed
client.messages.send(
body="Hello world",
to="+1234567890",
from_="+0987654321",
status_callback="https://...",
media_urls=["https://..."]
)
差的设计:
# Every call requires full configuration
message = Message(
body="Hello world",
to=PhoneNumber("+1234567890"),
from_=PhoneNumber(config.get_default_from()),
options=MessageOptions(
status_callback=None,
media_urls=[]
)
)
client.messages.send(message)
原则 2:渐进式披露
从简单开始,按需揭示复杂性。
// Level 1: Simplest possible usage
const result = await client.analyze("Hello world");
// Level 2: Common options
const result = await client.analyze("Hello world", {
language: "en",
features: ["sentiment", "entities"]
});
// Level 3: Full control
const result = await client.analyze("Hello world", {
language: "en",
features: ["sentiment", "entities"],
model: "v2-large",
timeout: 30000,
retries: { max: 3, backoff: "exponential" }
});
原则 3:快速且清晰地失败
尽早捕获错误,提供可操作的消息。
好的:
# Validation at construction time
client = MyClient(api_key="")
# Raises immediately: ValueError: API key cannot be empty.
# Get your API key at https://dashboard.example.com/keys
# Clear error at runtime
client.users.get("invalid-id")
# Raises: NotFoundError: User 'invalid-id' not found.
# Use client.users.list() to see available users.
差的:
client = MyClient(api_key="") # No validation
result = client.users.get("invalid-id")
# Returns: None (is this an error? empty result? who knows?)
# Or worse: raises generic Exception with stack trace
原则 4:合理默认值
默认值应该无需配置就能适用于大多数场景。
// This should just work without configuration
const client = new MyClient({ apiKey: process.env.MY_API_KEY });
// Sensible defaults:
// - Automatic retries with exponential backoff
// - Reasonable timeouts
// - JSON content type
// - Standard auth headers
// - Connection pooling
引导式错误消息
错误消息就是文档。让它们有用。
错误消息框架
每条错误消息都应该回答:
- 什么出了问题?
- 为什么会出问题?
- 如何修复?
好的 vs 差的错误消息
好的:
AuthenticationError: Invalid API key provided.
The API key 'sk_test_abc...' (test key) cannot be used for
production requests.
To fix this:
1. Go to https://dashboard.example.com/keys
2. Copy your production API key (starts with 'sk_live_')
3. Update your environment variable: MY_API_KEY=sk_live_...
Docs: https://docs.example.com/authentication
差的:
Error: 401 Unauthorized
需要区分的错误类型
创建开发者可以捕获的特定错误类型:
from myapi.errors import (
AuthenticationError, # Invalid/missing credentials
AuthorizationError, # Valid creds, insufficient permissions
ValidationError, # Invalid input data
NotFoundError, # Resource doesn't exist
RateLimitError, # Too many requests
ServerError, # Our fault, retry might help
)
try:
client.users.get(user_id)
except NotFoundError as e:
# Handle missing user specifically
except AuthenticationError as e:
# Handle auth issues specifically
except MyAPIError as e:
# Catch-all for other API errors
在错误中包含上下文
// Bad: generic error
throw new Error("Invalid parameter");
// Good: contextual error
throw new ValidationError({
message: "Invalid phone number format",
field: "to",
value: "+1abc",
expected: "E.164 format (e.g., +14155551234)",
docs: "https://docs.example.com/phone-numbers"
});
类型安全
类型安全是永不过时的文档。
TypeScript 最佳实践
// Define explicit types for all inputs and outputs
interface User {
id: string;
email: string;
name: string;
createdAt: Date;
metadata?: Record<string, unknown>;
}
interface CreateUserInput {
email: string;
name: string;
metadata?: Record<string, unknown>;
}
// Return types are explicit
async function createUser(input: CreateUserInput): Promise<User> {
// ...
}
// Use discriminated unions for responses
type ApiResponse<T> =
| { success: true; data: T }
| { success: false; error: ApiError };
自动补全驱动设计
为 IDE 自动补全而设计:
// Good: autocomplete shows all options
client.messages.create({
to: "+1...", // IDE shows: (property) to: string
body: "...", // IDE shows: (property) body: string
// User types 'me' and sees 'mediaUrls' autocomplete
});
// Bad: requires memorization
client.send("messages", { /* what goes here? */ });
枚举与字面量类型
// Good: constrained values with autocomplete
type MessageStatus = "queued" | "sending" | "sent" | "failed";
interface Message {
status: MessageStatus; // IDE shows valid values
}
// Bad: any string accepted
interface Message {
status: string; // No guidance, errors at runtime
}
IDE 集成
让发现变得容易
组织 SDK 结构,让 IDE 功能帮助开发者:
// Namespace methods logically
client.users.get(id)
client.users.list()
client.users.create(data)
client.users.update(id, data)
client.users.delete(id)
// After typing 'client.users.' the IDE shows all user operations
到处都是 JSDoc/Docstrings
/**
* Creates a new user in your organization.
*
* @param input - The user details
* @param input.email - Must be a valid email address
* @param input.name - Display name (max 100 characters)
* @returns The created user with generated ID
* @throws {ValidationError} If email format is invalid
* @throws {ConflictError} If email already exists
*
* @example
* const user = await client.users.create({
* email: "jane@example.com",
* name: "Jane Developer"
* });
*/
async createUser(input: CreateUserInput): Promise<User>
内联示例
def send_message(self, body: str, to: str, **kwargs) -> Message:
"""
Send an SMS message.
Args:
body: The message content (max 1600 characters)
to: Recipient phone number in E.164 format
Returns:
Message object with ID and status
Example:
>>> message = client.messages.send(
... body="Hello from Python!",
... to="+14155551234"
... )
>>> print(message.status)
'queued'
"""
版本策略
语义化版本
严格遵循 semver:
- 主版本:破坏性变更(移除、签名变更)
- 次版本:新功能(向后兼容)
- 修订版本:Bug 修复(向后兼容)
什么构成破坏性变更
破坏性变更(需要主版本号升级):
- 移除公共方法或属性
- 变更方法签名
- 变更返回类型
- 变更默认行为
- 移除对某个语言/运行时版本的支持
非破坏性(次版本或修订版本):
- 添加新方法
- 添加可选参数
- 废弃(但不移除)功能
- 修复错误行为的 Bug
废弃流程
import warnings
def old_method(self):
"""
.. deprecated:: 2.3.0
Use :meth:`new_method` instead. Will be removed in 3.0.0.
"""
warnings.warn(
"old_method() is deprecated, use new_method() instead. "
"See migration guide: https://docs.example.com/migrate-v3",
DeprecationWarning,
stacklevel=2
)
return self.new_method()
迁移指南
迁移指南结构
# Migrating from v2 to v3
## Overview
Version 3 introduces [major change] and removes [deprecated feature].
Migration typically takes [time estimate].
## Breaking Changes
### 1. Client Initialization
**Before (v2):**
```python
client = MyClient(key="...")
After (v3):
client = MyClient(api_key="...")
Why: Consistency with other SDK parameters.
2. [Next breaking change]
...
Deprecated Features Removed
client.old_method()- Useclient.new_method()insteadLegacyClass- UseModernClassinstead
New Features
- [Feature that makes migration worthwhile]
Need Help?
- [Migration support channel]
- [Office hours for migration questions]
### Codemods 与自动化
尽可能提供自动化迁移:
```bash
# Provide migration scripts
npx @myapi/migrate-v3
# Or codemods
npx jscodeshift -t @myapi/codemods/v2-to-v3 src/
让 SDK 有原生感
语言惯用法
Python:使用 snake_case、上下文管理器、生成器
# Pythonic
with client.batch() as batch:
for user in client.users.list():
batch.add(user.send_notification("Hello"))
# Not Pythonic
users = client.getUsers()
batch = client.createBatch()
for i in range(len(users)):
batch.addOperation(users[i].sendNotification("Hello"))
batch.execute()
JavaScript:使用 Promises、async/await、解构
// Idiomatic JS
const { data, error } = await client.users.get(id);
// Not idiomatic
client.users.get(id, function(err, result) {
if (err) { /* callback hell */ }
});
Go:使用 error 返回值、接口、channel
// Idiomatic Go
user, err := client.Users.Get(ctx, userID)
if err != nil {
return fmt.Errorf("getting user: %w", err)
}
// Not idiomatic
user := client.Users.Get(userID) // panics on error
匹配生态惯例
- 使用开发者期望的包管理器(npm、pip、gem、go get)
- 遵循该语言中流行库的命名惯例
- 集成流行框架(Express、Django、Rails)
- 支持流行的测试模式
SDK 质量清单
发布前
- 所有公共 API 都有文档
- 所有公共 API 都有类型(在语言支持的情况下)
- 错误消息包含修复步骤
- 文档中的代码示例经过自动测试
- Changelog 已更新所有变更
- ] 破坏性变更有迁移指南
- 移除的功能有废弃警告
优秀 DX
- 快速入门在 5 分钟内成功
- IDE 自动补全适用于所有操作
- 错误可按特定类型捕获
- 重试逻辑处理瞬时故障
- 日志可配置且有用
- 调试模式显示请求/响应详情
工具
SDK 生成
- OpenAPI Generator:从 OpenAPI 规范生成 SDK
- Swagger Codegen:替代生成器
- Speakeasy:现代 SDK 生成平台
- Fern:类型安全的 SDK 生成
测试
- VCR/Betamax:录制和回放 HTTP 交互
- WireMock:模拟 HTTP 服务
- Pact:契约测试
文档
- TypeDoc:TypeScript 文档
- Sphinx:Python 文档
- GoDoc:Go 文档
- YARD:Ruby 文档
相关技能
- docs-as-marketing:展示 SDK 能力的文档
- api-onboarding:SDK 的首次体验
- changelog-updates:有效传达 SDK 变更
- developer-sandbox:无需安装即可试用 SDK
- developer-audience-context:了解 SDK 用户
局限性
- 仅当任务明确匹配其上游来源和本地项目上下文时使用本技能。
- 在应用变更之前,验证命令、生成的代码、依赖项、凭证和外部服务行为。
- 不要将示例视为环境特定测试、安全审查或用户批准破坏性/高成本操作的替代。