FIDL 样式指南

本部分包含 Fuchsia 接口定义语言文件的样式相关信息。

另请参阅 FIDL API 评分准则

姓名

“猫咪”的命名非常困难,
这不仅仅是你的一款节日游戏;
--- T.S. Eliot

FIDL 中定义的名称用于以每种目标语言生成标识符。 有些语言为各种形式的名称附加语义或惯例的含义。例如,在 Go 中,标识符中的首字母是否大写用于控制标识符的可见性。因此,许多语言后端会转换库中的名称,使其更适合目标语言。本部分中的命名规则旨在平衡 FIDL 源代码中的可读性、每种目标语言中的易用性以及目标语言之间的一致性。

避免使用常用的预留字,例如 goto。语言后端会将保留字转换为非保留标识符,但这些转换会降低这些语言的易用性。避免使用常用的预留字可以降低这些转换的应用频率。

虽然某些 FIDL 关键字也是目标语言中的常用保留字(例如 C 和 C++ 中的 struct),因此应该避免使用,但其他 FIDL 关键字(尤其是 requesthandle)一般都是描述性关键字,可以在适当情况下使用。

名称不得包含前导或尾随下划线。前导或尾随下划线在某些语言中具有语义含义(例如,Dart 中的前导下划线控件可见性),而在其他语言中具有常规含义(例如,尾随下划线通常用于 C++ 中的成员变量)。此外,FIDL 编译器使用前导和尾随下划线来去除标识符以避免冲突。

使用 size 一词来命名字节数。使用 count 这一术语来命名一些其他数量(例如,结构矢量中的项目数量)。

病例定义

有时,有多种方法可以决定如何分隔标识符中的单词。我们的样式如下:

  • 以美式英语原文开头(例如“非 Null HTTP 客户端”)
  • 请移除所有标点符号。(“非 Null HTTP 客户端”)
  • Make everything lowercase ("non null http client")
  • 根据指定标识符的样式,执行以下某项操作:
    • 对于小写蛇形 (non_null_http_client),将空格替换为下划线(“_”)。
    • 对于蛇形大写 (NON_NULL_HTTP_CLIENT),请将空格大写并替换为下划线。
    • 将每个单词的首字母大写,并将所有单词联接在一起,实现大驼峰式大小写 (NonNullHttpClient)。

用法

下表将案例的使用与 元素一一对应:

元素 大小写 示例
bits 大驼峰式大小写 InfoFeatures
位字段成员 蛇形大写 WLAN_SNOOP
const 蛇形大写 MAX_NAMES
alias 大驼峰式大小写 DeviceId
protocol 大驼峰式大小写 AudioRenderer
协议方法参数 蛇形小写 enable_powersave
协议方法 大驼峰式大小写 GetBatteryStatus
struct 大驼峰式大小写 KeyboardEvent
结构体成员 蛇形小写 child_pid
table 大驼峰式大小写 ComponentDecl
表成员 蛇形小写 num_rx
union 大驼峰式大小写 BufferFormat
工会成员 蛇形小写 vax_primary
enum 大驼峰式大小写 PixelFormat
枚举成员 蛇形大写 RGB_888

库名称是用句点分隔的标识符列表。除最后一个部分之外,库名称中的一部分也称为命名空间。名称的每个组成部分均采用小写形式,并且必须与以下正则表达式匹配:[a-z][a-z0-9]*

我们之所以使用这些限制,是因为不同的目标语言在限定命名空间、库或软件包的方式方面有不同的限制。为了让 FIDL 能够很好地与当前的目标语言和未来的目标语言搭配使用,我们选择了一个保守的最低标准。

标识符名称:首选功能角色有意义

首选功能名称(例如,fuchsia.media)而非产品名或代号(例如fuchsia.amberfuchsia.scenic)。如果产品在 Fuchsia 之外存在一些外部存在,并且协议特定于该产品,则产品名称是合适的。例如,对于 Cobalt 接口协议,使用 fuchsia.cobaltfuchsia.metrics 更好,因为其他指标实现(例如Firebase)不太可能实现相同的协议。

标识符名称应与参与者所扮演的特定角色相关;避免将访问权限控制编码到名称中。基于角色的名称具有描述性,不会像基于访问权限控制的名称那样过时就过时,访问权限控制规定了一种外部定义关系,该关系可能会随着平台的发展而发生变化。例如,对于涉及 FocusChain 对象的 API,应该使用 fuchsia.ui.focus 而不是 fuchsia.ui.privileged;如果我们决定扩大 FocusChain 对象的访问范围,那么 fuchsia.ui.focus 就不是一个有问题的名称。应避免使用以下示例字词:

  • constrained
  • limited
  • oem
  • private
  • privileged
  • protected
  • special
  • vendor

