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


12

クラスでprivateを使用するタイミングとprotectedを使用するタイミングに関するこの質問に、私は考えました。(この質問は関連しているため、最終クラスとメソッドにも拡張します。Javaでプログラミングしていますが、これはすべてのOOP言語に関連すると思います)

受け入れられた答えは次のとおりです。

良い経験則は、すべてをできるだけプライベートにすることです。

そしてもう一つ:

  1. すぐにサブクラス化する必要がない限り、すべてのクラスを最終クラスにします。
  2. サブクラス化してすぐにオーバーライドする必要がない限り、すべてのメソッドをfinalにしてください。
  3. メソッドの本体内で変更する必要がある場合を除き、すべてのメソッドパラメーターを最終的なものにします。

これは非常に単純明快ですが、アプリケーションではなく主にライブラリ(GitHubのオープンソース)を書いている場合はどうでしょうか。

多くのライブラリとシチュエーションに名前を付けることができます。

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

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

必要以上のコード、必要以上の可視性、必要以上の抽象化を持たないというアイデアが好きです。そして、これはエンドユーザー向けのアプリケーションを書くときに機能するかもしれません。その場合、コードはそれを書く人だけが使用します。しかし、コードが他の開発者によって使用されることを意図している場合、これはどのように持続しますか?元の開発者があらゆる可能なユースケースを事前に考え、変更/リファクタリングが困難/不可能である可能性は低いですか?

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



オープンソースについて尋ねると、リストされた問題に対処するために適切なコーディング原則を曲げることは、クローズドソースよりも意味がありません。単に、必要な修正をライブラリコードに直接提供するか、フォークして独自のバージョンを作成できるからです彼らが望むどんな修正
gnat

2
私のポイントはこれについてではなく、この文脈では意味をなさないオープンソースへの言及についてです。実用的なニーズが、場合によっては厳格な原則からの逸脱を正当化する可能性があることを想像できます(技術債務の発生とも呼ばれます)が、この観点からは、コードがクローズドかオープンソースかは関係ありません。より正確には反対の方向に重要それがこれらに対処するために追加のオプションを提供していますので、コードされたオープンソースを閉じたよりも、これらのニーズが少ない押すことができますすることができますので、あなたがここに想像のものより
ブヨ

1
@piegames:私はここでgnatに完全に同意します。あなたが選択した問題は、クローズドソースライブラリで発生する可能性がはるかに高くなります-それが許容ライセンスを持つOSライブラリである場合、メンテナーが変更要求を無視すると、libをフォークして必要に応じて、自分で可視性を変更します。
Doc Brown

1
@piegames:あなたの質問がわかりません。「Java」は言語であり、ライブラリではありません。また、「あなたの小さなオープンソースライブラリ」の可視性が厳しすぎる場合、可視性を後で拡張しても、後方互換性が損なわれることはありません。逆方向のみ。
Doc Brown

回答:


15

残念なことに、多くのライブラリは設計ではなく記述されています。少し前もって考えておくと、将来の多くの問題を防ぐことができるため、これは悲しいことです。

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

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

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

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

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

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


そのため、拡張機能にフックを追加する必要があります。パブリック/オーバーライド可能にするだけでは不十分だからです。また、下位互換性のため、変更/新しいAPIをいつリリースするかについて考える必要もあります。しかし、特別なメソッドの可視性はどうですか?
piegames

@piegames可視性を使用して、どの部分を公開するか(安定したAPIの一部)、どの部分をプライベートにするか(変更される可能性があります)を決定します。誰かがリフレクションでそれを回避した場合、将来その機能が壊れるのは彼らの問題です。ところで、拡張ポイントは多くの場合、オーバーライドできるメソッドの形式になっています。ただし、オーバーライドできるメソッドと、オーバーライドすることを意図したメソッドには違いがあります(テンプレートメソッドパターンも参照)。
アモン

8

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

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

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

成熟したプロジェクトでは、これらのラインに沿ったものが表示されます。

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

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

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


1
完全に同意し、オープンソースのライブラリだけでなく、サードパーティのクローズドソースのライブラリに対しても、あなたが選択した変更要求プロセスを成功裏に実践しました。
Doc Brown

