プロトバッファが間違っおいたす

私の職業人生のほずんどで、プロトコルバッファの䜿甚に反察しおいたす。 それらはアマチュアによっお明確に曞かれおおり、非垞に専門性が高く、倚くの萜ずし穎に苊しみ、コンパむルするのが難しく、Google以倖の誰も実際に抱えおいない問題を解決したす。 プロトバッファのこれらの問題がシリアル化の抜象化の怜疫に残っおいれば、私の苊情はそこで終わりたす。 しかし、残念なこずに、Protobuffersの貧匱な蚭蚈は非垞に邪魔なため、これらの問題がコヌドに挏れるこずがありたす。



アマチュアによる狭い専門化ず開発



やめお メヌルクラむアントを閉じたす。「䞖界で最も優れた゚ンゞニアはGoogleで働いおいたす」、「その開発は定矩䞊、アマチュアによっお䜜成するこずはできたせん」ずいう手玙を既に曞いおいたす。 私はそれを聞きたくありたせん。



このトピックに぀いおは説明したせん。 完党な開瀺私は以前Googleで働いおいたした。 これは、私がProtobuffersを䜿甚した最初のしかし、残念ながら最埌ではない堎所でした。 話したい問題はすべおGoogleコヌドベヌスに存圚したす。 「プロトバッファの誀甚」などだけではありたせん。



Protobuffersの最倧の問題は、ひどい型システムです。 Javaファンはここでく぀ろいでいるはずですが、残念ながら文字通り、誰もJavaが適切に蚭蚈された型システムであるずは考えおいたせん。 動的型付けキャンプの人たちは䞍必芁な制限に぀いお䞍満を述べおいたすが、静的型付けキャンプの代衚者たちは、私のような䞍必芁な制限ず、型システムに本圓に必芁なものすべおの欠劂に぀いお䞍満を蚀っおいたす。 どちらの堎合も負け。



アマチュアによる狭い専門化ず開発は密接に関連しおいたす。 仕様の倚くは、最埌の瞬間にボルトで固定されおいるように芋えた-そしお最埌の瞬間に明らかにボルトで固定された。 いく぀かの制限は、あなたを止めさせ、頭を掻き、「䞀䜓䜕だ」ず尋ねさせたす。しかし、これらはより深い問題の症状です。



明らかに、プロトバッファはアマチュアによっお䜜成されたす。これは、よく知られた既に解決枈みの問題に察する貧匱な゜リュヌションを提䟛するためです。



構成の欠劂



プロトバッファヌは、互いに機胜しないいく぀かの機胜を提䟛したす。 たずえば、盎亀のリストを芋おください。同時に、ドキュメントで芋぀けたタむピング機胜が制限されおいたす。





このクレむゞヌな制限のリストは、最埌の瞬間に蚭蚈ずねじ蟌み機胜を無原則に遞択した結果です。 たずえば、サむドタむプではなく、コヌドゞェネレヌタヌが盞互に排他的なオプションフィヌルドを生成するため、 oneof



フィヌルドをrepeated



こずはできたせん。 このような倉換は、特異なフィヌルドに察しおのみ有効です埌で説明するように、それでも機胜したせん。



repeated



こずのできないmap



フィヌルドの制限は、ほが同じオペラからのものですが、型システムの異なる制限を瀺しおいたす。 舞台裏では、 map<k,v>



はrepeated Pair<k,v>



䌌たものに倉換されたす。 そしお、 repeated



は蚀語の魔法のキヌワヌドであり、通垞の型ではないため、それ自䜓ず結合したせん。



enum



の問題に぀いおのあなたの掚枬は、私のように真実です。



これらすべおに぀いおずおもむラむラしおいるのは、珟代の型システムがどのように機胜するかに぀いおの䞍十分な理解です。 この理解はProtobuffers の仕様を劇的に簡玠化し、同時にすべおの任意の制限を取り陀くでしょう。



解決策は次のずおりです。





以䞊です これらの3぀の倉曎は、可胜なデヌタを決定するために必芁なすべおです。 このシンプルなシステムでは、他のすべおのProtobuffers仕様をやり盎すこずができたす。



たずえば、 optional



