样式指南
本文档为 .proto
文件提供了一份风格指南。遵循这些约定,您将使您的 protocol buffer 消息定义及其对应的类保持一致且易于阅读。
以下风格指南通过 enforce_naming_style 进行强制执行。
标准文件格式
- 保持行长为 80 个字符。
- 使用 2 个空格的缩进。
- 字符串优先使用双引号。
文件结构
文件应命名为 lower_snake_case.proto
。
所有文件都应按以下方式排序
- 许可证头部(如果适用)
- 文件概述
- 语法
- 包
- 导入(已排序)
- 文件选项
- 其他所有内容
标识符命名风格
Protobuf 标识符使用以下命名风格之一
- TitleCase(大驼峰命名法)
- 包含大写字母、小写字母和数字
- 首字母为大写字母
- 每个单词的首字母大写
- lower_snake_case(小写蛇形命名法)
- 包含小写字母、下划线和数字
- 单词由单个下划线分隔
- UPPER_SNAKE_CASE(大写蛇形命名法)
- 包含大写字母、下划线和数字
- 单词由单个下划线分隔
- camelCase(小驼峰命名法)
- 包含大写字母、小写字母和数字
- 首字母为小写字母
- 后续每个单词的首字母大写
- 注意: 以下风格指南不建议在 .proto 文件中对任何标识符使用 camelCase;此处仅为澄清术语,因为某些语言生成的代码可能会将标识符转换为此风格。
在所有情况下,都应将缩写词视为单个单词:使用 GetDnsRequest
而不是 GetDNSRequest
,使用 dns_request
而不是 d_n_s_request
。
标识符中的下划线
不要在名称的开头或结尾使用下划线。任何下划线后面都应紧跟一个字母(而不是数字或第二个下划线)。
此规则的动机在于,每个 protobuf 语言实现都可能将标识符转换为本地语言的风格:.proto 文件中的名称 song_id
最终可能会生成 SongId
、songId
或 song_id
等不同风格的字段访问器,具体取决于语言。
通过仅在字母前使用下划线,可以避免名称在一种风格中是唯一的,但在转换为其他风格后发生冲突的情况。
例如,DNS2
和 DNS_2
都会转换为 TitleCase 风格的 Dns2
。允许这两种名称中的任何一种都可能导致棘手的情况:当一个消息最初只在某些语言中使用(生成的代码保留了原始的 UPPER_SNAKE_CASE 风格)并被广泛建立后,后来才在一种将名称转换为 TitleCase 风格的语言中使用,此时就会发生冲突。
应用此风格规则意味着您应该使用 XYZ2
或 XYZ_V2
,而不是 XYZ_2
或 XYZ_2V
。
包(Packages)
使用点分隔的 lower_snake_case 名称作为包名。
多词包名可以是 lower_snake_case 或点分隔的(dot.delimited)(点分隔的包名在大多数语言中会生成为嵌套的包/命名空间)。
包名应尽量是基于项目名称的简短但唯一的名称。包名不应该是 Java 包(com.x.y
);而应使用 x.y
作为包名,并根据需要使用 java_package
选项。
消息名称
对消息名称使用 TitleCase。
message SongRequest {
}
字段名称
对字段名称(包括扩展)使用 snake_case。
对 repeated 字段使用复数名称。
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;
}
// Won't compile - `SET` enum name will clash
// with the one defined in `CollectionType` enum.
enum TennisVictoryType {
TENNIS_VICTORY_TYPE_UNSPECIFIED = 0;
GAME = 1;
SET = 2;
MATCH = 3;
}
当枚举定义在文件的顶层(而不是嵌套在消息定义内)时,名称冲突的风险很高;在这种情况下,同级枚举包括在设置了相同包名的其他文件中定义的枚举,而 protoc 在代码生成时可能无法检测到冲突已经发生。
为了避免这些风险,强烈建议执行以下操作之一
- 为每个值加上枚举名称的前缀(转换为 UPPER_SNAKE_CASE)
- 将枚举嵌套在一个包含它的消息中
这两种方法都足以减轻冲突风险,但首选带前缀值的顶层枚举,而不是仅仅为了解决问题而创建一个消息。由于某些语言不支持在“结构体”类型中定义枚举,因此首选带前缀值的方法可以确保在不同语言绑定中采用一致的方法。
服务(Services)
对服务名称和方法名称使用 TitleCase。
service FooService {
rpc GetSomething(GetSomethingRequest) returns (GetSomethingResponse);
rpc ListSomething(ListSomethingRequest) returns (ListSomethingResponse);
}
应避免的事项
Required 字段
Required 字段是一种强制规定,在解析线路字节时必须设置给定字段,否则拒绝解析该消息。这种 required 的不变性通常不会在内存中构造的消息上强制执行。Required 字段已在 proto3 中被移除。已迁移到 editions 2023 的 Proto2 required
字段可以使用 field_presence
功能并将其设置为 LEGACY_REQUIRED
来适应。
虽然在模式(schema)级别强制执行 required 字段在直观上是可取的,但 protobuf 的一个主要设计目标是支持长期的模式演进。无论某个字段在今天看起来多么明显是必需的,在未来都有可能不再需要设置该字段(例如,一个 int64 user_id
将来可能需要迁移到一个 UserId user_id
)。
特别是在中间件服务器可能会转发它们实际上不需要处理的消息的情况下,required
的语义对于那些长期演进目标来说已经证明是有害的,因此现在强烈不鼓励使用。
请参阅强烈不推荐使用 Required。
Groups
Groups 是嵌套消息的另一种语法和线路格式。Groups 在 proto2 中被视为已弃用,在 proto3 中被移除,并在 edition 2023 中被转换为分隔表示法。您可以使用嵌套消息定义和该类型的字段来代替 group 语法,并使用 message_encoding
功能以实现线路兼容性。
请参阅groups。