後方互換性サポートを備えたAPIを適切に開発する方法。 ヤンデックスワークショップ

こんにちは 私の名前はSergey Konstantinovで、YandexでMaps APIの開発を指揮しています。 最近、私は同僚と後方互換性をサポートする経験を共有しました。 私のレポートは、2つの不平等な部分で構成されていました。 最初の大きなものは、APIを適切に開発する方法に専念し、APIが耐え難いほど苦痛にならないようにします。 2番目は、何かをリファクタリングする必要があり、途中で後方互換性を壊さないようにする必要がある場合の対処方法です。



ウィキペディアを見ると、後方互換性については、これは新しいバージョンをリリースするときのシステムインターフェイスの保存であると書かれています。 実際、エンドユーザーにとっての下位互換性とは、システムの前のバージョン用に記述されたコードが次のバージョンでも同じように機能することを意味します。

開発者にとって、後方互換性は主に、機能を提供するというコミットメントがキャンセル、修正、またはサポートの停止が不可能であることを意味します。

コミットメントをする必要があるのはなぜですか? まず、ユーザーの時間とお金を節約します。 後方互換性をサポートする方が安価だと考えるのは単純です。 実際、顧客サポートのコストを分散するだけです。 生産における1つのfakapは、製品全体の開発全体よりもはるかにコストがかかります。

次に、カルマをサポートします。 下位互換性を意識的に破棄すると、ユーザーはバグよりもはるかに混乱します。 問題に対して無関心であることを明確に示している場合、人々は本当にそれを嫌います。

第三に、後方互換性は競争上の優位性です。 これは、開発コストなしで最新バージョンに切り替えることができることを意味し、サービスが生産を中断しないことを保証します。

下位互換性:適切なアーキテクチャ


設計段階で何ができるので、それが耐え難いほど苦痛にならないでしょうか? 予備的な3つのポイントがあります。 まず、後方互換性は無料ではありません。 適切なアーキテクチャを構築するにはオーバーヘッドが必要です。 もっと考え、より多くのエンティティを導入し、冗長なコードを書く必要があります。

第二に、開発を開始する前に、責任範囲を特定し、サポート対象を明確にする必要があります。 公開されているAPIの一部がドキュメントに記載されていない状況を最小限に抑えます。 フォーマットが記述されていないエンティティを読み取り(書き込みの場合はさらに)しないでください。

第三に、APIが正しく設計され、抽象化レベルによって構造化されていることが前提です。

これをすべて理解し、実現したとします。 5年近くの経験から学んだルールに移りましょう。

ルール番号1:より多くのインターフェース


制限では、インターフェイスではなく、特定のタイプを受け入れる単一の署名を公開ドキュメントに含めることはできません。 基本グローバルクラスと明示的に従属するコンポーネントに対して例外を作成できます。

interface IGeoObject : IChildOnMap, ICustomizable, IDomEventEmitter, IParentOnMap { attribute IEventManager events; attribute IGeometry geometry; attribute IOptionManager options; attribute IDataManager properties; attribute IDataManager state; } Map getMap(); IOverlay getOverlay(); IParentOnMap getParent(); IGeoObject setParent(IParentOnMap parent) 

これが後方互換性の損失を避けるのに役立つのはなぜですか? インターフェースが署名で宣言されている場合、インターフェースの2番目(3番目、4番目)の実装があれば問題はありません。 施設の責任は霧化されています。 インターフェイスは、転送されるオブジェクトの条件を強制しません。標準オブジェクトの継承者または独立した実装のいずれかです。

APIを設計するときにこれが役立つのはなぜですか? インターフェースの分離は、主に開発者が頭の中で秩序を回復するために必要です。 メソッドがパラメーターとして20個のフィールドと30個のメソッドを持つオブジェクトを受け入れる場合、これらのフィールドとメソッドから正確に何が必要かを考えることを強くお勧めします。

このルールを適用した結果、多くの部分的なインターフェイスを取得する必要があります。 署名には、入力パラメーターからの5±2を超えるプロパティまたはメソッドは必要ありません。 システムアーキテクチャ全体のコンテキストでオブジェクトのどのプロパティが重要で、どのプロパティが重要でないかがわかります。 その結果、インターフェイスの冗長性が低下します。

ルール番号2:階層


オブジェクトは階層内に配置する必要があります:誰が誰と対話するか。 オブジェクトに提示したインターフェースがこの階層に重なると、特定のインターフェースの階層が得られます。 今最も重要なこと:オブジェクトは、隣接するレベルのオブジェクトについてのみ知る権利を持っています。

これが後方互換性の損失を避けるのに役立つのはなぜですか? アーキテクチャの全体的な接続性が低下し、リンクが少なくなり、副作用が少なくなります。 また、オブジェクトを変更するときは、その隣の木にしかタッチできません。

