风格指南

提供关于如何最好地组织您的 proto 定义的指导。

本文档提供了针对 .proto 文件的风格指南。遵循这些约定将使您的 protocol buffer 消息定义及其相应的类保持一致且易于阅读。

标准文件格式

  • 保持行长不超过 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 而不是 GetDNSRequest,使用 dns_request 而不是 d_n_s_request

标识符中的下划线

不要将下划线用作名称的首字符或尾字符。任何下划线之后应始终是一个字母(而不是数字或第二个下划线)。

这条规则的动机是,每个 protobuf 语言实现可能会将标识符转换为本地语言风格:.proto 文件中名为 song_id 的字段,最终其访问器可能会根据语言不同而大写为 SongIdsongId 或保持 song_id

仅在字母前使用下划线,可以避免在一种风格下名称是不同的,但在转换为其他风格后会发生冲突的情况。

例如,DNS2DNS_2 在转换为 TitleCase 后都将变成 Dns2。允许这些名称中的任何一个,可能导致令人痛苦的情况,当消息仅在某些生成代码保留原始 UPPER_SNAKE_CASE 风格的语言中使用并变得广泛应用后,才在后来用于名称转换为 TitleCase 并发生冲突的语言中。

应用此风格规则时,意味着您应该使用 XYZ2XYZ_V2 而不是 XYZ_2XYZ_2V

包名使用点分隔的 lower_snake_case 风格。

多词包名可以是 lower_snake_case 风格或点分隔 (dot.delimited) 风格(点分隔的包名在大多数语言中被生成为嵌套的包/命名空间)。

包名应尽量是基于项目名称的简短且唯一的名称。包名不应是 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

Groups 是嵌套消息的替代语法和有线格式。Groups 在 proto2 中被视为已弃用,并已从 proto3 中移除。您应使用嵌套消息定义和该类型的字段,而不是使用 group 语法。

请参阅groups