web-dev-qa-db-ja.com

ライブラリの可視性を処理する一般的な方法は何ですか?

この質問 プライベートをいつ使用するか、クラスでプロテクトをいつ使用するかについて考えるようになりました。 (この質問は関連しているため、最終的なクラスとメソッドにも拡張します。Javaでプログラミングしていますが、これはすべてのOOP言語)に関連すると思います)

受け入れられた回答は次のとおりです:

大体の目安は、すべてをできるだけプライベートにすることです。

そして別のもの:

  1. すぐにサブクラス化する必要がない限り、すべてのクラスをfinalにします。
  2. サブクラス化してすぐにオーバーライドする必要がない限り、すべてのメソッドをfinalにします。
  3. メソッド本体で変更する必要がない限り、すべてのメソッドパラメーターをfinalにします。

これはかなり単純明快ですが、アプリケーションの代わりにライブラリ(GitHubのオープンソース)を主に作成している場合はどうなりますか?

私は多くの図書館や状況に名前を付けることができました。

  • ライブラリは、開発者が考えたことのない方法で拡張されました
  • 可視性の制約のため、これは「クラスローダーマジック」やその他のハックで行う必要がありました。
  • ライブラリは、それらが構築されていない方法で使用され、必要な機能が「ハッキング」されました
  • 可視性が低下したために変更できない小さな問題(バグ、機能の欠落、「間違った」動作)のため、ライブラリを使用できませんでした
  • 修正できなかった問題により、(プライベートまたは最終的な)単純な関数をオーバーライドすることで解決できる可能性がある、巨大で醜くバグの多い回避策がもたらされました

そして、質問が長くなるまで実際にこれらの名前を付け始め、それらを削除することにしました。

必要以上のコード、必要以上の可視性、必要以上の抽象化を持たないという考えが好きです。そして、これはエンドユーザー向けのアプリケーションを書くときにうまくいくかもしれません。そこではコードはそれを書く人だけが使用します。しかし、コードが他の開発者によって使用されることを意図されている場合、これはどのようにして維持されますか?

大きなオープンソースライブラリは新しいものではないので、オブジェクト指向言語を使用するそのようなプロジェクトで可視性を処理する最も一般的な方法は何ですか?

12
piegames

残念なことに、多くのライブラリはdesignedではなくwrittenを取得します。これは悲しいことです。少し前に考えておけば、将来の多くの問題を防ぐことができるからです。

ライブラリの設計に着手した場合、予想される使用例がいくつかあります。ライブラリはすべてのユースケースを直接満たすわけではありませんが、ソリューションの一部として機能する場合があります。したがって、ライブラリは適応するのに十分な柔軟性が必要です。

制約は、通常、ライブラリのソースコードを取得して、新しいユースケースを処理するように変更することはお勧めできないことです。プロプライエタリライブラリの場合、ソースが利用できない場合があり、オープンソースライブラリの場合、分岐したバージョンを維持することは望ましくない場合があります。非常に具体的な適応を上流のプロジェクトにマージすることは現実的ではないかもしれません。

これがオープンクローズの原則の出番です。ライブラリは、ソースコードを変更せずに拡張に対してオープンにする必要があります。それは自然には起こりません。これは意図的な設計目標でなければなりません。ここで役立つ可能性のあるテクニックはたくさんありますが、古典的なOOP設計パターンはその一部です。一般的に、ユーザーコードがライブラリに安全にプラグインして機能を追加できるフックを指定します。

すべてのメソッドをpublicにするか、すべてのクラスをサブクラス化できるようにするだけでは、拡張性を実現するには不十分です。まず、ユーザーがライブラリにフックできる場所が明確でない場合、ライブラリを拡張することは本当に困難です。例えば。基本クラスのメソッドは暗黙の仮定で記述されているため、ほとんどのメソッドのオーバーライドは安全ではありません。拡張性を考慮して設計する必要があります。

さらに重要なことは、いったんパブリックAPIの一部になったら、元に戻すことはできません。ダウンストリームコードを壊さずにリファクタリングすることはできません。時期尚早の開放性は、ライブラリを次善の設計に制限します。対照的に、内部のものをプライベートにしますが、後で必要になった場合にフックを追加する方が安全なアプローチです。これはライブラリの長期的な進化に取り組むための健全な方法ですが、これはライブラリを使用する必要があるユーザーには不十分です今すぐ

では代わりに何が起こるのでしょうか?ライブラリの現在の状態に大きな問題がある場合、開発者は、時間の経過とともに蓄積された実際のユースケースに関するすべての知識を利用して、ライブラリのバージョン2を作成できます。すごい!それはそれらすべての設計上のバグを修正します!また、予想よりも時間がかかります。また、新しいバージョンが古いバージョンと非常に異なる場合、ユーザーに移行を促すのは難しいかもしれません。その後、互換性のない2つのバージョンを維持します。

15
amon

すべてのパブリックで拡張可能なクラス/メソッドは、サポートする必要があるAPIの一部です。そのセットをライブラリのreasonableサブセットに制限すると、安定性が最も高くなり、問題が発生する可能性のあるものの数が制限されます。それは、合理的にサポートできることに基づいた管理上の決定です(OSSプロジェクトでさえある程度管理されています)。