フィヌルドをやり盎すこずができたす。



 product Unit { // no fields } coproduct Optional<t> { t value = 0; Unit unset = 1; }
      
      





repeated



フィヌルドの䜜成も簡単です。



 coproduct List<t> { Unit empty = 0; Pair<t, List<t>> cons = 1; }
      
      





もちろん、シリアル化の実際のロゞックを䜿甚するず、リンクリストをネットワヌク経由でプッシュするよりも賢明なこずができたす。結局、 実装ずセマンティクスは互いに察応する必芁はありたせん 。



疑わしい遞択



Javaスタむルのプロトバッファは、 スカラヌタむプずメッセヌゞタむプを区別したす。 スカラヌは、ほがint32



、 bool



、 string



などのマシンプリミティブに察応しおいstring



。 䞀方、メッセヌゞタむプはすべお残りです。 すべおのラむブラリおよびナヌザヌタむプはメッセヌゞです。



もちろん、2皮類のタむプのセマンティクスはたったく異なりたす。



スカラヌ型のフィヌルドは垞に存圚したす。 それらをむンストヌルしなかった堎合でも。 私はすでに蚀った少なくずもproto3 1で プロトバッファは、デヌタがたったくない堎合でもれロに初期化されおいたすか スカラヌフィヌルドは停の倀を取埗したす。たずえば、 uint32



0



に初期化され、 string



""



初期化されたす。



デフォルト倀が割り圓おられおいるフィヌルドずプロトバッファにないフィヌルドを区別するこずはできたせん。 おそらく、この決定は、スカラヌのデフォルトを転送しないように最適化のために行われたものです。 ドキュメントではこの最適化に぀いお蚀及しおいないため、これは単なる仮定です。したがっお、あなたの仮定は私のものより悪くなるこずはありたせん。



Protobuffersの䞋䜍および将来のAPI互換性の理想的な゜リュヌションの䞻匵を議論するずき、未定矩の倀ずデフォルトの倀を区別できないこずは悪倢であるこずがわかりたす。 特に、フィヌルド甚に1ビット蚭定するかどうかを保存するずいう意識的な決定である堎合。



この動䜜をメッセヌゞタむプず比范したす。 スカラヌフィヌルドは「ダム」ですが、メッセヌゞフィヌルドの動䜜は完党に狂っおいたす。 内郚的には、メッセヌゞフィヌルドは存圚するかしないかのどちらかですが、動䜜はおかしいです。 アクセサ甚の小さな擬䌌コヌドは、千語に倀したす。 これをJavaたたは他の堎所で想像しおください。



 private Foo m_foo; public Foo foo { // only if `foo` is used as an expression get { if (m_foo != null) return m_foo; else return new Foo(); } // instead if `foo` is used as an lvalue mutable get { if (m_foo = null) m_foo = new Foo(); return m_foo; } }
      
      





理論的には、 foo



フィヌルドが蚭定されおいない堎合、デフォルトで初期化されたコピヌが衚瀺されたすが、それを尋ねるかどうかはわかりたせんが、コンテナを倉曎するこずはできたせん。 しかし、 foo



を倉曎するず、その芪も倉曎されたす これは、未定矩の倀が䜕を意味するのかを理解するために、 Maybe Foo



タむプずそれに関連する「頭痛」の䜿甚を避けるためです。



このような行動は、法埋に違反するため、特に悪質です。 ゞョブmsg.foo = msg.foo;



動䜜したせん。 代わりに、実装は実際にmsg



をfoo



コピヌに静かに倉曎したすmsg



が以前に存圚しおいなかった堎合は、れロで初期化したす。



スカラヌフィヌルドずは異なり、少なくずもメッセヌゞフィヌルドが蚭定されおいないず刀断できたす。 プロトバッファの蚀語バむンディングは、生成されたbool has_foo()



メ゜ッドのようなものを提䟛したす。 存圚する堎合、あるプロトバッファから別のプロトバッファにメッセヌゞフィヌルドを頻繁にコピヌする堎合、次のコヌドを蚘述する必芁がありたす。



 if (src.has_foo(src)) { dst.set_foo(src.foo()); }
      
      





少なくずも静的型付けの蚀語では、 foo()



、 set_foo()



およびhas_foo()



間の名目䞊の関係のため、このテンプレヌトを抜象化できないこずに泚意しおください。 これらの関数はすべお独自の識別子であるため、プリプロセッサマクロを陀き、プログラムで生成する手段はありたせん。



 #define COPY_IFF_SET(src, dst, field) \ if (src.has_##field(src)) { \ dst.set_##field(src.field()); \ }
      
      





ただし、プリプロセッサマクロはGoogleスタむルガむドで犁止されおいたす。



代わりに、すべおの远加フィヌルドがMaybe



ずしお実装された堎合、抜象化されたダむダルピアを安党に配眮できたす。



䞻題を倉えるために、別の疑わしい決定に぀いお話したしょう。 oneof



フィヌルドの1぀を定矩できたすが、それらのセマンティクスは連産品のタむプに察応しおいたせん  初心者間違いだよ 代わりに、各セッタヌのオプションフィヌルドずセッタヌのマゞックコヌドを取埗したす。これは、蚭定されおいる堎合、他のフィヌルドを元に戻すだけです。



䞀芋、これは正しいタむプのナニオンず意味的に同等であるように思われたす。 しかし、代わりに、うんざりするような蚀葉で衚せない゚ラヌの原因になりたす この動䜜が䞍正な実装ず組み合わされた堎合msg.foo = msg.foo;



、このような䞀芋正垞な割り圓おは、任意の量のデヌタを静かに削陀したす



その結果、これは、フィヌルドの1぀oneof



法埋を遵守するPrism



圢成せず、メッセヌゞが法埋を遵守するLens



圢成しないこずを意味したす。 バグなしで重芁なプロトバッファ操䜜を蚘述しようずするあなたの幞運を祈りたす。 protobuffersに、普遍的で゚ラヌのない倚態性コヌドを曞くこずは、文字通り䞍可胜です 。



これは、特に正反察のこずを玄束するパラメトリック倚型を愛する私たちにずっおは、聞くのはあたり楜しいこずではありたせん。



䞋䜍互換性ず将来の互換性



Protobuffersでよく蚀われる「キラヌ機胜」の1぀は、「䞋䜍互換性ず䞊䜍互換性のあるAPIを䜜成するためのトラブルのない機胜」です。 この声明は真実を曖昧にするためにあなたの目の前に掛けられたした。



そのprotobuffersは寛容です。 圌らは、あなたのデヌタがどのように芋えるかに぀いお党く玄束しないので、過去たたは未来からのメッセヌゞに察凊するこずができたす。 すべおがオプションです しかし、それが必芁な堎合、Protobuffersは、それが理にかなっおいるかどうかに関係なく、タむプチェックで䜕かを準備し、あなたに喜んで提䟛したす。



これは、Protobuffersが玄束された「タむムトラベル」を実行し、 デフォルトで静かに間違ったこずを実行するこずを意味したす 。 もちろん、慎重なプログラマヌは、受け取ったプロトバッファヌの正確さをチェックするコヌドを曞くこずができたすそしおそうすべきです。 しかし、すべおのサむトで保護の正圓性チェックを蚘述した堎合、逆シリアル化手順が蚱容範囲を超えおいたこずを意味しおいるだけかもしれたせん。 怜蚌のロゞックを明確に定矩された境界から分散させ、コヌドベヌス党䜓にスマッゞするだけでした。



考えられる匕数の1぀は、protobuffersがメッセヌゞで理解できない情報を保存するこずです。 これは、原則ずしお、このバヌゞョンのスキヌムを理解しおいない仲介者を介したメッセヌゞの非砎壊的な送信を意味したす。 これは明らかな勝利ですよね



もちろん、玙の䞊ではこれは玠晎らしい機胜です。 しかし、このプロパティが実際に保存されおいるアプリケヌションを芋たこずはありたせん。 ルヌティング゜フトりェアを陀き、プログラムはメッセヌゞの特定のビットのみをチェックし、倉曎せずに転送するこずを望みたせん。 プロトバッファ䞊のプログラムの倧郚分は、メッセヌゞをデコヌドし、別のメッセヌゞに倉換しお、別の堎所に送信したす。 残念ながら、これらの倉換は泚文に応じお行われ、手動で゚ンコヌドされたす。 たた、あるプロトバッファから別のプロトバッファぞの手動倉換では、文字通り意味がないため、未知のフィヌルドは保持されたせん。



普遍的に互換性があるプロトバッファに察するこの遍圚的な態床は、他の芋苊しい方法で珟れたす。 Protobuffersのスタむルガむドは、DRYに積極的に反察し、可胜な限りコヌドに定矩を埋め蟌むこずを提案しおいたす。 圌らは、これにより、定矩が異なる堎合、将来的に別のメッセヌゞを䜿甚できるようになるず䞻匵しおいたす。 私は、 䞇が䞀のために 、良いプログラミングの60幎の実践を攟棄するこずを申し出おいるこずを匷調したす。突然、将来、䜕かを倉曎する必芁がありたす。



問題の根本は、Googleがデヌタの意味ずその物理的衚珟を組み合わせおいるこずです。 Google芏暡の堎合、それは理にかなっおいたす。 最終的には、ネットワヌクを䜿甚したプログラマの時間絊、Xバむトを保存するコストなどを比范する内郚ツヌルがありたす。 ほずんどのテクノロゞヌ䌁業ずは異なり、プログラマヌの絊䞎はGoogleの最小の費甚項目の1぀です。 財政的には、プログラマヌが数バむトを節玄するために時間を費やすこずは理にかなっおいたす。



倧手テクノロゞヌ䌁業5瀟に加えお、Googleの5桁以内にいる人は誰もいたせん。 スタヌトアップは、゚ンゞニアリング時間を費やしおバむトを節玄する䜙裕はありたせん 。 しかし、プロセスのバむト数を節玄し、プログラマヌの時間を浪費するこずは、Protobuffersがたさに最適化されおいるものです。



それに盎面したしょう。 Googleの芏暡に適合しないため、適合したせん。 「Googleが䜿甚しおいる」ずいう理由だけでなく、「これらは業界のベストプラクティスである」ずいう理由だけで、テクノロゞヌのカヌゎカルトの䜿甚をやめたす。



Protobuffersはコヌドベヌスを汚染したす



Protobuffersの䜿甚をネットワヌクのみに制限するこずが可胜であれば、このテクノロゞヌに぀いおそれほど厳しくは語りたせん。 残念ながら、原則ずしおいく぀かの゜リュヌションがありたすが、実際の゜フトりェアで実際に䜿甚するのに十分な゜リュヌションはありたせん。



プロトバッファは、通信チャネルを介しお送信するデヌタに察応しおいたす。 倚くの堎合、アプリケヌションが実際に䜿甚したい実際のデヌタず䞀臎しおいたすが、 同䞀ではありたせん。 これにより、私たちは䞍快な立堎に眮かれたす。次の3぀の悪いオプションのいずれかを遞択する必芁がありたす。



  1. 本圓に必芁なデヌタを蚘述する別のタむプを維持し、䞡方のタむプが同時にサポヌトされるようにしたす。
  2. アプリケヌションが送信および䜿甚するための圢匏で完党なデヌタをパックしたす。
  3. 送信するために短い圢匏から必芁なたびに完党なデヌタを取埗したす。


オプション1は明らかに「正しい」゜リュヌションですが、Protobuffersには適しおいたせん。 この蚀語は、2぀の圢匏で二重に機胜するタむプを゚ンコヌドするほど匷力ではありたせん。 ぀たり、完党に独立したデヌタ型を蚘述し、Protobuffersず同期しお開発し、 具䜓的にはそれらのシリアル化コヌドを蚘述する必芁がありたす 。 ただし、ほずんどの人はProtobuffersを䜿甚しおシリアル化コヌドを蚘述しないように芋えるため、このオプションは明らかに実装されおいたせん。



代わりに、プロトバッファヌを䜿甚するコヌドにより、コヌドベヌス党䜓にプロトバッファヌを配垃できたす。 それは珟実です。 Googleでの私の䞻なプロゞェクトは、1぀のバリ゚ヌションのProtobuffersで蚘述された「プログラム」を受け取り、別のバリ゚ヌションで同等の「プログラム」を䜜成するコンパむラヌでした。 入力圢匏ず出力圢匏はたったく異なっおいたため、C ++の正しい䞊列バヌゞョンは機胜したせんでした。 その結果、Protobuffersデヌタおよび生成されたコヌドが非垞に難しく、興味深い凊理を行うこずができなかったため、私のコヌドではリッチコンパむラヌの蚘述手法を䜿甚できたせんでした。



その結果、50行の再垰スキヌムではなく、10,000行の特別なバッファシャッフルが䜿甚されたした。 私が曞きたかったコヌドは、プロトバッファでは文字通り䞍可胜でした。



これは1぀のケヌスですが、䞀意ではありたせん。 コヌド生成の厳しい性質により、蚀語のプロトバッファの症状は決しお慣甚的ではなく、コヌドゞェネレヌタを曞き盎さない限りそうするこずはできたせん。



しかし、それでも、タヌゲット蚀語に安っぜい型システムを埋め蟌む問題はただありたす。 Protobuffersのほずんどの機胜は十分に考慮されおいないため、これらの疑わしいプロパティはコヌドベヌスに挏れたす。 これは、実装するだけでなく、Protobuffersずやり取りしたいプロゞェクトでこれらの悪いアむデアを䜿甚せざるを埗ないこずを意味したす。



堅実な基盀では、意味のないこずを実珟するのは簡単ですが、別の方向に進むず、せいぜい困難に盎面し、最悪の堎合、本圓の叀代の恐怖に遭遇したす。



䞀般に、プロゞェクトにProtobuffersを実装する人には垌望をあきらめたす。






1.今日たで、proto2ず、フィヌルドをrequired



ずしおマヌクするrequired



があるかどうかに぀いお、Googleで激しい議論がrequired



たす。 マニフェスト「 optional



は有害ず芋なされoptional



」 ず 「 required



有害ず芋なされたす」が同時に配垃されたす。 頑匵っおください。 ↑



All Articles