标识符名称应具有意义;避免使用无意义的名称。如果 fuchsia.foo.barfuchsia.foo.baz 有许多您希望分解到单独的库中的概念,请考虑在 fuchsia.foo(而不是 fuchsia.foo.common)中定义这些概念。应避免使用以下示例字词:

  • common
  • service
  • util
  • base
  • f<letter>l
  • zx<word>

顶级

避免重复使用库名称中的名称。例如,在 fuchsia.process 库中,用于启动进程的协议应命名为 Launcher,而不是 ProcessLauncher,因为库名称中已包含名称 process。在所有目标语言中,顶级名称在某种程度上是由库名称限定的。

原初别名

原初别名不得重复所属库中的名称。在所有目标语言中,原始别名都会替换为基础原始类型,因此不会导致名称冲突。

alias vaddr = uint64;

常量

常量名称不得与所属库中的名称重复。在所有目标语言中,常量名称的范围都由其所属库限定。

描述最小和最大边界的常量应分别使用前缀 MIN_MAX_

const MAX_NAMES uint64 = 32;

协议

协议由 protocol 关键字指定。

协议必须是名词短语。通常,协议是使用可建议操作的名词来命名的。例如,AudioRenderer 是一个名词,表明协议与呈现音频有关。同样,Launcher 是一个名词,表明该协议与启动某项内容有关。协议也可以是被动名词,尤其是当它们与实现持有的某个状态相关时。例如,Directory 是一个名词,表示协议用于与实现包含的目录进行交互。

可以使用面向对象的设计模式来命名协议。例如,fuchsia.fonts.Provider 使用 provider 后缀,这表示协议提供字体(而不是表示字体本身)。同样,fuchsia.tracing.Controller 使用 controller 后缀,这表示协议控制跟踪系统(而不是表示跟踪本身)。

名称 Manager 可用作范围广泛的协议的最后手段。例如,fuchsia.power.Manager。但请注意,“管理器”协议往往会吸引大量松散相关的功能,这些功能可能更好地融入到多个协议中。

协议不得包含名称 service.。所有协议均定义服务。这个词没有任何意义。例如,fuchsia.tts.TtsService 在两个方面违反了此评分准则。首先,Tts 前缀与库名是多余的。其次,禁止使用 Service 后缀。

明确的“open/ajar/closed”修饰符

