mcpField
A
api-design

api-design

github.com/wshaddix开发8676 次安装36 周前更新
代码仓库

关于 api-design

面向库作者和评审者的公共 API 与线格式设计技能,涵盖兼容性、扩展式演进及版本管理实践。

主要功能

  1. 01源码、二进制与线格式兼容性
  2. 02扩展式 API 演进
  3. 03命名与参数约定
  4. 04错误及返回类型指导
  5. 05弃用与版本管理实践
  6. 06API 审批测试指导

适用场景

  1. 01设计公共库 API
  2. 02评审拉取请求中的破坏性变更
  3. 03规划分布式线格式更新
  4. 04选择弃用与发布策略

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/wshaddix/dotnet-skills --skill api-design

安装后在 Claude Code 中输入/api-design确认已加载。

详细介绍

公共 API 设计与兼容性

何时使用此技能

在以下情况使用此技能:

  • 为 NuGet 包或库设计公共 API
  • 更改现有公共 API
  • 规划分布式系统的线路格式更改
  • 实施版本控制策略
  • 审查可能造成重大变更的拉取请求

三种兼容性类型

类型定义范围
API/源代码代码可针对较新版本编译公共方法签名、类型
二进制编译后的代码可在较新版本上运行程序集布局、方法令牌
线路序列化数据可由其他版本读取网络协议、持久化格式

破坏其中任何一种兼容性,都会增加用户升级的阻力。


仅扩展式设计

稳定 API 的基础:绝不移除或修改,只进行扩展。

三大支柱
  1. 既有功能保持不变 - 发布后,行为和签名即固定
  2. 通过新构造添加新功能 - 添加重载、新类型和可选择启用的功能
  3. 仅在弃用期结束后移除 - 以年为单位,而不是以发布次数为单位
优势
  • 旧代码在新版本中继续正常工作
  • 新旧实现路径可以共存
  • 默认情况下,升级不会造成破坏性变更
  • 用户可以按照自己的计划升级

API 表面的命名约定

类型命名
类型种类后缀模式示例
基类仅抽象基类型使用 Base 后缀ValidatorBase
接口I 前缀IWidgetFactory
异常Exception 后缀WidgetNotFoundException
特性Attribute 后缀RequiredPermissionAttribute
事件参数EventArgs 后缀WidgetCreatedEventArgs
选项/配置Options 后缀WidgetServiceOptions
构建器Builder 后缀WidgetBuilder
方法命名
模式约定示例
同步动词或动词短语Calculate()、GetWidget()
异步Async 后缀CalculateAsync()、GetWidgetAsync()
布尔查询Is/Has/Can 前缀IsValid()、HasPermission()
Try 模式Try 前缀、out 参数TryGetWidget(int id, out Widget widget)
工厂Create 前缀CreateWidget()、CreateWidgetAsync()
转换To/From 前缀ToDto()、FromEntity()
避免在公共 API 中使用缩写
// WRONG -- abbreviations in public surface
public IReadOnlyList<TxnResult> GetRecentTxns(int cnt);

// CORRECT -- spelled out for clarity
public IReadOnlyList<TransactionResult> GetRecentTransactions(int count);

参数顺序

统一的参数顺序可以减轻认知负担。

标准顺序
  1. 目标/主体 -- 要操作的主要实体
  2. 必需参数 -- 没有默认值的必要输入
  3. 可选参数 -- 具有合理默认值的输入
  4. 取消令牌 -- 始终放在最后(CA1068 约定强制执行)
public Task<Widget> GetWidgetAsync(
    int widgetId,                              // 1. Target
    WidgetOptions options,                     // 2. Required
    bool includeHistory = false,               // 3. Optional
    CancellationToken cancellationToken = default); // 4. Always last
重载递进
// Simple -- sensible defaults
public Task<Widget> GetWidgetAsync(int widgetId,
    CancellationToken cancellationToken = default)
    => GetWidgetAsync(widgetId, WidgetOptions.Default, cancellationToken);

// Detailed -- full control
public Task<Widget> GetWidgetAsync(int widgetId,
    WidgetOptions options,
    CancellationToken cancellationToken = default);

返回类型选择