明らかな方法でこれを取得することは常に可能ではありません。 必要なメソッドとプロパティは、中間リンクを介してチェーンに沿ってスローする必要があります(もちろん、抽象化のレベルを考慮して!)。 したがって、拡張ポイントのセットを自動的に受け取り、便利になります。

ルール3:コンテキスト


階層の中間レベルは、基礎となるレベルの情報コンテキストとして考慮してください。

例:
Map =地図上のコンテキスト(観察された地図領域+縮尺)。
IPane =クライアント座標でのコンテキストの配置。
ITileContainer =タイル座標でのコンテキストの配置。



オブジェクトツリーは、コンテキストの階層として見ることができます。 階層の各レベルは、何らかの抽象化レベルに対応する必要があります。

これが後方互換性の損失を避けるのに役立つのはなぜですか? 適切に構築されたコンテキストツリーは、リファクタリング中にほとんど変更されません。情報フローは表示されますが、消えることはほとんどありません。 コンテキストルールを使用すると、階層レベルを互いに効果的に分離できます。

これは、プロジェクトの情報スキームを完全なツリーよりも大幅に単純化することを念頭に置いて、APIを設計するときに役立ちます。 そして、オブジェクトが提供するコンテキストに関するオブジェクトの説明により、抽象化のレベルを正しく識別することができます。

ルール4:一貫性


この場合、データベースのACIDパラダイムでは一貫性という用語を使用します。 これは、トランザクション間で、オブジェクトの状態が常に有効でなければならないことを意味します。 オブジェクトは、いつでもその状態の完全な説明と、その状態のすべての変更を追跡できるイベントの完全なセットを提供する必要があります。

同様のパターンは一貫性に違反します:

 obj.name = '-'; // do something obj.setOptions('-'); // do something obj.update(); 

特に、ルールはここから続きます:更新、ビルド、適用メソッドを避けます。

これにより、後方互換性の損失を回避できます。 外部オブザーバーは、パブリックインターフェイスを使用して、オブジェクトの状態と履歴を常に完全に復元できます。 さらに、そのようなオブジェクトは、その内部構造を知らなくても、常に置換または複製できます。

オブジェクトの状態とその変化に関するイベントがあるような相互作用がある場合、オブジェクトのメソッドとイベントの命名法は多様性が少なくなり、一貫性が増します。 インターフェイスを選択し、これらすべてを念頭に置くことが容易になります。

ルール番号5:イベント


イベントを使用して、双方向でオブジェクト間の相互作用を整理します。

ボタンとレイアウト間の相互作用を整理する方法の2つの例を見てみましょう。

 button.onStateChange = function () { layout.setCaption(state.caption); } layout.onClick = function () { button.select(); } 



 button.onStateChange = function () { this.fire('statechange'); } layout.onClick = function () { this.fire('click') } 

2番目の相互作用スキームは、一貫性要件を遵守しながらネイティブに取得されます。



前者の場合、ボタンとレイアウトは互いの内部構造に関する詳細を知っています。後者の場合-いいえ。

これにより、後方互換性の損失を回避できます。 イベントは両方のオブジェクトのオプションです。一部のイベントにのみ応答し、2番目のオブジェクトの状態の一部のみを表示する両方のオブジェクトの実装を簡単にサポートできます。 同じアクションに応答する必要がある3番目のオブジェクトがある場合、問題はありません。

前の4つの手順を正しく実行すると、標準パターンが得られます。つまり、状態、その変更に関するイベント、このイベントをリッスンして何らかの形で反応する基になるオブジェクトがあります。 オブジェクト間の相互作用の組織は非常に統一されています。 この方法でのオブジェクト間の相互作用は、プライベートなものではなく、一般的なメソッドとイベントに基づいています。 特定のオブジェクトの詳細がはるかに少ない

ルール番号6:委任


6番目のルールは、論理的に最初の5つから続きます。 システム全体を構築し、インターフェースとイベント、抽象化レベルがあります。 ここで、すべてのロジックを下位レベルの抽象化に転送するために、可能な限り多くのことが必要です。 下位レベルの抽象化(レイアウト、対話プロトコルなど)の実装と機能はほとんどの場合変更されるため、下位レベルの抽象化へのインターフェースは可能な限り一般的でなければなりません。

このアプローチでは、オブジェクト間の接続が可能な限り抽象的になります。 必要に応じて、抽象化の下位レベルのオブジェクト全体を安全に書き換えることができます。

ルール番号7:テスト


インターフェイスのテストを作成します。

ルール番号8:外部ソース


ほとんどの場合、他のサービスによる下位互換性が維持されないため、下位互換性の維持に関する最大の問題が発生します。 隣接するサービス(データソース)を制御しない場合は、バージョン管理されたラッパーを取得します。

下位互換性:リファクタリング


乗る前に


状況を明確にします。



リファクタリングの1.5の受容:



リリースからリリースまで


安心のノートブックを手に入れましょう。

Source: https://habr.com/ru/post/J237459/


All Articles