風格指南

提供關於如何最佳化結構化 Proto 定義的指引。

本文件提供 .proto 檔案的風格指南。遵循這些慣例,您將使您的 Protocol Buffer 訊息定義及其對應的類別保持一致且易於閱讀。

標準檔案格式

  • 將行長度保持在 80 個字元以內。
  • 使用 2 個空格的縮排。
  • 字串偏好使用雙引號。

檔案結構

檔案應命名為 lower_snake_case.proto

所有檔案應依以下方式排序

  1. 授權條款標頭 (如果適用)
  2. 檔案總覽
  3. 語法
  4. 套件
  5. 匯入 (已排序)
  6. 檔案選項
  7. 其他所有項目

識別項命名樣式

Protobuf 識別項使用以下命名樣式之一

  1. TitleCase
    • 包含大寫字母、小寫字母和數字
    • 首字元是大寫字母
    • 每個單字的首字母都大寫
  2. lower_snake_case
    • 包含小寫字母、底線和數字
    • 單字以單一下底線分隔
  3. UPPER_SNAKE_CASE
    • 包含大寫字母、底線和數字
    • 單字以單一下底線分隔
  4. camelCase
    • 包含大寫字母、小寫字母和數字
    • 首字元是小寫字母
    • 每個後續單字的首字母都大寫
    • 注意: 以下風格指南未在 .proto 檔案中的任何識別項使用 camelCase;此處僅闡明術語,因為某些語言產生的程式碼可能會將識別項轉換為此樣式。

在所有情況下,將縮寫視為單一單字:使用 GetDnsRequest 而不是 GetDNSRequestdns_request 而不是 d_n_s_request

識別項中的底線

請勿使用底線作為名稱的開頭或結尾字元。任何底線後方應始終跟隨字母 (而非數字或第二個底線)。

此規則的動機是每個 Protobuf 語言實作都可能將識別項轉換為本機語言樣式:.proto 檔案中的 song_id 名稱最終可能會具有欄位的存取器,這些存取器會依照語言而大寫為 SongIdsongIdsong_id

透過僅在字母之前使用底線,它可以避免名稱在一個樣式中可能不同,但在轉換為其他樣式之一後會衝突的情況。

例如,DNS2DNS_2 都會轉換為 TitleCase,即 Dns2。允許這些名稱中的任何一個都可能導致痛苦的情況,當訊息僅在某些語言中使用 (其中產生的程式碼保留原始的 UPPER_SNAKE_CASE 樣式),變得廣泛建立,然後僅在稍後在名稱轉換為 TitleCase 的語言中使用時,它們會發生衝突。

當應用此風格規則時,表示您應使用 XYZ2XYZ_V2 而不是 XYZ_2

套件

將點分隔的 lower_snake_case 名稱用作套件名稱。

多字套件名稱可以是 lower_snake_case 或點分隔 (點分隔套件名稱在大多數語言中會作為巢狀套件/命名空間發出)。

套件名稱應嘗試成為基於專案名稱的簡短但唯一的名稱。套件名稱不應為 Java 套件 (com.x.y);而是使用 x.y 作為套件,並根據需要使用 java_package 選項。

訊息名稱

訊息名稱使用 TitleCase。

message SongRequest {
}

欄位名稱

欄位名稱 (包括擴充功能) 使用 snake_case。

重複欄位使用複數名稱。

string song_name = 1;
repeated Song songs = 2;

Oneof 名稱

Oneof 名稱使用 lower_snake_case。

oneof song_id {
  string song_human_readable_id = 1;
  int64 song_machine_id = 2;
}

列舉

列舉類型名稱使用 TitleCase。

列舉值名稱使用 UPPER_SNAKE_CASE。

enum FooBar {
  FOO_BAR_UNSPECIFIED = 0;
  FOO_BAR_FIRST_VALUE = 1;
  FOO_BAR_SECOND_VALUE = 2;
}

第一個列出的值應為零值列舉,並具有 _UNSPECIFIED_UNKNOWN 的字尾。此值可用作未知/預設值,並且應與您預期明確設定的任何語意值不同。如需關於未指定列舉值的更多資訊,請參閱Proto 最佳實務頁面

列舉值字首

列舉值在語意上被認為不受其包含的列舉名稱的範圍限制,因此不允許兩個同級列舉中的相同名稱。例如,以下內容會被 protoc 拒絕,因為在兩個列舉中定義的 SET 值被認為位於相同的範圍內

enum CollectionType {
  COLLECTION_TYPE_UNSPECIFIED = 0;
  SET = 1;
  MAP = 2;
  ARRAY = 3;
}

enum TennisVictoryType {
  TENNIS_VICTORY_TYPE_UNSPECIFIED = 0;
  GAME = 1;
  SET = 2;
  MATCH = 3;
}

當列舉在檔案的頂層定義時 (未巢狀於訊息定義內),名稱衝突的風險很高;在這種情況下,同級項目包括在設定相同套件的其他檔案中定義的列舉,其中 protoc 可能無法在程式碼產生時偵測到衝突已發生。

為避免這些風險,強烈建議執行以下其中一項

  • 在每個值前面加上列舉名稱 (轉換為 UPPER_SNAKE_CASE)
  • 將列舉巢狀於包含訊息內

任一選項都足以減輕衝突風險,但相較於僅為了減輕問題而建立訊息,更偏好具有字首值的頂層列舉。由於某些語言不支援在「struct」類型內定義列舉,因此偏好字首值可確保跨繫結語言的一致方法。

服務

服務名稱和方法名稱使用 TitleCase。

service FooService {
  rpc GetSomething(GetSomethingRequest) returns (GetSomethingResponse);
  rpc ListSomething(ListSomethingRequest) returns (ListSomethingResponse);
}

如需更多與服務相關的指引,請參閱 API 最佳實務主題中的為每個方法建立唯一的 Proto請勿在頂層請求或回應 Proto 中包含基本類型,以及 Proto 最佳實務中的在個別檔案中定義訊息

應避免的事項

必要欄位

必要欄位是一種強制執行在剖析線路位元組時必須設定給定欄位的方法,否則拒絕剖析訊息。必要不變量通常不會在記憶體中建構的訊息上強制執行。必要欄位已在 proto3 中移除。

雖然在結構描述層級強制執行必要欄位在直覺上是理想的,但 protobuf 的主要設計目標之一是支援長期結構描述演進。無論給定欄位今天看起來有多麼明顯是必要的,未來都可能出現不應再設定該欄位的情況 (例如,int64 user_id 可能需要在未來遷移到 UserId user_id)。

尤其是在中介軟體伺服器的情況下,這些伺服器可能會轉發它們實際上不需要處理的訊息,required 的語意已被證明對這些長期演進目標過於有害,因此現在非常不鼓勵使用。

請參閱強烈反對使用必要欄位

群組

群組是巢狀訊息的替代語法和線路格式。群組在 proto2 中被視為已棄用,並已從 proto3 中移除。您應使用巢狀訊息定義和該類型的欄位,而不是使用群組語法。

請參閱群組