FIDL 樣式指南

本節包含 Fuchsia 介面定義語言檔案的樣式相關資訊。

另請參閱 FIDL API 評量表。

名稱

為貓咪命名可不是件簡單的事,
這不是你放假時玩的遊戲;
--- T.S. Eliot

FIDL 中定義的名稱會用於在各譯文語言中產生 ID。某些語言會為各種形式的名稱賦予語意或傳統意義。舉例來說,在 Go 中,ID 的首字母是否大寫會控制 ID 的可見度。因此,許多語言後端會轉換程式庫中的名稱,使其更適合目標語言。本節的命名規則旨在兼顧 FIDL 來源的可讀性、各譯文語言的可用性,以及譯文語言之間的一致性。

避免使用常見的保留字詞,例如 goto。語言後端會將保留字轉換為非保留 ID,但這些轉換會降低這些語言的可用性。避免使用常見的保留字,可減少套用這些轉換的頻率。

雖然部分 FIDL 關鍵字也是譯文語言中的常見保留字 (例如 C 和 C++ 中的 struct),因此應避免使用,但其他 FIDL 關鍵字 (尤其是 request 和 handle) 通常具有描述性,可視情況使用。

名稱開頭或結尾不得使用底線。在某些語言中,開頭或結尾底線具有語意意義 (例如,開頭底線控制 Dart 中的可見性),在其他語言中則具有傳統意義 (例如,結尾底線通常用於 C++ 中的成員變數)。此外,FIDL 編譯器會使用開頭和結尾底線來混淆 ID,以避免衝突。

請使用 size 一詞來命名位元組數。使用 count 一詞來命名其他數量的數字 (例如結構體向量中的項目數量)。

案件定義

有時,決定如何分隔 ID 中的字詞不只有一種方法。我們的風格如下:

  • 先從美式英文的原始詞組開始 (例如「Non-Null HTTP 用戶端」)
  • 移除所有標點符號。("Non Null HTTP Client")
  • 將所有內容轉換為小寫 (「non null HTTP 用戶端」)
  • 請根據指定 ID 的適當樣式,執行下列其中一項操作:
    • 將空格替換為底線 ('_'),以使用小寫蛇形命名法 (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
struct 成員 小寫蛇形命名法 child_pid
table 大駝峰式大小寫 ComponentDecl
資料表成員 小寫蛇形命名法 num_rx
union 大駝峰式大小寫 BufferFormat
組合成員 小寫蛇形命名法 vax_primary
enum 大駝峰式大小寫 PixelFormat
列舉成員 大寫蛇形命名法 RGB_888

程式庫

程式庫名稱是以半形句號分隔的 ID 清單。程式庫名稱中除了最後一個部分以外,其他部分也稱為命名空間。名稱的每個元件都必須是小寫,且符合下列規則運算式:[a-z][a-z0-9]*。

我們之所以採用這些限制規則,是因為不同的目標語言對命名空間、程式庫或套件的限定方式有不同的限制。我們選擇了保守的最小公分母,讓 FIDL 能與目前的目標語言組合和未來可能的目標語言搭配運作。

ID 名稱:偏好有意義的功能角色

建議使用功能名稱 (例如 fuchsia.media),而非產品或產品代號名稱 (例如 fuchsia.amber 或 fuchsia.scenic)。如果產品在 Fuchsia 以外有外部存在,且通訊協定專屬於該產品,則適合使用產品名稱。舉例來說,fuchsia.cobalt 是比 fuchsia.metrics 更適合 Cobalt 介面通訊協定的名稱,因為其他指標實作 (例如 Firebase) 不太可能實作相同的通訊協定。

ID 名稱應與參與者扮演的特定角色相關,避免將存取控管編碼至名稱中。以角色為基礎的名稱具有描述性,且不會像以存取控管為基礎的名稱那樣快速過時,後者規定了外部定義的關係,會隨著平台發展而改變。舉例來說,如果 API 涉及 FocusChain 物件,適當的名稱應為 fuchsia.ui.focus,而非 fuchsia.ui.privileged;如果我們決定讓 FocusChain 物件更廣泛存取,則 fuchsia.ui.focus 不是有問題的名稱。請避免使用下列範例字詞:

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

識別碼名稱應有意義,避免使用無意義的名稱。如果 fuchsia.foo.bar 和 fuchsia.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」修飾符

通訊協定應一律指定 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 是定義鍵盤相關事件的結構。

Struct、union 和 table 成員

在可行情況下,建議使用單一字詞做為 struct、union 和 table 成員名稱 (單一字詞名稱在譯文語言中的呈現方式較一致)。不過,如果單一字詞會造成混淆或令人困惑,請不要害怕使用多個字詞。

成員名稱不得與封閉類型 (或程式庫) 的名稱重複,除非成員名稱在沒有封閉類型名稱的情況下會造成混淆。舉例來說,如果 KeyboardEvent 類型的成員包含事件傳送時間,則應命名為 time,而非 event_time,因為 event 這個名稱已出現在封閉類型的名稱中。在所有譯文語言中,成員名稱的範圍都由其封閉類型決定。

不過,如果類型 DeviceToRoom 將智慧型裝置與所在房間建立關聯,則可能需要成員 device_id 和 room_name,因為 id 和 name 模稜兩可。兩者都可能指裝置或會議室。

列舉

列舉必須是名詞片語。

舉例來說,PixelFormat 是列舉,定義了顏色在圖片中編碼為位元的方式。

列舉成員

列舉成員名稱不得與所屬類型 (或程式庫) 的名稱重複。舉例來說,PixelFormat 列舉的成員應命名為 ARGB,而非 PIXEL_FORMAT_ARGB,因為 PIXEL_FORMAT 名稱已出現在封閉類型的名稱中。在所有目標語言中,列舉成員名稱的範圍取決於其封閉類型。

Bitfields

位元欄位必須是名詞片語。

例如,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」子節中:

## 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 依附元件圖表,呈現程式庫中的檔案。
  • 建議將相互參照的定義放在相近的位置,最好是在同一個檔案中。
  • 如果是複雜的程式庫,建議在葉檔案中定義純資料類型或常數,並在主幹檔案中定義參照這些類型的通訊協定。