Fuchsia API 的演变准则

本部分包含面向 Fuchsia 贡献者更改 Fuchsia Platform API 的指南。在开始之前,您应该先熟悉以下概念:

平台 API 的生命周期

Fuchsia 平台 API 应遵循以下生命周期:“已添加”→“已弃用”→“已移除”→“已删除”,如下所示:

此图显示了 Fuchsia 平台上 API 的生命周期,从添加 API、废弃、移除,最后到废弃的版本开始。

以下部分介绍了作为 API 开发者如何管理此生命周期。

添加 FIDL API

始终使用 @available 属性为新的 FIDL API 添加注解。不稳定的 API 应在 HEAD API 级别添加。请注意,根据设计,使用该 SDK 的合作伙伴不能以 HEAD API 级别为目标。

例如:

@available(added=HEAD)
library fuchsia.examples.docs;

稳定版 API 应该是处于开发阶段的 API 级别added。这意味着,从当前处于开发阶段的 API 级别开始,此 API 可用,并且如果没有适当的弃用流程,它不会发生变化。例如:

// At the time of writing the in development level was 10.
@available(added=10)
library fuchsia.examples.docs;

当一个 FIDL 库有多个 .fidl 文件时,该库应包含一个单独的 overview.fidl 文件,并且应在该文件中写入 @available 属性以及一条描述该库的文档注释。如需了解详情,请参阅 FIDL 样式指南

合作伙伴 SDK 类别中的每个 API 都已选择在 CI/CQ 中接受静态兼容性测试。如果 API 以向后不兼容的方式更改,这些测试会失败。如果您的 API 不稳定,不妨考虑将其添加到内部或实验性 SDK 类别中,以防止合作伙伴依赖该 API 并选择退出静态兼容性测试,从而允许 API 随意更改。在该 API 稳定后,将其添加到合作伙伴类别中。

取代 FIDL API

有时,您需要将 API 替换为新的定义。如需在 API 级别 N 执行此操作,请使用 @available(replaced=N) 为旧定义添加注解,使用 @available(added=N) 为新定义添加注解。例如,您可以在 API 级别 5 中更改常量的值:

@available(replaced=5)
const MAX_LENGTH uint32 = 16;
@available(added=5)
const MAX_LENGTH uint32 = 32;

废弃 FIDL API

您始终应在移除 API 之前的级别弃用该 API。如果最终开发者以已弃用的 API 为目标平台,他们在构建时会看到一条警告,指出该 API 已废弃,他们应迁移到替代方案。您应添加备注,帮助最终开发者找到替代方案。例如:

protocol Example {
    // (Description of the method.)
    //
    // # Deprecation
    //
    // (Detailed explanation of why the method is deprecated, the timeline for
    // removing it, and what should be used instead.)
    @available(deprecated=5, removed=6, note="use Replacement")
    Deprecated();

    @available(added=5)
    Replacement();
};

废弃和移除某个 API 之间必须相差至少一个 API 级别。 不过,在添加某个 API 时,废弃该 API 是完全可行的。例如:

// These are OK.
@available(deprecated=5, removed=6)
@available(deprecated=5, removed=100)
@available(added=5, deprecated=5)

// These will not compile.
@available(deprecated=5, removed=5)
@available(deprecated=5, removed=3)

移除 FIDL API

请注意,您始终应在移除某个 API 之前先将其弃用,并尽可能在移除该 API 时保留相应 ABI

建议使用其 @available 属性移除 API。这是我们通常建议采用的方法。例如,如果某个 API 是在级别 10 添加的,那么您可以在级别 12 将其移除,如下所示:

@available(added=10, removed=12)
library fuchsia.examples.docs;

在此示例中,目标级别为 10 或 11 的最终开发者会看到 fuchsia.examples.docs 库的客户端绑定,而目标级别为级别 12 或更高级别的开发者则不会看到客户端绑定。如果在平台停止支持 API 级别 12 之前移除了此 API 的源代码,此 API 的静态兼容性测试将失败,并且需要获得 //sdk/history/OWNERS 的特殊批准才能提交更改。当 Fuchsia 平台停止支持 API 级别 12 时,可以删除 API 的源代码。

或者,您也可以删除 API 的源代码,对于大多数使用场景,我们不建议这样做。如果该 API 是在开发阶段的 API 级别或当前支持的上一个 API 级别添加的,这会将该 API 从 Fuchsia 的历史记录中移除,这通常是不允许的。在这种情况下,静态兼容性测试将失败,您需要获得 //sdk/history/OWNERS 的特别批准才能提交更改。如果此 API 是在处于开发阶段的 API 级别之上的任何级别(包括特殊的 HEAD API 级别)添加的,则可以采用此移除方法。