私の経験では、小さなライブラリの小さな変更でさえ(他の人を納得させるためであっても)多くの作業であり、しばらく時間がかかるかもしれません(それまでスナップショットを使用する余裕がない場合は次のリリースを待つために)。したがって、これは明らかに私にとって選択肢ではありません。しかし、私は興味があります:そのコンセプトを実際に使用する(GitHubの)より大きなライブラリはありますか?
piegames

それは常に多くの仕事です。私が貢献したほとんどすべてのプロジェクトには、それに類似したプロセスがあります。私がApacheの時代に戻ったとき、私たちは作成したものに情熱を傾けていたので、何日も何かを議論するかもしれません。多くの人がライブラリを使用することを知っていたので、提案された変更がAPIを破壊するかどうか、提案された機能に価値があるかなどのことを議論する必要がありました。等
ベリン・ロリッチ

0

それは少数の人々に神経を打ったように見えるので、私は私の応答を言い換えます。

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

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

  1. 並行性

カプセル化されていないモジュールをビルドすると、ユーザーはモジュールの状態を直接変更することに慣れます。これはシングルスレッド環境では正常に機能しますが、スレッドの追加を検討した場合でも、状態を非公開にし、ロック/モニターを、他のスレッドがリソース上で競合するのではなく、リソースを待機させるゲッターおよびセッターと共に使用することを強制されます。これは、プライベート変数に従来の方法ではアクセスできないため、ユーザープログラムが機能しなくなることを意味します。これは、多くの書き直しが必要になることを意味します。

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

  1. ユーザーがインターフェースを足で/流線で使用するのを避けてください。本質的には、オブジェクトの不変式を制御するのに役立ちます。

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

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

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

  1. スコープ汚染

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

本質的には、クラス外ではなくクラス内でヘルパー関数を使用することで回避できます。ユーザーが決して使用してはならないサービスの束でユーザーの注意をそらすことなく、可視性の制御を行わないため、メソッドをヘルパーメソッドの束に分解して逃げることができます(ただし、ユーザーのスコープではなく、スコープを汚染します)。

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

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

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


1
「何かを隠すことは意味がありません」-なぜカプセル化について考えるのでしょうか?多くのコンテキストでは、リフレクションには特別な特権が必要です。
フランクヒルマン

カプセル化について考えるのは、モジュールの開発中にスペースを確保し、誤用の可能性を減らすためです。たとえば、クラスの内部状態を直接変更する4つのスレッドがある場合、簡単に問題が発生しますが、変数をプライベートにすると、パブリックメソッドを使用して世界の状態を操作するようにユーザーに促し、モニター/ロックを使用して問題を防ぐことができます。これがカプセル化の唯一の本当の利点です。
ドミトリー

セキュリティのために物事を隠すことは、APIに穴を開けなければならないようなデザインを作成する簡単な方法です。これの良い例は、多くのツールボックスと、サブウィンドウを持つ多くのウィンドウがあるマルチドキュメントアプリケーションです。カプセル化に夢中になると、1つのドキュメントに何かを描画する状況になってしまいます。内部ドキュメントに何かを描画するように要求するように内部ドキュメントに要求するようウィンドウに要求する必要がありますコンテキストを無効にします。クライアント側がクライアント側で遊びたい場合、それらを防ぐことはできません。
ドミトリー

環境がそれをサポートしている場合、アクセス制御を介してセキュリティを実現できますが、OO言語設計の当初の目標の1つでしたが、より理にかなっています。また、カプセル化を促進し、同時に使用しないでください。少しわかりにくい。
フランクヒルマン

私はそれを使わないつもりはなかった。セキュリティのために使用しないでください。ユーザーのエクスペリエンスを改善し、よりスムーズな開発環境を提供するために戦略的に使用します。私のポイントは、セキュリティやソースのオープン性とは何の関係もないということです。定義上、クライアント側オブジェクトはイントロスペクションに対して脆弱であり、ユーザープロセス空間からオブジェクトを移動すると、カプセル化されていないものもカプセル化されたものと同様にアクセスできなくなります。
ドミトリー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.