優れたSDKインターフェース設計の10の原則

長年にわたり、私は多くのオーディオおよびビデオSDKに参加してその設計と開発を主導し、大小を問わず数十のtoB顧客にサービスを提供してきました。その中で、私は深い洞察を持っています。

PaaSテクノロジーミドルウェア製品は、サーバーとカーネルの設計と実装がどれほど素晴らしいか美しいかに関係なく、最終的に顧客の開発者に提供されるSDKが最も重要な要素と外観です。それは、たとえ存在する場合でも、適切に設計されています。不十分さもある程度補うことができます。その貧弱な設計は最下層のすべての努力をほとんど放棄し、それはまた無数の効果のない残業とトラブルシューティングの投資を追加します。

この記事では、優れたSDKが次の目標を達成するためにインターフェイス仕様を設計する方法に焦点を当てます。 

  1. 簡潔で明確、明確な境界、直交するインターフェース(2つのインターフェースが互いに競合しない)、ユーザーはピットを踏むのは簡単ではありません
  2. 各APIの動作が決定され、呼び出しエラーまたは実行時例外のフィードバックがタイムリーかつ正確になります
  3. 上級者向け:豊富な構成、豊富なコールバック、優れたビジネスのスケーラビリティと柔軟性

私は「EffectiveC ++」の書き込みモードに敬意を表して、個人的な考えや結論を用語の形で説明および例示します(最近深く参加したRTC SDKインターフェイスの設計を例として取り上げます)。

項目1:パラメータ構成は独立したプロファイルクラスを提供し、各パラメータに設定されたメソッドを提供しません

//適切なケース
//適切なデフォルト値
クラスを指定することを忘れないでくださいAudioProfile  
{ 
   int samplerate {44100}; 
   int channel {1}; 
}; 

//適切なデフォルト値
クラスを指定することを忘れないでくださいVideoProfile  
{ 
   int maxEncodeWidth {1280}; 
   int maxEncodeHeight {720}; 
   int maxEncodeFps {15}; 
}; 

// SystemProfile、ScreenProfileなどのように適切に拡張できます... 
class EngineProfile  
{ 
    AudioProfile audio; 
    VideoProfile video; 
}; 

class RtcEngine  
{ 
public:
    static RtcEngine * CreateRtcEngine(const EngineProfile&profile)= 0; 
}; 

//悪いケース
// 1。コアインターフェイスクラスRtcEngineの関数の数が爆発しました
// 2。ビジネスパーティがAPIを呼び出す時間を制限できない(おそらく、部屋に参加した後、またはパラメータを設定するのに不適切な時間に) 
// 3.構成が動的更新をサポートすると予想される場合はどうなりますか?通常、構成を頻繁に更新することはお勧めしません(SDKの内部動作に影響します)
。//必要に応じて、updateXXXXまたはswitchXXXインターフェイスをエンジン
クラスRtcEngine  
{ 
public:
    static RtcEngine * CreateRtcEngine()= 0; 
    
    virtual voidsetAudioSampelerateで明示的に指定してください(int samplerate)= 0; 
    virtual void setAudioChannels(intチャネル)= 0; 
    virtual void setVideoMaxEncodeResolution(int width、int height)= 0; 
    virtual void setVideoMaxEncodeFps(int fps)= 0; 
};

項目2:非実行時のステータスと情報のクエリおよび構成インターフェイスは静的メソッドを提供します

//グッドケース
クラスRtcEngine  
{ 
public:
    static int GetSdkVersion(); 
    static void SetLogLevel(int loglevel); 
};

項目3:主要な非同期メソッドには、結果を通知するためのクロージャーコールバックが伴います

//グッドケース
typedefstd 
:: function <void(int code、string message)> Callback; 
class RtcEngine  
{ 
public:
    //顧客はコールバックで次のようなイベントを時間内に処理できます:UIステータスの変更|プロンプトエラー|
    仮想再試行void Publish(Callback const&callback = nullptr)= 0; 
    virtual void Subscribe(Callback const&callback = nullptr)= 0; 
}; 

