对于 .NET 库来说,在现有用户的稳定性与未来创新之间找到平衡非常重要。 库作者倾向于重构和重新思考代码,直到代码完美,但破坏现有用户会产生负面影响,尤其是对于低级别库。
项目类型和重大变更
.NET 社区使用库的方式会影响重大变更对最终用户开发人员的影响。
序列化程序、HTML 分析程序、数据库对象关系映射器或 Web 框架等中低级库受中断性变更影响最大。
最终用户开发人员使用构建基块包生成应用程序,并将其他库用作 NuGet 依赖项。 例如,你正在生成应用程序并使用开源客户端调用 Web 服务。 客户端使用的依赖项的破坏性更新是无法修复的。 这是需要更改的开源客户端,你无法控制它。 必须找到兼容的库版本,或向客户端库提交修补程序,并等待新版本。 最糟糕的情况是,如果您想使用两个库,而这两个库依赖于一个第三方库的相互不兼容的版本。
高级库 (如 UI 控件套件)对中断性变更不太敏感。
高级库直接在最终用户应用程序中引用。 如果发生破坏性变更,开发人员可以选择更新到最新版本,也可以修改其应用程序以适应这些变更。
请务必考虑如何使用您的库。 重大更改会对使用它的应用程序和库产生什么影响?
✔️ 在开发低级 .NET 库时,请尽量减少中断性变更。
✔️ 请考虑将库的重大重写作为新 NuGet 包进行发布。
重大更改的类型
各类重大变更的影响程度不一。
源代码重大更改
源中断性变更不会影响程序执行,但在下次重新编译应用程序时会导致编译错误。 例如,新的重载可以在以前明确的方法调用中创建歧义,或者重命名的参数会中断使用命名参数的调用方。
public class Task
{
// Adding a type called Task could conflict with System.Threading.Tasks.Task at compilation
}
由于源代码中断性变更仅在开发人员重新编译其应用程序时有害,因此这是影响最小的中断性变更。 开发人员可以轻松修复自己的损坏源代码。
行为重大更改
行为更改是最常见的中断性变更类型:几乎任何行为更改都可能导致使用者出现逻辑错误。 对库的更改(例如方法签名、引发的异常或输入或输出数据格式)可能会对库使用者产生负面影响。 如果用户依赖于以前出错的软件行为,那么即使是 bug 修复也可能算作破坏性变更。
添加功能和改进不良行为是一件好事,但毫不小心,它会使现有用户很难升级。 帮助开发人员处理行为重大更改的一种方法是将更改隐藏在设置后面。 设置使开发人员可以更新到库的最新版本,同时可选择加入或退出重大更改。 此策略可让开发人员保持最新状态,同时让其消耗代码随时间推移而进行调整。
例如,ASP.NET Core MVC 具有修改已启用和禁用功能的MvcOptions
的概念。
✔️ 如果新功能影响现有用户,请考虑默认关闭新功能,并允许开发人员选择使用设置加入该功能。
有关 .NET API 中的行为中断性变更的详细信息,请参阅 .NET 行为更改兼容性。
二进制重大更改
更改库的公共 API 时,会发生二进制中断性变更,因此针对较旧版本的库编译的程序集将无法再调用该 API。 例如,通过添加新参数来更改方法签名会导致针对库的较旧版本编译的程序集引发 MissingMethodException。
二进制重大更改还可能会中断整个程序集。 重命名具有的 AssemblyName
程序集将更改程序集的标识,添加、删除或更改程序集的强命名密钥。 程序集标识的更改将中断使用该程序集的所有已编译代码。
❌ 请勿更改程序集名称。
❌ 请勿添加、删除或更改强命名密钥。
✔️ 请考虑使用抽象基类而不是接口。
向接口添加任何内容将导致实现接口的现有类型失败。 抽象基类允许添加默认虚拟实现。
✔️ 请考虑把 ObsoleteAttribute 放置在您打算删除的类型和成员上。 该属性应提供更新代码以不再使用过时 API 的说明。
调用带有 ObsoleteAttribute 的类型和方法的代码将产生一个编译警告,在此警告中显示提供给该属性的消息。 这些警告为使用过时 API 的用户提供时间进行迁移,以便在移除过时 API 时,大多数用户不再依赖它。
public class Document
{
[Obsolete("LoadDocument(string) is obsolete. Use LoadDocument(Uri) instead.")]
public static Document LoadDocument(string uri)
{
return LoadDocument(new Uri(uri));
}
public static Document LoadDocument(Uri uri)
{
// Load the document
}
}
✔️ 请考虑将类型和方法 ObsoleteAttribute 无限期保留在中低级库中。
删除 API 是一种二进制重大更改。 如果维护过时类型和方法的成本较低,同时不会给库增加大量技术债务,可以考虑保留这些类型和方法。 不删除类型和方法有助于避免上述最坏的情况。
有关哪些 .NET API 更改会导致二进制兼容性中断的更多信息,请参阅 .NET 公共契约兼容性。