npx skills add https://github.com/aaronontheweb/dotnet-skills --skill api-design在以下情况下使用此技能:
| 类型 | 定义 | 范围 |
|---|---|---|
| API/源代码 | 代码能针对新版本编译 | 公共方法签名、类型 |
| 二进制 | 编译后的代码能在新版本上运行 | 程序集布局、方法标记 |
| 线格式 | 序列化数据能被其他版本读取 | 网络协议、持久化格式 |
破坏其中任何一种都会给用户带来升级阻力。
稳定 API 的基础:永不删除或修改,只进行扩展。
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
资源:
// 添加带有默认参数的新重载
public void Process(Order order, CancellationToken ct = default);
// 向现有方法添加新的可选参数
public void Send(Message msg, Priority priority = Priority.Normal);
// 添加新类型、接口、枚举
public interface IOrderValidator { }
public enum OrderStatus { Pending, Complete, Cancelled }
// 向现有类型添加新成员
public class Order
{
public DateTimeOffset? ShippedAt { get; init; } // 新增
}
// 移除或重命名公共成员
public void ProcessOrder(Order order); // 原为:Process()
// 更改参数类型或顺序
public void Process(int orderId); // 原为:Process(Order order)
// 更改返回类型
public Order? GetOrder(string id); // 原为:public Order GetOrder()
// 更改访问修饰符
internal class OrderProcessor { } // 原为:public
// 添加没有默认值的必需参数
public void Process(Order order, ILogger logger); // 破坏调用者!
// 步骤 1:标记为已过时并注明版本(任何版本)
[Obsolete("自 v1.5.0 起已过时。请改用 ProcessAsync。")]
public void Process(Order order) { }
// 步骤 2:添加新的推荐 API(同一版本)
public Task ProcessAsync(Order order, CancellationToken ct = default);
// 步骤 3:在下一个主版本中移除(v2.0+)
// 仅在用户有足够时间迁移后进行
通过自动化的 API 表面测试防止意外的破坏性变更。
dotnet add package PublicApiGenerator
dotnet add package Verify.Xunit
[Fact]
public Task ApprovePublicApi()
{
var api = typeof(MyLibrary.PublicClass).Assembly.GeneratePublicApi();
return Verify(api);
}
创建 ApprovePublicApi.verified.txt:
namespace MyLibrary
{
public class OrderProcessor
{
public OrderProcessor() { }
public void Process(Order order) { }
public Task ProcessAsync(Order order, CancellationToken ct = default) { }
}
}
任何 API 变更都会导致测试失败 - 审查者必须明确批准变更。
*.verified.txt 文件的更改对于分布式系统,序列化数据必须能在不同版本间读取。
| 方向 | 要求 |
|---|---|
| 向后兼容 | 旧写入者 → 新读取者(当前版本能读取旧数据) |
| 向前兼容 | 新写入者 → 旧读取者(旧版本能读取新数据) |
两者都是零停机滚动升级所必需的。
阶段 1:添加读取端支持(可选)
// 新消息类型 - 读取器先部署
public sealed record HeartbeatV2(
Address From,
long SequenceNr,
long CreationTimeMs); // 新字段
// 反序列化器同时处理新旧格式
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
阶段 3:设为默认(未来版本)
在安装基础已吸收读取端代码之后。
优先选择基于模式的格式而非基于反射的格式:
| 格式 | 类型 | 线格式兼容性 |
|---|---|---|
| Protocol Buffers | 基于模式 | 优秀 - 明确的字段编号 |
| MessagePack | 基于模式 | 良好 - 配合契约 |
| System.Text.Json | 基于模式(配合源生成) | 良好 - 明确的属性 |
| Newtonsoft.Json | 基于反射 | 差 - 类型名称包含在有效负载中 |
| BinaryFormatter | 基于反射 | 极差 - 切勿使用 |
详情请参阅 dotnet/serialization 技能。
明确标记非公共 API:
// 用于文档的属性
[InternalApi]
public class ActorSystemImpl { }
// 命名空间约定
namespace MyLibrary.Internal
{
public class InternalHelper { } // 为可扩展性而公开,非为用户
}
清晰记录:
.Internal命名空间中的类型或标记有[InternalApi]的类型可能在任意版本间变更,恕不另行通知。
// 应做:密封非为继承设计的类
public sealed class OrderProcessor { }
// 不应做:意外地不密封
public class OrderProcessor { } // 用户可能继承,从而阻碍变更
// 应做:小巧、专注的接口
public interface IOrderReader
{
Order? GetById(OrderId id);
}
public interface IOrderWriter
{
Task SaveAsync(Order order);
}
// 不应做:庞大的接口(添加方法会破坏兼容性)
public interface IOrderRepository
{
Order? GetById(OrderId id);
Task SaveAsync(Order order);
// 添加新方法会破坏所有实现!
}
| 版本 | 允许的变更 |
|---|---|
| 补丁 (1.0.x) | 错误修复、安全补丁 |
| 次版本 (1.x.0) | 新功能、弃用、移除已过时 API |
| 主版本 (x.0.0) | 破坏性变更、移除旧 API |
[Obsolete] 至少持续一个次版本在移除或更改某物之前,先理解它为何存在。
假设每个公共 API 都有人在使用。如果你想更改它:
审查涉及公共 API 的 PR 时:
[Obsolete]).verified.txt 变更)// 破坏用户的“错误修复”
public async Task<Order> GetOrderAsync(OrderId id) // 原为同步!
{
// “修复”为异步 - 但破坏了所有调用者
}
// 正确做法:添加新方法,弃用旧方法
[Obsolete("请改用 GetOrderAsync")]
public Order GetOrder(OrderId id) => GetOrderAsync(id).Result;
public async Task<Order> GetOrderAsync(OrderId id) { }
// 更改默认值会破坏依赖旧行为的用户
public void Configure(bool enableCaching = true) // 原为:false!
// 正确做法:使用新名称的新参数
public void Configure(
bool enableCaching = false, // 保留原始默认值
bool enableNewCaching = true) // 新行为可选
// 避免:线格式中包含类型名称
{ "$type": "MyApp.Order, MyApp", "Id": 123 }
// 重命名 Order 类 = 线格式破坏!
// 推荐:明确的鉴别器
{ "type": "order", "id": 123 }
每周安装量
114
仓库
GitHub 星标数
495
首次出现
2026年1月28日
安全审计
安装于
claude-code88
codex73
opencode72
github-copilot69
gemini-cli67
kimi-cli64
Use this skill when:
| Type | Definition | Scope |
|---|---|---|
| API/Source | Code compiles against newer version | Public method signatures, types |
| Binary | Compiled code runs against newer version | Assembly layout, method tokens |
| Wire | Serialized data readable by other versions | Network protocols, persistence formats |
Breaking any of these creates upgrade friction for users.
The foundation of stable APIs: never remove or modify, only extend.
Resources:
// 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 (any release)
[Obsolete("Obsolete since v1.5.0. Use ProcessAsync instead.")]
public void Process(Order order) { }
// Step 2: Add new recommended API (same release)
public Task ProcessAsync(Order order, CancellationToken ct = default);
// Step 3: Remove in next major version (v2.0+)
// Only after users have had time to migrate
Prevent accidental breaking changes with automated API surface testing.
dotnet add package PublicApiGenerator
dotnet add package Verify.Xunit
[Fact]
public Task ApprovePublicApi()
{
var api = typeof(MyLibrary.PublicClass).Assembly.GeneratePublicApi();
return Verify(api);
}
Creates ApprovePublicApi.verified.txt:
namespace MyLibrary
{
public class OrderProcessor
{
public OrderProcessor() { }
public void Process(Order order) { }
public Task ProcessAsync(Order order, CancellationToken ct = default) { }
}
}
Any API change fails the test - reviewer must explicitly approve changes.
*.verified.txt filesFor distributed systems, serialized data must be readable across versions.
| Direction | Requirement |
|---|---|
| Backward | Old writers → New readers (current version reads old data) |
| Forward | New writers → Old readers (old version reads new data) |
Both are required for zero-downtime rolling upgrades.
Phase 1: Add read-side support (opt-in)
// New message type - readers deployed first
public sealed record HeartbeatV2(
Address From,
long SequenceNr,
long CreationTimeMs); // NEW field
// Deserializer handles both old and new
public object Deserialize(byte[] data, string manifest) => manifest switch
{
"Heartbeat" => DeserializeHeartbeatV1(data), // Old format
"HeartbeatV2" => DeserializeHeartbeatV2(data), // New format
_ => throw new NotSupportedException()
};
Phase 2: Enable write-side (opt-out, next minor version)
// Config to enable new format (off by default initially)
akka.cluster.use-heartbeat-v2 = on
Phase 3: Make default (future version)
After install base has absorbed read-side code.
Prefer schema-based formats over reflection-based:
| Format | Type | Wire Compatibility |
|---|---|---|
| Protocol Buffers | Schema-based | Excellent - explicit field numbers |
| MessagePack | Schema-based | Good - with contracts |
| System.Text.Json | Schema-based (with source gen) | Good - explicit properties |
| Newtonsoft.Json | Reflection-based | Poor - type names in payload |
| BinaryFormatter | Reflection-based | Terrible - never use |
See dotnet/serialization skill for details.
Mark non-public APIs explicitly:
// Attribute for documentation
[InternalApi]
public class ActorSystemImpl { }
// Namespace convention
namespace MyLibrary.Internal
{
public class InternalHelper { } // Public for extensibility, not for users
}
Document clearly:
Types in
.Internalnamespaces or marked with[InternalApi]may change between any releases without notice.
// DO: Seal classes not designed for inheritance
public sealed class OrderProcessor { }
// DON'T: Leave unsealed by accident
public class OrderProcessor { } // Users might inherit, blocking changes
// DO: Small, focused interfaces
public interface IOrderReader
{
Order? GetById(OrderId id);
}
public interface IOrderWriter
{
Task SaveAsync(Order order);
}
// DON'T: Monolithic interfaces (can't add methods without breaking)
public interface IOrderRepository
{
Order? GetById(OrderId id);
Task SaveAsync(Order order);
// Adding new methods breaks all implementations!
}
| Version | Changes Allowed |
|---|---|
| Patch (1.0.x) | Bug fixes, security patches |
| Minor (1.x.0) | New features, deprecations, obsolete removal |
| Major (x.0.0) | Breaking changes, old API removal |
[Obsolete] for at least one minor versionBefore removing or changing something, understand why it exists.
Assume every public API is used by someone. If you want to change it:
When reviewing PRs that touch public APIs:
[Obsolete] instead).verified.txt changes reviewed)// "Bug fix" that breaks users
public async Task<Order> GetOrderAsync(OrderId id) // Was sync!
{
// "Fixed" to be async - but breaks all callers
}
// 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 who relied on old behavior
public void Configure(bool enableCaching = true) // Was: false!
// Correct: New parameter with new name
public void Configure(
bool enableCaching = false, // Original default preserved
bool enableNewCaching = true) // New behavior opt-in
// AVOID: Type names in wire format
{ "$type": "MyApp.Order, MyApp", "Id": 123 }
// Renaming Order class = wire break!
// PREFER: Explicit discriminators
{ "type": "order", "id": 123 }
Weekly Installs
114
Repository
GitHub Stars
495
First Seen
Jan 28, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
claude-code88
codex73
opencode72
github-copilot69
gemini-cli67
kimi-cli64
Lark Skill Maker 教程:基于飞书CLI创建AI技能,自动化工作流与API调用指南
30,100 周安装
LangSmith Trace 追踪工具:AI应用调试与性能监控的完整指南
953 周安装
GoFrame v2 开发框架:Go语言企业级项目脚手架与最佳实践指南
951 周安装
ClawHub CLI:AI开发工具包管理神器,一键安装、更新、发布技能
971 周安装
Claude智能体开发指南:创建自主AI助手,掌握agent-development核心技巧
982 周安装
Buttercup 系统调试指南:Kubernetes Pod 故障排查与 Redis 级联问题解决
980 周安装
memory-setup技能:为Moltbot/Clawdbot配置持久化记忆,实现智能体长期记忆管理
970 周安装