Proto Best Practices
Proto最佳实践
This topic contains vetted best practices for authoring Protocol Buffers.
本主题包含经过审查的创作协议缓冲区的最佳实践。
Clients and servers are never updated at exactly the same time - even when you try to update them at the same time. One or the other may get rolled back. Don’t assume that you can make a breaking change and it’ll be okay because the client and server are in sync.
客户端和服务器永远不会完全同时更新,即使试图同时更新它们。其中一个或另一个可能会被回滚。不要以为可以做出突破性的改变,因为客户端和服务器是同步的。
Don’t Re-use a Tag Number
不要重复使用标签号
Never re-use a tag number. It messes up deserialization. Even if you think no one is using the field, don’t re-use a tag number. If the change was live ever, there could be serialized versions of your proto in a log somewhere. Or there could be old code in another server that will break.
切勿重复使用标签号。它扰乱了反序列化。即使认为没有人在使用这个字段,也不要重复使用标签号。如果更改是实时的,那么在某个日志中可能会有proto的序列化版本。或者,另一台服务器中可能有旧代码将被破坏。
Don’t Change the Type of a Field
不更改字段的类型
Almost never change the type of a field; it’ll mess up deserialization, same as re-using a tag number. The protobuf docs outline a small number of cases that are okay (for example, going between int32
, uint32
, int64
and bool
). However, changing a field’s message type will break unless the new message is a superset of the old one.
几乎从不改变字段的类型;它会打乱反序列化,就像重新使用标记号一样。protobuf文档概述了少数可以处理的情况(例如,介于int32、uint32、int64和bool之间)。但是,更改字段的消息类型将中断,除非新消息是旧消息的超集。
Don’t Add a Required Field
不添加必填字段
Never add a required field, instead add // required
to document the API contract. Required fields are considered harmful by so many they were removed from proto3 completely. Make all fields optional or repeated. You never know how long a message type is going to last and whether someone will be forced to fill in your required field with an empty string or zero in four years when it’s no longer logically required but the proto still says it is.
永远不要添加必填字段,而是添加// required
字段以记录API合同。Required字段被许多人认为是有害的,它们被完全从proto3中移除。将所有字段设为可选字段或重复字段。永远不知道一个消息类型会持续多久,也不知道四年后,当逻辑上不再需要,但proto仍然表示需要时,是否会有人被迫用空字符串或零来填写你的必填字段。
Don’t Make a Message with Lots of Fields
不要使用大量字段发送消息
Don’t make a message with “lots” (think: hundreds) of fields. In C++ every field adds roughly 65 bits to the in-memory object size whether it’s populated or not (8 bytes for the pointer and, if the field is declared as optional, another bit in a bitfield that keeps track of whether the field is set). When your proto grows too large, the generated code may not even compile (for example, in Java there is a hard limit on the size of a method ).
不要用“很多”(想想:几百个)字段来传达信息。在C++中,无论是否填充,每个字段都会向内存中的对象大小添加大约65位(指针为8个字节,如果字段被声明为可选,则为跟踪字段是否设置的位字段中的另一位)。当proto变得太大时,生成的代码甚至可能无法编译(例如,在Java中,方法的大小有严格限制)。
Do Include an Unspecified Value in an Enum
在枚举中包含未指定的值
Enums should include a default FOO_UNSPECIFIED
value as the first value in the declaration . When new values are added to a proto2 enum, old clients will see the field as unset and the getter will return the default value or the first-declared value if no default exists . For consistent behavior with proto enums, the first declared enum value should be a default FOO_UNSPECIFIED
value and should use tag 0. It may be tempting to declare this default as a semantically meaningful value but as a general rule, do not, to aid in the evolution of your protocol as new enum values are added over time. All enum values declared under a container message are in the same C++ namespace, so prefix the unspecified value with the enum’s name to avoid compilation errors. If you’ll never need cross-language constants, an int32
will preserve unknown values and generates less code. Note that proto enums require the first value to be zero and can round-trip (deserialize, serialize) an unknown enum value.
枚举应该包括一个默认的FOO_UNSPECIFIED值作为声明中的第一个值。当新值添加到proto2枚举中时,旧客户端将看到该字段未设置,getter将返回默认值或第一个声明的值(如果不存在默认值)。为了与proto枚举保持一致,第一个声明的枚举值应该是默认的FOO_UNSPECIFIED值,并且应该使用标记0。将此默认值声明为语义上有意义的值可能很诱人,但作为一般规则,不要这样做,以随着时间的推移添加新的枚举值,从而帮助协议的发展。容器消息下声明的所有枚举值都在同一个C++命名空间中,因此在未指定的值前面加上枚举的名称以避免编译错误。如果永远不需要跨语言常量,那么int32将保留未知值并生成更少的代码。请注意,proto枚举要求第一个值为零,并且可以往返(反序列化、序列化)未知枚举值。
Don’t Use C/C++ Macro Constants for Enum Values
不要将C/C++宏常量用于枚举值
Using words that have already been defined by the C++ language - specifically, in its headers such as math.h
, may cause compilation errors if the #include
statement for one of those headers appears before the one for .proto.h
. Avoid using macro constants such as “NULL
,” “NAN
,” and “DOMAIN
” as enum values.
如果其中一个头文件的#include语句出现在.proto.h标头之前,则使用C++语言已经定义的单词,特别是在其头文件(如math.h)中,可能会导致编译错误。避免使用宏常量(如“NULL”、“NAN”和“DOMAIN”)作为枚举值。
Do Use Well-Known Types and Common Types
请务必使用已知类型和常见类型
Embedding the following common, shared types is strongly encouraged. Do not use int32 timestamp_seconds_since_epoch
or int64 timeout_millis
in your code when a perfectly suitable common type already exists!
强烈鼓励嵌入以下常见的共享类型。当已经存在完全合适的公共类型时,不要在代码中使用int32 timestamp_seconds_since_epoch或int64 timeout_millis!
Well-Known Types:
众所周知的类型:
- duration is a signed, fixed-length span of time (for example, 42s).
- 持续时间是一个有符号的、固定长度的时间跨度(例如,42s)。
- timestamp is a point in time independent of any time zone or calendar (for example, 2017-01-15T01:30:15.01Z).
- 时间戳是一个独立于任何时区或日历的时间点(例如,2017-01-15T01-30:15.01Z)。
- field_mask is a set of symbolic field paths (for example, f.b.d).
- fieldmask是一组符号字段路径(例如,f.b.d)。
Common Types:
常见类型:
- interval is a time interval independent of time zone or calendar (for example, 2017-01-15T01:30:15.01Z - 2017-01-16T02:30:15.01Z).
- interval是独立于时区或日历的时间间隔(例如,2017-01-15T01:20:15.01Z-2017-01-16T02:30:15.01Z)。
- date is a whole calendar date (for example, 2005-09-19).
- 日期是整个日历日期(例如2005-09-19)。
- dayofweek is a day of week (for example, Monday).
- 星期一是一周中的一天(例如,星期一)。
- timeofday is a time of day (for example, 10:42:23).
- timeofday是一天中的时间(例如,10:42:23)。
- latlng is a latitude/longitude pair (for example, 37.386051 latitude and -122.083855 longitude).
- latlng是一对纬度/经度(例如,37.386051纬度和-122.083855经度)。
- money is an amount of money with its currency type (for example, 42 USD).
- money是一种货币类型的货币量(例如,42美元)。
- postal_address is a postal address (for example, 1600 Amphitheatre Parkway Mountain View, CA 94043 USA).
- postal_address是一个邮政地址(例如,1600 Amphitheatre Parkway Mountain View,CA 94043 USA)。
- color is a color in the RGBA color space.
- 颜色是RGBA颜色空间中的一种颜色。
- month is a month of year (for example, April).
- 月份是一年中的一个月(例如,四月)。
Do Define Widely-used Message Types in Separate Files
在单独的文件中定义广泛使用的消息类型
If you’re defining message types or enums that you hope/fear/expect to be widely used outside your immediate team, consider putting them in their own file with no dependencies. Then it’s easy for anyone to use those types without introducing the transitive dependencies in your other proto files.
如果正在定义希望/害怕/期望在直属团队之外广泛使用的消息类型或枚举,请考虑将它们放在自己的文件中,不带依赖项。然后,任何人都可以很容易地使用这些类型,而无需在其他原型文件中引入可传递的依赖关系。
Do Reserve Tag Numbers for Deleted Fields
为已删除的字段保留标记号
When you delete a field that’s no longer used, reserve its tag number so that no one accidentally re-uses it in the future. Just reserved 2, 3;
is enough. No type required (lets you trim dependencies!). You can also reserve names to avoid recycling now-deleted field names: reserved "foo", "bar";
.
删除不再使用的字段时,请保留其标记号,这样以后就不会有人意外地重复使用它。刚刚保留了2,3;足够了。不需要任何类型(可以修剪依赖项!)。还可以保留名称以避免回收现在已删除的字段名称:保留的“foo”、“bar”;。
Do Reserve Numbers for Deleted Enum Values
为已删除的枚举值保留数字
When you delete an enum value that’s no longer used, reserve its number so that no one accidentally re-uses it in the future. Just reserved 2, 3;
is enough. You can also reserve names to avoid recycling now-deleted value names: reserved "FOO", "BAR";
.
删除不再使用的枚举值时,请保留其编号,这样以后就不会有人意外地重复使用它。刚刚保留了2,3;足够了。还可以保留名称以避免回收现在已删除的值名称:保留的“FOO”、“BAR”;。
Don’t Change the Default Value of a Field
不更改字段的默认值
Almost never change the default value of a proto field. This causes version skew between clients and servers. A client reading an unset value will see a different result than a server reading the same unset value when their builds straddle the proto change. Proto3 removed the ability to set default values.
几乎从不更改proto字段的默认值。这会导致客户端和服务器之间的版本偏移。当客户端的构建跨越原型更改时,读取未设置的值的客户端将看到与读取相同未设置值的服务器不同的结果。Proto3删除了设置默认值的功能。
Don’t Go from Repeated to Scalar
不要从重复变为标量
Although it won’t cause crashes, you’ll lose data. For JSON, a mismatch in repeatedness will lose the whole message. For numeric proto3 fields and proto2 packed
fields, going from repeated to scalar will lose all data in that field. For non-numeric proto3 fields and un-annotated proto2 fields, going from repeated to scalar will result in the last deserialized value “winning.”
虽然它不会导致崩溃,但会丢失数据。对于JSON,重复性的不匹配将丢失整个消息。对于数值proto3字段和proto2压缩字段,从重复到标量将丢失该字段中的所有数据。对于非数字的proto3字段和未注释的proto2字段,从重复到标量将导致最后一个反序列化值“获胜”
Going from scalar to repeated is OK in proto2 and in proto3 with [packed=false]
because for binary serialization the scalar value becomes a one-element list .
在proto2和proto3中,从标量到重复是可以的,因为对于二进制序列化,标量值变成一个单元素列表。
Do Follow the Style Guide for Generated Code
请遵循生成代码的样式指南
Proto generated code is referred to in normal code. Ensure that options in .proto
file do not result in generation of code which violate the style guide. For example:
协议生成的代码在正常代码中被引用。确保.proto文件中的选项不会导致生成违反样式指南的代码。例如:
-
java_outer_classname
should follow Google Java Style Guide -
java_outer_classname应该遵循Google java样式指南
-
java_package
andjava_alt_package
should follow Google Java Style Guide -
java_package和java_alt_package应遵循Google java样式指南
-
package
, although used for Java whenjava_package
is not present, always directly corresponds to C++ namespace and thus should follow Google C++ Style Guide. If these style guides conflict, usejava_package
for Java. -
包,尽管在不存在Java_package时用于Java,但始终直接对应于C++命名空间,因此应该遵循GoogleC++样式指南。如果这些样式指南冲突,请使用java_package for java。
Never Use Text-format Messages for Interchange
从不使用文本格式的消息进行交换
Deserializing of text-format protocol buffers will fail when the binary is unaware of a field rename, a new field, or a new extension. In general, text-format is not future proof. Use text-format for human editing and debugging only.
当二进制文件不知道字段重命名、新字段或新扩展时,文本格式协议缓冲区的反序列化将失败。一般来说,文本格式不能经得起未来的考验。仅将文本格式用于人工编辑和调试。
Never Rely on Serialization Stability Across Builds
永远不要依赖跨构建的序列化稳定性
The stability of proto serialization is not guaranteed across binaries or across builds of the same binary. Do not rely on it when, for example, building cache keys.
在二进制文件之间或同一二进制文件的构建之间,不能保证proto序列化的稳定性。例如,在构建缓存密钥时不要依赖它。
Don’t Generate Java Protos in the Same Java Package as Other Code
不要在与其他代码相同的Java包中生成Java Protos
Generate Java proto sources into a separate package from your hand-written Java sources. The package
, java_package
and java_alt_api_package
options control where the generated Java sources are emitted. Make sure hand-written Java source code does not also live in that same package. A common practice is to generate your protos into a proto
subpackage in your project that only contains those protos (that is, no hand-written source code).
将Java proto源代码从手工编写的Java源代码中生成到一个单独的包中。包、java_package和java_alt_api_package选项控制生成的java源的发出位置。确保手工编写的Java源代码不会也存在于同一个包中。一种常见的做法是将proto生成到项目中的proto子包中,该子包只包含这些proto(也就是说,没有手工编写的源代码)。
Appendix
附录
API Best Practices
API最佳实践
This document lists only changes that are extremely likely to cause breakage. For higher-level guidance on how to craft proto APIs that grow gracefully see API Best Practices.
本文档仅列出极有可能导致损坏的更改。有关如何创建优雅增长的原型API的高级指导,请参阅API最佳实践。