对于协议,应始终指定 openajarclosed,而不是依赖于默认值。也就是说,始终优先选择 open protocol Foo { ...,而不是仅 protocol Foo { ...

方法

方法必须是动词短语。

例如,GetBatteryStatusCreateSession 是指示方法执行的操作的动词短语。

在事件发生时调用的 listenerobserver 协议中的方法应带有 On 前缀,并描述过去时发生的事件。例如,ViewContainerListener 协议具有一个名为 OnChildAttached 的方法。

事件

同样,事件(即从服务器向客户端发送的垃圾消息)应添加 On 前缀,并描述过去时发生的事件。

例如,AudioCapturer 协议有一个名为 OnPacketCaptured 的事件。

单一方法协议

单一方法协议的方法应该是定义它们的协议名词短语的动词短语,例如 Loader.LoadGetter.GetUploader.Upload。如果是限定名词短语(如 JobCreatorProcessStopper),则应使用不限定动词短语,即 JobCreator.CreateProcessStopper.Stop

单一方法但打算演变为多方法机制的协议不一定需要遵循此命名惯例,例如,如果协议的某个已知扩展项不适合采用建议的命名方式,那么最好尽早选择其他名称。如有疑问,应优先遵循默认建议。

由于替换协议比改进协议更困难,因此如果某个 API 从未打算改进,但最终发现需要改用多方法协议,则最好通过添加方法并可能重命名现有方法来改进现有协议。

显式“strict/flexible”修饰符

对于方法和事件,应始终指定 strictflexible,而不是依赖于默认值。也就是说,始终优先选择 flexible Foo();,而不是仅 Foo()

结构体、联合和表

结构体、联合和表必须是名词短语。例如,Point 是一个用于定义空间中位置的结构体,KeyboardEvent 是一个用于定义键盘相关事件的结构体。

结构体、联合成员和表成员

如果切实可行,请优先使用包含一个单词的结构体、联合体和表成员名称(单单词名称在目标语言中的呈现方式更一致)。但是,如果单个字词模糊或令人困惑,也不要害怕使用多个字词。

成员名称不得重复来自封闭类型(或库)的名称,除非成员名称不明确,而不含封闭类型中的名称。例如,包含事件传送时间的 KeyboardEvent 类型成员应命名为 time,而不是 event_time,因为名称 event 已出现在封闭类型的名称中。在所有目标语言中,成员名称的范围取决于其所属类型。

但是,将智能设备与其所在房间关联的 DeviceToRoom 类型可能需要包含 device_idroom_name 成员,因为 idname 不明确。这两个元素都可以指设备或房间。

枚举

枚举必须是名词短语。

例如,PixelFormat 是一个枚举,用于定义如何将颜色编码为图片中的位。

枚举成员

枚举成员名称不得与所属类型(或库)中的名称重复。 例如,PixelFormat 枚举的成员应命名为 ARGB,而不是 PIXEL_FORMAT_ARGB,因为名称 PIXEL_FORMAT 已出现在封装类型的名称中。在所有目标语言中,枚举成员名称的范围取决于其所属类型。

比特字段

位字段必须是名词短语。

例如,InfoFeatures 是一个位字段,它表示以太网接口上存在哪些功能。

Bitfield 成员

位字段成员不得重复来自所属类型(或库)的名称。 例如,InfoFeatures 位字段的成员应命名为 WLAN,而不是 INFO_FEATURES_WLAN,因为名称 INFO_FEATURES 已出现在封装类型的名称中。在所有目标语言中,位字段成员名称的范围取决于其所属类型。

类型

显式“strict/flexible”修饰符

对于接受 strict/flexible 修饰符的类型(bitsenumunion),应始终指定此类修饰符,而不是依赖于默认值。也就是说,始终优先选择 flexible bits ...,而不是仅 bits ...

组织机构

语法

  • 缩进 4 个空格。
  • 不使用标签页。
  • 避免尾随空格。
  • 使用一个空行(两个连续的换行符)将 bitsenumprotocolstructtableunion 构造的声明与其他声明分开。
  • 在文件结尾处只能包含一个换行符。

注释

注释使用 ///(三个正斜线)。库中的注释也会显示在生成的代码中,以便在针对库进行编码时简化开发。我们说评论“延伸”到目标语言。

请在要描述的内容上方添加评论。使用合理完整的句子,并合理使用大写和句点,但下面列出的情况除外。将注释宽度限制为 80 个字符,除非无法避免较长的注释(例如,较长网址)。

注释应使用 Markdown 编写。我们依赖 CommonMark 规范进行 Markdown。某些工具可能会使用其他 Markdown 标准呈现输出;如果您的工具不使用 CommonMark,我们建议开发者编写与 CommonMark 及其工具都兼容的 Markdown。对 FIDL 元素的引用应始终采用代码字体。

可记录实体是指附加了注释的任何 FIDL 元素。对注释中任何记录的实体的第一个引用都应以 [`<library>/<top level declaration>.<member>`] 格式指定其完全限定名称(例如,[`fuchsia.io/Node.clone`])。此表单可能会生成超链接(如果工具支持)。对有文档记录的实体的后续引用可以使用缩写版本,只要缩写版本明确无歧义(例如,clone)。不带方括号的格式不会生成超链接。

请求参数、响应参数和错误类型应记录为以下形式的列表:

+ request `param1` <description>
+ request `param2` <description>
- response `param1` <description>
- response `param2` <description>
* error <description>

请求、响应和错误必须按该顺序显示。一组给定的参数还必须遵循它们在参数列表中声明的顺序。如果参数名称仅出现在请求或响应参数列表中,则可以省略术语“请求”和“响应”。

描述变量、字段或类型的文档注释的第一部分应该是名词短语,用于简要说明记录实体的预期用途,包括无法从名称和类型推断出的信息。说明应以英文句点结尾。说明中不应重述所记录实体的名称或其特定类型的 FIDL 语言元素(例如,structprotocol)。

/// A representation of violins displayed on the screen.
type Widget = struct {
    /// A monotonically increasing id, uniquely identifying the widget.
    id uint64;
    /// Location of the top left corner of the widget.
    location Point;
};

下面列举了一些示例来说明应避免的做法:

/// BAD: Widget is a representation of violins displayed on the screen.
/// BAD: struct Widget is a representation of violins displayed on the screen.

附加到协议方法的文档注释的第一部分应该是对该方法行为的简要说明,从动词开始,包括无法从名称和类型推断出的信息。动词应该采用现在式,同意第三人称单数代词,并使用陈述语气(这实际上意味着您应该假装“it”一词在动词前面,并且您是在陈述事实)。短语应以句点结尾。

完整示例:

/// An abstract representation of a [`fuchsia.io/Node`] whose layout is flat.
protocol File {
    compose Node;

    /// Acquires a [`fuchsia.mem/Buffer`] representing this file, if
    /// there is one, with the requested access rights.
    ///
    /// ## Rights
    ///
    /// This method requires the following rights:
    ///
    /// * [`fuchsia.io/OPEN_RIGHT_WRITABLE`] if `flags` includes
    ///   [`fuchsia.io/VMO_FLAG_WRITE`].
    /// * [`fuchsia.io/OPEN_RIGHT_READABLE`] if `flags` includes
    ///   [`fuchsia.io/VMO_FLAG_READ`] or [`fuchsia.io/VMO_FLAG_EXEC`].
    ///
    /// + request `flags` a bit field composing any of
    ///     `VMO_FLAG_READ`, `VMO_FLAG_WRITE`, or `VMO_FLAG_EXEC`.
    /// - response `buffer` the requested `fuchsia.mem/Buffer`, or
    ///     null if there was an error, or the buffer does not exist.
    /// * error a zx_status value indicating success or failure.
    /// * see [`fuchsia.mem/Buffer`]
    /// [`fuchsia.mem/Buffer`]:
    ///    https://fuchsia.googlesource.com/fuchsia/+/HEAD/sdk/fidl/fuchsia.io/
    GetBuffer(struct {
        flags uint32;
    }) -> (resource struct {
        buffer box<fuchsia.mem.Buffer>;
    }) error zx.Status;
};

由某些外部可信来源定义的类型或值应使用对外部内容的引用进行注释。例如,您可以参考介绍配置结构的 Wi-Fi 规范。同样,如果结构必须与 C 头文件中定义的 ABI 匹配,请引用 C 头文件。

如需详细了解注释应包含的内容,请参阅 API 文档评分准则

引用 FIDL 协议或协议方法

在注释中引用 FIDL 协议或其方法时应遵循以下模式:

/// See fuchsia.library/ProtocolName.Method for more information.

当在注释所在的库中引用协议时,库名称可以省略:ProtocolName.Method

同样,在引用与注释的协议相同的方法时,库名称和协议名称可以省略:Method

库概览

您可以通过对 library 语句的文档注释提供库概览。“library”语句用于启动 FIDL 文件。例如:

/// Library containing example FIDL used throughout the Fuchsia documentation.
library fuchsia.examples.docs;

库概览应提供定义库的一般文档。它们还可能详细介绍了各种消息、定义的协议以及如何结合使用这些消息和协议。

虽然一个库可以细分为多个 FIDL 文件,但只能有一个库概览。请考虑以下关于库概览的建议:

  • 如果概览很短,并且库由单个文件组成,您可以将概览放入库文件顶部的 library 语句中。
  • 如果库由多个文件组成,请创建一个独立的文件 overview.fidl 来记录该库。“overview.fidl”文件不应包含任何声明、类型别名或协议定义。

非流式评论

如果您的注释是面向库作者的,请使用较为简单的注释 //(两个正斜杠),这些注释不会延伸到目标语言。

在决定常规 /// 注释与非直通注释时,请注意以下几点。

定期评论:

  • 参数、实参和函数的说明
  • 使用说明

非流式注释:

  • 内部“待办事项”注释
  • 版权通知
  • 实现细节

这两种评论样式可以结合使用:

/// A widget displaying violins on the screen.
// TODO -- widgets should use UUIDs instead of sequential ids
type ViolinWidget = struct {
    /// A monotonically increasing id, uniquely identifying the widget.
    id uint64;
    /// Location of the top left corner of the widget.
    location Point;
};

Google 文件极客

库由一个或多个文件组成。这些文件会按以下惯例存储在目录层次结构中:

fidl/<library>/[<dir>/]*<file>.fidl

<library> 目录使用以点分隔的 FIDL 库名称命名。<dir> 子目录是可选的,通常不用于文件少于十几个的库。此目录结构与 Fuchsia SDK 包含 FIDL 文件的方式一致。

将库划分为文件不会对库的使用者产生技术影响。在整个库中,声明(包括协议)可以相互引用,无论它们出现在哪个文件中,都是如此。将库划分为多个文件,以最大限度地提高可读性。

  • 对于库中的文件,最好使用 DAG 依赖关系图。
  • 最好使相互引用的定义在文本上彼此靠近,最好在同一个文件中。
  • 对于复杂的库,最好在叶文件中定义纯数据类型或常量,并在主文件中定义将这些类型一起引用的协议。