返回类型的选择
场景返回类型理由
单个实体,始终存在Widget未找到时抛出异常
单个实体,可能不存在Widget?可空类型表示可选性
集合,可能为空IReadOnlyList<Widget>不可变、可通过索引访问,并表示不可修改
流式结果IAsyncEnumerable<Widget>避免缓冲整个结果集
包含详细信息的操作结果Result<Widget> / 判别联合无需异常即可提供丰富的错误信息
异步空返回值Task除事件处理程序外,绝不使用 async void
经常同步完成ValueTask<Widget>缓存命中时避免分配 Task
优先使用 IReadOnlyList,而非 IEnumerable
// WRONG -- caller does not know if result is materialized or lazy
public IEnumerable<Widget> GetWidgets();

// CORRECT -- signals materialized, indexable collection
public IReadOnlyList<Widget> GetWidgets();

// CORRECT -- signals streaming/lazy evaluation explicitly
public IAsyncEnumerable<Widget> GetWidgetsStreamAsync(
    CancellationToken cancellationToken = default);
Try 模式
public bool TryGetWidget(int widgetId, [NotNullWhen(true)] out Widget? widget);

public Task<Widget?> TryGetWidgetAsync(int widgetId,
    CancellationToken cancellationToken = default);

错误报告策略

异常层次结构
public class WidgetServiceException : Exception
{
    public WidgetServiceException(string message) : base(message) { }
    public WidgetServiceException(string message, Exception inner) : base(message, inner) { }
}

public class WidgetNotFoundException : WidgetServiceException
{
    public int WidgetId { get; }
    public WidgetNotFoundException(int widgetId)
        : base($"Widget {widgetId} not found.") => WidgetId = widgetId;
}

public class WidgetValidationException : WidgetServiceException
{
    public IReadOnlyList<string> Errors { get; }
    public WidgetValidationException(IReadOnlyList<string> errors)
        : base("Widget validation failed.") => Errors = errors;
}
何时使用异常与返回值
方式适用场景
抛出异常意外故障、编程错误、基础设施故障
返回 null / default“未找到”是正常且符合预期的结果
Try 模式(bool + out)解析或验证失败很常见且操作同步的场景
结果对象存在多种调用方需要区分的失败类型
参数验证
public Widget CreateWidget(string name, decimal price)
{
    ArgumentException.ThrowIfNullOrWhiteSpace(name);
    ArgumentOutOfRangeException.ThrowIfNegativeOrZero(price);

    return new Widget(name, price);
}

API 变更指南

安全变更(任何版本均可)
// ADD new overloads with default parameters
public void Process(Order order, CancellationToken ct = default);

// ADD new optional parameters to existing methods
public void Send(Message msg, Priority priority = Priority.Normal);

// ADD new types, interfaces, enums
public interface IOrderValidator { }
public enum OrderStatus { Pending, Complete, Cancelled }

// ADD new members to existing types
public class Order
{
    public DateTimeOffset? ShippedAt { get; init; }  // NEW
}
不安全变更(禁止,或仅限主版本)
// REMOVE or RENAME public members
public void ProcessOrder(Order order);  // Was: Process()

// CHANGE parameter types or order
public void Process(int orderId);  // Was: Process(Order order)

// CHANGE return types
public Order? GetOrder(string id);  // Was: public Order GetOrder()

// CHANGE access modifiers
internal class OrderProcessor { }  // Was: public

// ADD required parameters without defaults
public void Process(Order order, ILogger logger);  // Breaks callers!
弃用模式
// Step 1: Mark as obsolete with version
[Obsolete("Obsolete since v1.5.0. Use ProcessAsync instead.")]
public void Process(Order order) { }

// Step 2: Add new recommended API
public Task ProcessAsync(Order order, CancellationToken ct = default);

// Step 3: Remove in next major version

扩展点

基于接口的扩展
// GOOD -- interface-based extension point
public interface IWidgetValidator
{
    ValueTask<bool> ValidateAsync(Widget widget, CancellationToken ct = default);
}

// GOOD -- delegate-based extension for simple hooks
public class WidgetServiceOptions
{
    public Func<Widget, CancellationToken, ValueTask>? OnWidgetCreated { get; set; }
}
扩展方法指南
指南原因
将扩展方法放在与类型相同的命名空间中无需额外的 using 语句即可发现
绝不要将扩展方法放在 System 或 System.Linq 中避免命名空间污染
如果类型由你维护,优先使用实例方法而非扩展方法扩展方法是最后的选择
将 this 参数设为最具体且可用的类型避免污染 IntelliSense

