Proto 最佳實務
用戶端和伺服器永遠不會完全同時更新 - 即使您嘗試同時更新它們也是如此。其中一方可能會回溯。不要假設您可以進行破壞性變更,並且一切都會沒問題,因為用戶端和伺服器是同步的。
不要 重複使用標記編號
永遠不要重複使用標記編號。這會搞亂還原序列化。即使您認為沒有人使用該欄位,也不要重複使用標記編號。如果變更曾經生效,則您的 Proto 序列化版本可能會存在於某處的記錄中。或者另一個伺服器中可能有舊程式碼會因此損壞。
務必 為已刪除的欄位保留標記編號
當您刪除不再使用的欄位時,請保留其標記編號,以免未來有人不小心重複使用它。只需 reserved 2, 3;
就足夠了。不需要類型 (可讓您修剪依附元件!)。您也可以保留名稱,以避免回收現在已刪除的欄位名稱:reserved "foo", "bar";
。
務必 為已刪除的列舉值保留編號
當您刪除不再使用的列舉值時,請保留其編號,以免未來有人不小心重複使用它。只需 reserved 2, 3;
就足夠了。您也可以保留名稱,以避免回收現在已刪除的值名稱:reserved "FOO", "BAR";
。
務必 將新的列舉別名放在最後
當您新增新的列舉別名時,請將新名稱放在最後,以便讓服務有時間接收它。
若要安全地移除原始名稱 (如果它正用於交換,但它不應該),您必須執行下列步驟
將新名稱新增到舊名稱下方,並棄用舊名稱 (序列化程式將繼續使用舊名稱)
在每個剖析器都推出結構描述之後,交換這兩個名稱的順序 (序列化程式將開始使用新名稱,剖析器會接受這兩個名稱)
在每個序列化程式都擁有該版本的結構描述之後,您可以刪除已棄用的名稱。
注意:雖然理論上用戶端不應使用舊名稱進行交換,但遵循上述步驟仍然是禮貌的做法,尤其是對於廣泛使用的列舉名稱而言。
不要 變更欄位的類型
幾乎永遠不要變更欄位的類型;這會搞亂還原序列化,就像重複使用標記編號一樣。《protobuf 文件》概述了少數幾個可以接受的情況 (例如,在 int32
、uint32
、int64
和 bool
之間轉換)。但是,變更欄位的訊息類型將會中斷,除非新訊息是舊訊息的超集合。
不要 新增必要欄位
永遠不要新增必要欄位,而是新增 // required
來記錄 API 合約。必要欄位被認為是有害的,因此它們已從 proto3 中完全移除。讓所有欄位都成為選用或重複欄位。您永遠不知道訊息類型會持續多久,以及是否有人會在四年後被迫用空字串或零來填寫您的必要欄位,因為它在邏輯上不再是必要的,但 Proto 仍然這樣說。
對於 proto3,沒有 required
欄位,因此此建議不適用。
不要 建立具有大量欄位的訊息
不要建立具有「大量」(想想:數百個) 欄位的訊息。在 C++ 中,每個欄位大約會為記憶體中的物件大小增加 65 位元,無論它是否已填入 (指標為 8 個位元組,如果欄位宣告為選用,則位元欄位中會有另一個位元來追蹤欄位是否已設定)。當您的 Proto 變得太大時,產生的程式碼甚至可能無法編譯 (例如,在 Java 中,方法的規模有硬性限制 )。
務必 在列舉中包含未指定的數值
列舉應包含預設的 FOO_UNSPECIFIED
值,作為宣告中的第一個值 。當新值新增至 proto2 列舉時,舊用戶端會將欄位視為未設定,而 getter 會傳回預設值,如果不存在預設值,則會傳回第一個宣告的值 。為了與 proto 列舉 保持一致的行為,第一個宣告的列舉值應為預設的 FOO_UNSPECIFIED
值,並且應使用標記 0。您可能會想將此預設值宣告為語意上有意義的值,但作為一般規則,請勿這樣做,以協助您的協定隨著時間推移新增列舉值而發展。在容器訊息下宣告的所有列舉值都在相同的 C++ 命名空間中,因此請使用列舉的名稱作為未指定值的前置詞,以避免編譯錯誤。如果您永遠不需要跨語言常數,則 int32
將保留未知值並產生較少的程式碼。請注意,proto 列舉 要求第一個值為零,並且可以往返 (還原序列化、序列化) 未知的列舉值。
不要 將 C/C++ 巨集常數用於列舉值
使用 C++ 語言已定義的字詞 - 特別是在其標頭 (例如 math.h
) 中,如果其中一個標頭的 #include
陳述式出現在 .proto.h
的陳述式之前,則可能會導致編譯錯誤。避免使用巨集常數 (例如「NULL
」、「NAN
」和「DOMAIN
」) 作為列舉值。
務必 使用常用類型和通用類型
強烈建議使用下列常用、共用類型。例如,當已經存在完全合適的通用類型時,請勿在您的程式碼中使用 int32 timestamp_seconds_since_epoch
或 int64 timeout_millis
!
duration
是已簽署的固定長度時間跨度 (例如,42 秒)。timestamp
是獨立於任何時區或行事曆的時間點 (例如,2017-01-15T01:30:15.01Z)。interval
是獨立於時區或行事曆的時間間隔 (例如,2017-01-15T01:30:15.01Z - 2017-01-16T02:30:15.01Z)。date
是完整的行事曆日期 (例如,2005-09-19)。month
是一年中的月份 (例如,四月)。dayofweek
是一週中的某一天 (例如,星期一)。timeofday
是一天中的時間 (例如,10:42:23)。field_mask
是一組符號欄位路徑 (例如,f.b.d)。postal_address
是郵寄地址 (例如,1600 Amphitheatre Parkway Mountain View, CA 94043 USA)。money
是具有貨幣類型的金額 (例如,42 美元)。latlng
是緯度/經度配對 (例如,緯度 37.386051 和經度 -122.083855)。color
是 RGBA 色彩空間中的色彩。
務必 在個別檔案中定義訊息類型
定義 Proto 結構描述時,您應該讓每個檔案有一個訊息、列舉、擴充功能、服務或循環相依性群組。這讓重構更容易。當檔案分隔開來時,移動檔案比從包含其他訊息的檔案中擷取訊息容易得多。遵循此實務也有助於保持 Proto 結構描述檔案更小,從而提高可維護性。
如果它們將在您的專案之外廣泛使用,請考慮將它們放在自己的檔案中,且不包含任何依附元件。這樣,任何人都可以輕鬆使用這些類型,而不會在您的其他 Proto 檔案中引入轉移依附元件。
如需有關此主題的更多資訊,請參閱 1-1-1 規則。
不要 變更欄位的預設值
幾乎永遠不要變更 Proto 欄位的預設值。這會導致用戶端和伺服器之間的版本偏差。當用戶端的建構版本與伺服器的建構版本跨越 Proto 變更時,讀取未設定值的用戶端會看到與讀取相同未設定值的伺服器不同的結果。Proto3 移除了設定預設值的功能。
不要 從重複欄位改為純量欄位
雖然這不會導致損毀,但您會遺失資料。對於 JSON,重複性不符會遺失整個訊息。對於數值 Proto3 欄位和 Proto2 packed
欄位,從重複欄位改為純量欄位會遺失該欄位中的所有資料。對於非數值 Proto3 欄位和未註解的 Proto2 欄位,從重複欄位改為純量欄位將導致最後還原序列化的值「勝出」。
從純量欄位改為重複欄位在 Proto2 和具有 [packed=false]
的 Proto3 中是可行的,因為對於二進位序列化,純量值會變成單元素清單。
務必 遵循產生程式碼的風格指南
Proto 產生的程式碼在一般程式碼中會被參照。請確保 .proto
檔案中的選項不會導致產生違反風格指南的程式碼。例如
java_outer_classname
應遵循 https://google.github.io/styleguide/javaguide.html#s5.2.2-class-namesjava_package
和java_alt_package
應遵循 https://google.github.io/styleguide/javaguide.html#s5.2.1-package-namespackage
雖然在沒有java_package
時用於 Java,但始終直接對應於 C++ 命名空間,因此應遵循 https://google.github.io/styleguide/cppguide.html#Namespace_Names。如果這些風格指南衝突,請對 Java 使用java_package
。ruby_package
應採用Foo::Bar::Baz
格式,而不是Foo.Bar.Baz
。
不要 使用文字格式訊息進行交換
基於文字的序列化格式 (例如文字格式和 JSON) 將欄位和列舉值表示為字串。因此,當欄位或列舉值重新命名,或新增新的欄位或列舉值或擴充功能時,使用舊程式碼還原序列化這些格式的 Protocol Buffer 將會失敗。盡可能使用二進位序列化進行資料交換,並僅將文字格式用於人工編輯和偵錯。
如果您在 API 中或用於儲存資料時使用轉換為 JSON 的 Proto,您可能根本無法安全地重新命名欄位或列舉。
絕對不要 依賴跨建構版本的序列化穩定性
Proto 序列化的穩定性無法跨二進位檔或跨相同二進位檔的建構版本保證。例如,在建構快取金鑰時,不要依賴它。
不要 在與其他程式碼相同的 Java 套件中產生 Java Proto
將產生的 Java Proto 來源程式碼放入與手寫 Java 來源程式碼不同的套件中。package
、java_package
和 java_alt_api_package
選項控制產生的 Java 來源程式碼的發出位置。請確保手寫 Java 原始碼也不要位於同一個套件中。常見的做法是將您的 Proto 產生到專案中的 proto
子套件中,該子套件僅包含這些 Proto (也就是說,沒有手寫原始碼)。
避免將語言關鍵字用於欄位名稱
如果訊息、欄位、列舉或列舉值的名稱是讀取/寫入該欄位的語言中的關鍵字,則 Protobuf 可能會變更欄位名稱,並且可能具有與一般欄位不同的存取方式。例如,請參閱有關 Python 的此警告。
您也應避免在檔案路徑中使用關鍵字,因為這也可能導致問題。
附錄
API 最佳實務
本文件僅列出極有可能導致中斷的變更。如需有關如何製作可順利成長的 Proto API 的更高等級指南,請參閱API 最佳實務。