在移除 FIDL API 时保留 ABI

您可以从 SDK 中移除 API 的客户端绑定,这会阻止未来的最终开发者以该 API 为目标,同时保留平台的 API 实现(即 ABI)。此功能允许现有应用在较新版本的平台上运行。如果某个 API 已从 SDK 中移除,而平台仍支持其 ABI,则表示该平台拥有对该 API 的旧版支持

如需保留对某个 API 的旧版支持,请在移除该 API 时设置 legacy=true。例如:

protocol LegacyExample {
    @available(added=10, deprecated=11, removed=12, legacy=true)
    LegacyMethod();
};

移除 Fuchsia 平台中的所有方法后,均应保留旧版支持。如果 Fuchsia 平台在移除该方法之前不再支持所有 API 级别,则可以安全地移除 legacy=true 和该方法的实现。

设计可优雅进化的 API

此评分准则侧重于提升与一系列平台版本的兼容性。这些属性有助于尽可能轻松地维护兼容性,它们是 FIDL API 评分准则的一部分。

遵循 FIDL 样式指南

FIDL 样式准则用于使 FIDL 便于阅读并体现最佳实践。这些通常是最佳实践,无论 sdk_category 为何,都应遵循。

使用 FIDL 版本控制注解

FIDL 版本控制注释可让库、协议和其他元素与特定 API 级别相关联。所有兼容性原因均基于 API 版本。通过这种方式来表达 API 演变过程中的某一点。

  • 只修改处于 in-developmentHEAD API 级别的 API。

  • 将某个 API 级别声明为“稳定”后,便不应再对其进行更改。(请参阅 version_history.json)。这样,您就可以更改 API,同时现有 API 级别保持不变。

指定矢量和字符串的边界

更多信息:FIDL API 评分准则 - 指定矢量和字符串的范围

使用枚举与布尔值

由于布尔值是二进制值,因此在使 API 兼容兼容性时,最好使用可具有多种状态的枚举。这样一来,如果需要其他状态,枚举可以扩展,而布尔值必须替换为其他类型。更多信息:FIDL API 评分准则 - 如果可能有更多状态,请避免使用布尔值

使用灵活的枚举和位

灵活枚举具有默认的未知成员,因此可以轻松地演变枚举。

请仅在您非常确信永远不会扩展 strict enumbits 类型时使用这些类型。strict enumbits 类型无法扩展,如果要将其迁移到 flexible,则需要迁移具有给定类型的每个字段。

更多信息:FIDL 语言 - 严格与灵活

首选表而非结构体

结构体和表都表示具有多个命名字段的对象。不同之处在于结构体具有传输格式的固定布局,这意味着结构无法在不破坏二进制文件兼容性的情况下被修改。相比之下,表采用有线格式的灵活布局,这意味着字段可以随着时间的推移添加到表中,而不会破坏二进制文件兼容性。

更多信息:FIDL API 评分准则 - 我应该使用结构体还是表?

将开放式协议与灵活的方法和事件结合使用

一般来说,所有协议都应为 open,且这些协议中的所有方法和事件都应为 flexible

将协议标记为开放后,当在不同版本下构建了不同的组件时,您可以更轻松地移除方法或事件,这样一来,每个组件对存在的方法和事件都有不同的视图。由于人们通常希望能够灵活地演变协议,因此建议选择对协议开放,除非有理由选择更封闭的协议。

一种可能的例外情况是表示事务的拆解协议,其中唯一的双向方法是提交操作,该操作必须严格,而事务的其他操作可能会发生变化。如果某个协议非常小,不太可能更改,并且预计由客户端实现,您可以将其设为 closed 以及所有方法 strict。这样可让客户端确定如何处理“未知交互”。不过,代价是永远无法在此类协议中添加或移除方法或事件。如果您确定确实要添加方法或事件,则需要定义新的拆解协议来替换它。

更多信息:

使用错误语法

错误语法用于指定方法将返回一个值,或者指定错误输出并返回表示错误的整型或枚举。

使用自定义错误枚举,而不是 zx.Status

定义和控制网域时,请使用用途构建的枚举错误类型。例如,在构建协议时定义枚举,传达错误的语义是唯一的设计约束条件。

如果您遵循明确定义的规范(如 HTTP 错误代码),并且枚举是一种符合人体工程学的方法,用于表示规范规定的原始值,请使用网域特定的枚举错误类型。

更多信息:FIDL API 评分准则 - 针对错误优先使用针对特定领域的枚举

不要使用其他库中的声明

如果公共 API 在语义上等效,最好重复使用类型和 Compose 协议,但很容易出错。