线格式兼容性

对于分布式系统,序列化数据必须能跨版本读取。

要求
方向要求
向后兼容旧写入方 → 新读取方
向前兼容新写入方 → 旧读取方

零停机滚动升级需要同时满足这两项要求。

安全演进线格式

阶段 1:添加读取端支持

public sealed record HeartbeatV2(
    Address From,
    long SequenceNr,
    long CreationTimeMs);  // NEW field

public object Deserialize(byte[] data, string manifest) => manifest switch
{
    "Heartbeat" => DeserializeHeartbeatV1(data),
    "HeartbeatV2" => DeserializeHeartbeatV2(data),
    _ => throw new NotSupportedException()
};

阶段 2:启用写入端(下一个次版本)

akka.cluster.use-heartbeat-v2 = on
防御性序列化设计
public sealed class WidgetDto
{
    [JsonPropertyName("id")]
    public int Id { get; init; }

    [JsonPropertyName("name")]
    public required string Name { get; init; }

    [JsonPropertyName("category")]
    public string? Category { get; init; }

    [JsonPropertyName("priority")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
    public int Priority { get; init; }
}
枚举序列化策略
// GOOD -- string serialization is rename-safe
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum WidgetStatus
{
    Draft,
    Active,
    Archived
}

// RISKY -- integer serialization breaks when members are reordered
public enum WidgetPriority
{
    Low = 0,
    Medium = 1,
    High = 2
}

API 审批测试

通过自动化 API 表面测试,防止意外的破坏性变更。

[Fact]
public Task ApprovePublicApi()
{
    var api = typeof(MyLibrary.PublicClass).Assembly.GeneratePublicApi();
    return Verify(api);
}
PR 审查流程
  1. PR 包含对 *.verified.txt 文件的更改
  2. 审查者可以在差异中看到精确的 API 表面变更
  3. 破坏性变更会立即显现
  4. 必须经过审慎决策才能批准

版本控制策略

语义化版本控制(实践指南)
版本允许的变更
补丁(1.0.x)Bug 修复、安全补丁
次版本(1.x.0)新功能、弃用、移除过时内容
主版本(x.0.0)破坏性变更、移除旧 API
核心原则
  1. 避免意外破坏——即使是主版本,也应提前公告
  2. 随时扩展——新 API 可以随任何版本发布
  3. 先弃用,再移除——至少提前一个次版本使用 [Obsolete]
  4. 说明时间安排——用户需要规划升级

Pull Request 检查清单

  • 没有移除公共成员(改用 [Obsolete])
  • 没有更改签名(改为添加重载)
  • 没有新增必需参数(使用默认值)
  • 已更新 API 审批测试(已审查 .verified.txt 的更改)
  • 线格式变更为可选启用(先支持读取端)
  • 已记录破坏性变更(发布说明、迁移指南)

反模式

伪装成修复的破坏性变更
// "Bug fix" that breaks users
public async Task<Order> GetOrderAsync(OrderId id)  // Was sync!
{
}

// Correct: Add new method, deprecate old
[Obsolete("Use GetOrderAsync instead")]
public Order GetOrder(OrderId id) => GetOrderAsync(id).Result;

public async Task<Order> GetOrderAsync(OrderId id) { }
静默的行为变更
// Changing defaults breaks users
public void Configure(bool enableCaching = true)  // Was: false!

// Correct: New parameter with new name
public void Configure(
    bool enableCaching = false,
    bool enableNewCaching = true)
多态序列化
// AVOID: Type names in wire format
{ "$type": "MyApp.Order, MyApp", "Id": 123 }

// PREFER: Explicit discriminators
{ "type": "order", "id": 123 }

Agent 注意事项

  1. 公共 API 名称中不要使用缩写——应拼写完整单词。
  2. 不要将 CancellationToken 放在可选参数之前——CA1068 要求它位于最后。
  3. 不要从公共 API 返回可变集合——应返回 IReadOnlyList<T>。
  4. 更改序列化属性名称时,必须添加 [JsonPropertyName] 注解。
  5. 不要向现有公共方法添加必需参数——应添加重载或使用默认值。
  6. 不要在 API 表面中使用 async void——应返回 Task 或 ValueTask。
  7. 设计异常层次结构时,必须包含基础库异常作为基类。
  8. 不要将扩展方法放在 System 命名空间中。

资源

评论

支持 Markdown