本部分包含 Fuchsia 接口定义语言文件的样式相关信息。
另请参阅 FIDL API 准则。
姓名
给猫起名字可不是一件简单的事,
这可不是你假期里玩的游戏;
--- T.S. Eliot
FIDL 中定义的名称用于在每种目标语言中生成标识符。在某些语言中,各种形式的名称都具有语义或传统含义。例如,在 Go 中,标识符中的首字母是否大写控制着标识符的可见性。因此,许多语言后端都会转换库中的名称,使其更适合目标语言。本部分中的命名规则旨在平衡 FIDL 源代码的可读性、每种目标语言的易用性以及不同目标语言之间的一致性。
避免使用常用的预留字词,例如 goto。语言后端会将预留字词转换为非预留标识符,但这些转换会降低这些语言的易用性。避免使用常见的预留字词可减少应用这些转换的频率。
虽然某些 FIDL 关键字也是目标语言中的常见保留字(例如 C 和 C++ 中的 struct),因此应避免使用,但其他 FIDL 关键字(尤其是 request 和 handle)通常具有描述性,可以酌情使用。
名称不得包含前导或尾随下划线。前导或尾随下划线在某些语言中具有语义含义(例如,前导下划线控制 Dart 中的可见性),在其他语言中具有常规含义(例如,尾随下划线通常用于 C++ 中的成员变量)。此外,FIDL 编译器使用前导和尾随下划线来混淆标识符,以避免冲突。
使用术语 size 来命名字节数。使用术语 count 来命名其他一些数量(例如,结构体向量中的项数)。
案例定义
有时,有多种方法可以决定如何分隔标识符中的字词。我们的风格如下:
- 从美式英语的原始短语开始(例如“非空 HTTP 客户端”)
- 移除所有标点符号。("Non Null HTTP Client")
- 将所有内容设为小写(“non null HTTP 客户端”)
- 根据给定标识符适用的样式,执行以下操作之一:
- 将空格替换为下划线 ('_'),以采用小写蛇形命名法 (
non_null_http_client)。 - 对于大写蛇形命名法 (
NON_NULL_HTTP_CLIENT),请将字母大写,并将空格替换为下划线。 - 将每个单词的首字母大写,并将所有单词连接在一起,以采用大驼峰命名法 (
NonNullHttpClient)。
- 将空格替换为下划线 ('_'),以采用小写蛇形命名法 (
用法
下表将大小写用法映射到元素:
| 元素 | 大小写 | 示例 |
|---|---|---|
bits |
大驼峰命名法 | InfoFeatures |
| bitfield 成员 | 大写蛇形命名法 | 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.amber 或 fuchsia.scenic)。如果产品在 Fuchsia 之外具有某种外部存在,并且协议特定于该产品,则商品名称是合适的。例如,对于 Cobalt 接口协议,fuchsia.cobalt 是比 fuchsia.metrics 更好的名称,因为其他指标实现(例如 Firebase)不太可能实现相同的协议。
标识符名称应与参与者扮演的具体角色相关;避免将访问权限控制编码到名称中。基于角色的名称具有描述性,不会像基于访问权限控制的名称那样很快过时,后者规定了一种外部定义的关系,这种关系会随着平台的发展而发生变化。例如,对于涉及 FocusChain 对象的 API,合适的名称应为 fuchsia.ui.focus,而不是 fuchsia.ui.privileged;如果我们决定让 FocusChain 对象更易于访问,那么 fuchsia.ui.focus 就不是一个有问题的名称。应避免使用以下示例字词:
constrainedlimitedoemprivateprivilegedprotectedspecialvendor
标识符名称应有意义;避免使用无意义的名称。如果 fuchsia.foo.bar 和 fuchsia.foo.baz 共享您希望提取到单独库中的许多概念,请考虑在 fuchsia.foo(而非 fuchsia.foo.common)中定义这些概念。应避免使用以下示例字词:
commonserviceutilbasef<letter>lzx<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”修饰符
对于协议,应始终指定 open、ajar 或 closed,而不是依赖默认值。也就是说,始终首选 open protocol Foo {
...,而不是仅使用 protocol Foo { ...。
方法
方法必须是动词短语。
例如,GetBatteryStatus 和 CreateSession 是动词短语,用于指明方法执行的操作。
在事件发生时调用的 listener 或 observer 协议的方法应以 On 为前缀,并使用过去时态描述发生的事件。例如,ViewContainerListener 协议有一个名为 OnChildAttached 的方法。
事件
同样,事件(即从服务器发送到客户端的主动发送消息)应以 On 为前缀,并使用过去时态描述发生的事件。
例如,AudioCapturer 协议具有一个名为 OnPacketCaptured 的事件。
单方法协议
单方法协议的方法应是其所定义的协议名词短语的动词短语,例如 Loader.Load、Getter.Get、Uploader.Upload。对于限定名词短语(例如 JobCreator 或 ProcessStopper),应使用非限定动词短语,即 JobCreator.Create 或 ProcessStopper.Stop。
如果协议目前只有一种方法,但计划随着时间的推移演变为多种方法,则不一定需要遵循此命名惯例。也就是说,如果协议有已知扩展,而推荐的命名不合适,那么最好尽早选择其他名称。如有疑问,应优先遵循默认建议。
由于替换协议比演进协议更难,因此如果 API 从未打算演进,但最终发现需要迁移到多方法协议,则最好通过添加方法并可能重命名现有方法来演进现有协议。
显式“strict/flexible”修饰符
对于方法和事件,应始终指定 strict 或 flexible,而不是依赖于默认值。也就是说,始终优先使用 flexible Foo();,而不是仅使用 Foo()。
结构体、并集和表
结构体、并集和表必须是名词短语。
例如,Point 是一个定义空间位置的结构体,KeyboardEvent 是一个定义键盘相关事件的结构体。
结构体、联合和表成员
在可行的情况下,首选使用单个字词的结构体、并集和表成员名称(单个字词的名称在目标语言中呈现得更一致)。不过,如果单个字词会造成歧义或混淆,请不要害怕使用多个字词。
成员名称不得重复封闭类型(或库)中的名称,除非成员名称在没有封闭类型中的名称的情况下不明确。例如,包含事件传送时间的 KeyboardEvent 类型成员应命名为 time,而不是 event_time,因为名称 event 已出现在封闭类型的名称中。在所有目标语言中,成员名称都由其封闭类型限定范围。
不过,将智能设备与其所在房间相关联的类型 DeviceToRoom 可能需要具有成员 device_id 和 room_name,因为 id 和 name 具有歧义。其中任何一个都可以指代设备或房间。
枚举
枚举必须是名词短语。
例如,PixelFormat 是一个枚举,用于定义如何将颜色编码为图片中的位。
枚举成员
枚举成员名称不得重复封闭类型(或库)中的名称。
例如,PixelFormat 枚举的成员应命名为 ARGB,而不是 PIXEL_FORMAT_ARGB,因为名称 PIXEL_FORMAT 已出现在封闭类型的名称中。在所有目标语言中,枚举成员名称的作用域都由其封闭类型限定。
位字段
位字段必须是名词短语。
例如,InfoFeatures 是一个位字段,用于指示以太网接口上存在哪些功能。
位域成员
位字段成员不得重复使用封闭类型(或库)中的名称。
例如,InfoFeatures 位字段的成员应命名为 WLAN,而不是 INFO_FEATURES_WLAN,因为名称 INFO_FEATURES 已出现在封闭类型的名称中。
在所有目标语言中,位字段成员名称的作用域由其封闭类型确定。
类型
显式“strict/flexible”修饰符
对于接受 strict/flexible 修饰符(bits、enum 和 union)的类型,应始终指定此类修饰符,而不是依赖默认值。也就是说,始终首选 flexible bits ...,而不是仅使用 bits ...。
组织
语法
- 使用 4 个空格缩进。
- 切勿使用制表符。
- 避免尾随空格。
- 使用一个空行(两个连续的换行符)将
bits、enum、protocol、struct、table和union构造的声明与其他声明分隔开。 - 文件结尾必须恰好有一个换行符。
评论
注释使用 ///(三个正斜杠)。库中的注释也会出现在生成的代码中,以便在针对库进行编码时简化开发。我们称之为评论“流转”到目标语言。
将注释放在所描述内容的上方。除下列情况外,请使用合理完整的句子,并使用正确的大小写和句点。将注释宽度限制为 80 个字符,除非无法避免更长的注释(例如,对于长网址)。
评论应采用 Markdown 格式。我们依靠 CommonMark 规范来处理 Markdown。某些工具可能会使用其他 Markdown 标准呈现输出;如果您的工具不使用 CommonMark,我们建议开发者编写与 CommonMark 及其工具都兼容的 Markdown。对 FIDL 元素的引用应始终采用代码字体。
文档化实体是指附加了注释的任何 FIDL 元素。在注释中首次引用任何记录的实体时,应使用其完全限定名称,格式为 [`<library>/<top level declaration>.<member>`](例如 [`fuchsia.io/Node.clone`])。如果工具支持,此格式可能会生成超链接。后续对该文档化实体的引用可以使用缩写版本,只要该缩写版本没有歧义(例如 clone)。不带方括号的形式不会生成超链接。
描述变量、字段或类型的文档注释的第一部分应为名词短语,简要说明所记录实体的预期用途,包括无法从名称和类型中推断出的信息。说明应以英文句号结尾。说明不应重复所记录实体的名称或其特定类型的 FIDL 语言元素(例如 struct 或 protocol)。
/// 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”一词位于动词之前,并且您是在陈述事实)。短语应以英文句号结尾。
请求和响应参数应在相应的结构体或表格中以内嵌方式记录。
如果协议返回错误值,则应在“错误”小节中进行记录:
## Error
Description of the error value.
完整示例:
/// 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`].
///
/// ## Error
///
/// Returns ZX_ERR_INVALID_ARGS if `flags` contains an invalid VMO flag.
/// Returns ZX_ERR_NOT_FOUND if the requested buffer does not exist.
///
/// * see [`fuchsia.mem/Buffer`]
/// [`fuchsia.mem/Buffer`]:
/// https://fuchsia.googlesource.com/fuchsia/+/HEAD/sdk/fidl/fuchsia.mem/buffer.fidl
GetBuffer(struct {
/// A bit field composing any of `VMO_FLAG_READ`, `VMO_FLAG_WRITE`, or
/// `VMO_FLAG_EXEC`.
flags uint32;
}) -> (resource struct {
/// The requested `fuchsia.mem/Buffer`.
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 语句的文档注释。FIDL 文件以“library”语句开头。例如:
/// 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;
};
文件
一个库由一个或多个文件组成。这些文件存储在具有以下约定的目录层次结构中:
fidl/<library>/[<dir>/]*<file>.fidl
<library> 目录使用 FIDL 库的点分隔名称命名。<dir> 子目录是可选的,通常不用于文件数量少于 12 个的库。此目录结构与 Fuchsia SDK 中包含 FIDL 文件的方式一致。
将库拆分为文件对库的使用者没有技术影响。声明(包括协议)可以在整个库中相互引用和引用自身,无论它们出现在哪个文件中。将库拆分为多个文件,以最大限度地提高可读性。
- 对于库中的文件,最好使用 DAG 依赖关系图。
- 最好将相互引用的定义在文本上保持靠近,最好在同一个文件中。
- 对于复杂的库,最好在叶文件中定义纯数据类型或常量,并在主干文件中定义引用这些类型的协议。