OSSとクローズドソースの違いは、ほとんどの人がコードを中心にコミュニティを作成して成長させようとしているため、複数の人がライブラリを管理しているということです。とはいえ、利用可能な管理ツールはいくつかあります。

  • メーリングリストでは、ユーザーのニーズと実装方法について議論します
  • 問題追跡システム(JIRAまたはGitの問題など)は、バグと機能リクエストを追跡します
  • バージョン管理はソースコードを管理します。

成熟したプロジェクトでは、次のように表示されます。

  1. もともとは設計されていなかったライブラリで誰かが何かをしたい
  2. 問題追跡にチケットを追加します
  3. チームはメーリングリストまたはコメントで問題について話し合うことができ、要求者は常に議論に参加するよう招待されます
  4. APIの変更が受け入れられ、何らかの理由で優先順位付けまたは拒否されました

その時点で、変更は受け入れられたがユーザーが修正を加速したい場合は、作業を行い、プルリクエストまたはパッチ(バージョン管理ツールに応じて)を送信できます。

静的なAPIはありません。ただし、その成長は何らかの形で形成する必要があります。物事を開く必要があることが明らかになるまですべてを閉じたままにしておくと、バグのあるライブラリや不安定なライブラリの評判を得るのを避けることができます。

8
Berin Loritsch

数人で神経質になってしまったようですので、回答を言い換えさせていただきます。

クラスのプロパティ/メソッドの可視性は、セキュリティやソースの開放性とは関係ありません。

可視性が存在する理由は、オブジェクトが4つの特定の問題に対して脆弱であるためです。

  1. 並行性

カプセル化されていないモジュールをビルドすると、ユーザーはモジュールの状態を直接変更することに慣れます。これはシングルスレッド環境では問題なく機能しますが、スレッドの追加についても考えてみてください。状態をプライベートにして、他のスレッドがリソースで競合するのではなく、リソースを待機するゲッターおよびセッターとともにロック/モニターを使用する必要があります。つまり、プライベート変数には従来の方法ではアクセスできないため、ユーザープログラムは動作しなくなります。これは、多くの書き換えが必要になることを意味します。

真実は、シングルスレッドランタイムを念頭に置いてコーディングする方がはるかに簡単です。プライベートキーワードを使用すると、同期したキーワードまたはいくつかのロックを簡単に追加でき、最初からカプセル化してもユーザーのコードは壊れません。 。

  1. ユーザーがインターフェースの足/流線型の使用で自分自身を撃つことを防ぐのに役立ちます。つまり、オブジェクトの不変条件を制御するのに役立ちます。

すべてのオブジェクトには、一貫した状態になるためにtrueであることが必要なものがたくさんあります。残念ながら、これらのオブジェクトはクライアントの可視空間にあります。これは、各オブジェクトを独自のプロセスに移動し、メッセージを介してそれとやり取りするのはコストがかかるためです。これは、ユーザーが完全な可視性を持っている場合、オブジェクトがプログラム全体をクラッシュすることが非常に簡単であることを意味します。

これは避けられませんが、注意深く作成されたインターフェースを介してユーザーがオブジェクトの状態と対話することのみを許可することで、プログラムをより堅牢にすることで、サービスのインターフェースを誤ってクラッシュさせないようにして、オブジェクトを誤って不整合な状態にすることを防ぐことができます。 。これは、ユーザーが不変条件を意図的に破損できないことを意味しませんが、破損した場合、クライアントがクラッシュするため、プログラムを再起動するだけです(保護するデータはクライアント側に保存しないでください) )。

モジュールの使いやすさを向上できるもう1つの素晴らしい例は、コンストラクターをプライベートにすることです。コンストラクタが例外をスローすると、プログラムが強制終了するためです。これを解決するための怠惰なアプローチの1つは、try/catchブロックにない限り、コンストラクターでコンパイルできないというコンパイル時エラーをスローすることです。コンストラクターをプライベートにして、パブリックの静的なcreateメソッドを追加することで、作成に失敗した場合にcreateメソッドにnullを返すようにしたり、コールバック関数を使用してエラーを処理したりすることができ、プログラムがよりユーザーフレンドリーになります。

  1. スコープ汚染

多くのクラスには多くの状態とメソッドがあり、それらをスクロールしようとすると圧倒されるのは簡単です。これらのメソッドの多くは、ヘルパー関数や状態などの単なる視覚的なノイズです。変数とメソッドをプライベートにすると、スコープの汚染を減らし、ユーザーが探しているサービスを見つけやすくなります。

本質的に、それはあなたがクラスの外側ではなくクラスの内側にヘルパー関数を持つことを手放すことを可能にします。ユーザーが使用してはならない一連のサービスでユーザーの注意をそらさずに可視性を制御しないため、メソッドを一連のヘルパーメソッドに分解することができます(ただし、スコープは汚染されますが、ユーザーのスコープは汚染されません)。

  1. 依存関係に縛られている

巧妙に作成されたインターフェースは、その作業を行うために依存する内部データベース/ウィンドウ/イメージングを非表示にすることができます。別のデータベース/別のウィンドウシステム/別のイメージングライブラリに変更する場合は、インターフェースとユーザーを同じに保つことができます気づかない。

一方、これを行わないと、依存関係が公開され、コードがそれに依存するため、依存関係を変更できなくなる可能性があります。システムが十分に大きいと、移行のコストが手ごろな価格になる可能性がありますが、それをカプセル化することで、動作の良いクライアントユーザーを、依存関係を交換するという将来の決定から保護できます。

0
Dmitry