//悪いケースの
クラスRtcEngine  
{ 
public:
    class Listener 
    { 
        //コードに従ってエラーイベントを詳細に判断する必要があります。また、API呼び出しによって生成されたエラーと一致しない場合があります
        //多くの種類のエラーがあり、元のロジックから、多くのビジネスパーティはここでいくつかの重要なエラーの処理を無視します
        virtual void OnError(int code、string message)= 0; 
    }; 

    void SetListener(Listener * listener)  
    {
        _listener =リスナー; 
    } 
    
    virtual void Publish()= 0; 
    仮想ボイドSubscribe()= 0; 
    
プライベート:
    リスナー* _listener; 
};

記事4:すべてのインターフェースは「直交」関係を確保しようとします(2つのインターフェース間に競合はありません)

//悪いケース
// EnalbeAudioと他のAPIインターフェースは「直交」ではなく、間違った組み合わせを使用するのは簡単です
// MuteLocalAudioStream(true)とMuteAllRemoteAudioStreams(true)は、ユーザーがEnalbeLocalAudio(true)ファースト
クラスRtcEngine  
{ 
public:
    / / EnalbeLocalAudio + MuteLocalAudioStream + MuteRemoteAudioStream 
    virtual void EnalbeAudio(bool enable)= 0; 
    //ローカルオーディオデバイス(マイクとスピーカー)を
    オンにしますvirtual void EnalbeLocalAudio(bool enable)= 0; 
    //ローカルオーディオストリームを公開/非公開
    virtualvoid MuteLocalAudioStream (bool mute)= 0; 
    //リモートオーディオストリームのサブスク
    ライブ/サブスクライブ解除virtualvoid MuteAllRemoteAudioStreams(bool mute)= 0; 
};

条項5:拡張性を考慮して、抽象オブジェクトにアトミックタイプの代わりに構造を使用するようにしてください

//良いケースの
クラスRtcUser 
{ 
    string userId;
    文字列のメタデータ; 
};

クラスRtcEngineEventListenr  
{ 
public:
    //ユーザーの情報と属性は将来簡単に拡張できます
    virtual void OnUserJoined(const RtcUser&user)= 0; 
}; 

//悪いケースの
クラスRtcEngineEventListenr  
{ 
public:
    //インターフェイスが提供されると、Userオブジェクトの一部の拡張情報と属性を将来追加できなくなり
    ます。virtualvoidOnUserJoined(string userId、stringmetadata)= 0; 
};

条項6:回復不能な終了イベントに明示的なOnExitを使用し、理由を説明する

SDKが提供するOnErrorコールバックイベントに直面すると、エラーが多数発生するため、対処方法や対処方法がわからないことがよくあります。解決策を知らせる明確なドキュメントを用意することをお勧めします。さらに、オブジェクトを破棄してページを終了する必要のあるイベントがSDKで発生した場合、顧客が処理するための独立したコールバック関数を提供することをお勧めします。

列挙ExitReason { 
    EXIT_REASON_FATAL_ERROR、//不明キー例外
    EXIT_REASON_RECONNECT_FAILED、//切断&制限時間の後、自動再接続の数
    、//部屋を閉じたEXIT_REASON_ROOM_CLOSED 
    、EXIT_REASON_KICK_OUT //室から追い出さ
};

クラスRTC EventListenr 
{ publicEngineListenr  
    //一部の警告メッセージが邪魔にならない場合は、
    virtual void OnWarning(int code、const string&message)= 0; 
    // SDKオブジェクトを破棄する必要のあるイベントが発生しました。ページを閉じてください
    virtualvoid OnExit(ExitReason reason、const string&message) = 0; 
};

条項7:PaaS製品のSDKには、ビジネスロジックと情報を含めるべきではありません

//悪いケース
enumClientRole { 
    CLIENT_ROLE_BROADCASTER、//ホスト、ストリームをプッシュまたはプルできます
    CLIENT_ROLE_AUDIENCE //オーディエンス、プッシュできません、ストリームのみをプルできます
}; 

