风格指南

提供有关如何最佳地构建 proto 定义的指导。

本文档提供了 .proto 文件的风格指南。通过遵循这些约定,您将使您的协议缓冲区消息定义及其相应的类保持一致且易于阅读。

标准文件格式

  • 保持行长为 80 个字符。
  • 使用 2 个空格的缩进。
  • 首选使用双引号表示字符串。

文件结构

文件应命名为 lower_snake_case.proto

所有文件应按以下顺序排列

  1. 许可证标头(如果适用)
  2. 文件概述
  3. 语法
  4. 导入(已排序)
  5. 文件选项
  6. 其他所有内容

标识符命名风格

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 的语义已被证明对那些长期演变目标过于有害,因此现在强烈不建议使用。

请参阅 强烈反对使用 Required

组是嵌套消息的替代语法和线路格式。组在 proto2 中被认为是已弃用的,并在 proto3 中已删除。您应该使用嵌套消息定义和该类型的字段,而不是使用组语法。

请参阅