class RtcEngine  
{ 
public:
    //さまざまな役割を紹介するための明確なドキュメントが必要です対応するロールとロール切り替えの結果として生じる動作
    //このAPIは、次のような他のAPIに対して「直交」ではありません
    。Publishvirtualvoid SetClientRole(ClientRole&role)= 0; 
}; 

//良いケース
//例またはを使用することをお勧めしますベストプラクティスでは、複数のSDKのアトミックインターフェイスをカプセル化して、上記のAPI
クラスの役割を実現します。RoleManager 
{ 
public:
    //このようにして、顧客はこのAPIの背後にある一連のアクションを明示的に認識できます。
    void SetClientRole(ClientRole&role)
    { 
        // _engine-> xxxxx1();  
        // _engine-> xxxxx2();
        // _engine-> xxxxx3(); 
    }
    
プライベート:
    RtcEngine * _engine; 
};

第8条:必要なすべてのステータスクエリとイベントコールバックを提供し、ユーザーにステータスをキャッシュさせないでください

//グッドケース
クラスRtcUser 
{ 
    string userId; 
    string metadata; 
    bool audio {false}; //オーディオストリームを開いて公開するかどうかboolvideo { false}; //
    ビデオストリームを開いて公開するかどうかboolscreen 
    {false}; / /画面ストリームを開いて公開するかどうか
}; 

class RtcEngine  
{ 
public:
    // SDKは内部でユーザーステータス(最も正確でリアルタイム)を維持し、
    顧客が自分のコードでステータスをキャッシュできるようにする代わりに//明確なクエリAPIを提供します(両側で状態の不整合の問題が発生するのは簡単です)
    virtual list <RtcUser> GetUsers()= 0; 
    virtual RtcUser GetUsers(const string&userId)= 0; 
};

条項9:パラメーター構成の列挙機能を可能な限り提供し、boolを返して構成結果を通知する

class VideoProfile  
{ 
public:
    //機能の列挙と構成の結果を提供して、構成が実際の状況と矛盾していると顧客が考えないようにします
    bool IsHwEncodeSupported(); 
    bool SetHwEncodeEnabled(bool enabled); 

    //機能の列挙と構成の結果を提供します顧客が実際の状況と矛盾していると考える構成を防ぐために
    intGetSupportedMaxEncodeWidth(); 
    int GetSupportedMaxEncodeHeight(); 
    bool SetMaxEncodeResolution(int width、int height); 
};

第10条:インターフェースファイルの場所と命名スタイルは、特定の規則と関係を維持するものとします。

//良いケース
//コードリポジトリのディレクトリ構造(もちろん、Androidパッケージの顧客のみがディレクトリ構造を認識でき、C ++ライブラリはディレクトリ構造を認識できません)
//すべての外部インターフェイスヘッダーファイルをルートディレクトリに配置することをお勧めします。内部フォルダに隠されたファイルの実現
//合理的なヘッダーファイルの場所の関係は、開発者と顧客がインターフェイスファイルと内部ファイルを正確に認識するのに役立ちます
//すべての外部ヘッダーファイルに内部ファイルを含めることは許可されていませんファイル、そうでない場合は、ヘッダファイルの汚染の問題があるだろう
なRtcXXXXとして、//すべてのインタフェースクラス名が統一されたスタイルで始まり、コールバックがXXXCallbackと呼ばれている、など
SRC 
-base 
-audio 
-Video 
-utils 
-metrics 
-rtc_types.h 
-rtc_engine.h 
-rtc_engine_event_listener.h

概要

これでSDKインターフェイスの設計エクスペリエンスは終了です。誰もが独自のスタイルと好みを持ちます。これは私の個人的な見解と意見の一部です。メッセージを残して話し合うか、lujun.hust @ gmail.comに連絡して連絡するか、フォローしてください。より多くのフォローアップ記事と情報のためのWeChatパブリックアカウント@ Jhuster ~~

おすすめ

転載: blog.51cto.com/ticktick/2598082