Java、C#、およびその他の多くの強く型付けされた静的にチェックされる言語では、次のようなコードを作成するのに慣れています。
public void m1() { ... }
protected void m2() { ... }
private void m2() { ... }
void m2() { ... }
一部の動的にチェックされる言語は、特定のクラスメンバーの「プライベート」のレベルを表すキーワードを提供せず、代わりにコーディング規則に依存しています。 Pythonたとえば、プライベートメンバーの前にアンダースコアを付けます。
_m(self): pass
動的にチェックされる言語でこのようなキーワードを指定しても、実行時にのみチェックされるため、ほとんど使用しないと主張できます。
ただし、これらのキーワードを静的にチェックされる言語で提供する十分な理由も見つかりません。 protected
のようなかなり冗長なキーワードをコードに入力する必要があるので、煩わしくて気が散ります。これまでのところ、これらのキーワードが原因で発生したコンパイラエラーによってバグから救われる状況にはありませんでした。まったく逆に、誤って配置したprotected
がライブラリの使用を妨げる状況にありました。
これを念頭に置いて、私の質問は:
クラスの公式インターフェースの一部を定義するために使用されるプログラマ間の規則以上のものを隠す情報はありますか?
クラスの秘密の状態を攻撃から保護するために使用できますか?リフレクションはこのメカニズムをオーバーライドできますか?コンパイラーがenforce情報を非表示にする価値があるのは何ですか?
私はJava認定を取得するために勉強していますが、アクセス修飾子の管理方法に関連しています。それらは理にかなっており、適切に使用する必要があります。
私はpython=を使用してきました。そして、私の学習の旅の間に、Pythonで慣習があると聞いたことがあります。つまり、_m(self): pass
では、アンダースコアはそのフィールドをいじらないように警告します。しかし、誰もがその慣例に従っていますか?私はJavaScriptで作業するので、彼らはしません時々私は問題をチェックする必要があり、その理由はその人が彼がするはずのないことをしていたということでした...
python先頭のアンダースコア)に関するこのディスカッションを読む
クラスの公式インターフェースの一部を定義するために使用されるプログラマ間の規約以上の情報を隠すことはありますか?
私が言ったことから、はい。
クラスの秘密の状態を攻撃から保護するために使用できますか?リフレクションはこのメカニズムをオーバーライドできますか?コンパイラーによって強制されるより正式なメカニズムによって提供される他の利点はありますか?
はい、それはクラスの秘密の状態を保護するために使用でき、それだけでなく、ユーザーがオブジェクトの状態をいじってオブジェクトの本来あるべき動作に変更することを防ぐために使用する必要があります働いています。開発者は、それを計画および検討し、その振る舞いが改ざんされないように、意味のある方法でクラスを設計する必要があります。そして、コンパイラーは、親友として、コードを設計した方法でアクセスポリシーを適用してコードを維持するのに役立ちます。
[〜#〜]編集済み[〜#〜]はい、反映できます。コメントを確認してください
最後の質問は興味深いもので、私はそれに関する回答を読みたいと思っています。
「プライベート」アクセス指定子は、初めて表示したときに生成されるコンパイラエラーに関するものではありません。実際には、約防止プライベートメンバーを保持するクラスの実装が変更されたときに、変更される可能性があるものにアクセスできないようにします。
つまり、まだ機能しているときに使用できないようにするprevents機能していないときに誤って使用し続けることを防ぎます。
デルナンが接頭辞規則の下で述べたように、規則が正しく順守され理解されている限り、変更される可能性のあるメンバーを誤って使用しないようにしてください。悪意のある(または無知の)ユーザーの場合、考えられるすべての結果を伴うそのメンバーへのアクセスを阻止することはできません。アクセス指定子のサポートが組み込まれている言語では、これは無知(コンパイラエラー)では発生せず、悪意のある場合は(親指の構造がプライベートメンバーに到達するための奇妙な)親指のように目立ちます。
「保護された」アクセス指定子は別の話です-これを単に「あまりパブリックではない」または「プライベートによく似ている」と考えないでください。 「保護されている」とは、保護されたメンバーを含むクラスから派生したときに、おそらくwantがその機能を使用することを意味します。保護されたメンバーは、既存のクラス自体を変更せずに既存のクラスの上に機能を追加するために使用する「拡張インターフェース」の一部です。
したがって、簡単に要約すると:
他のユーザーが使用するコードを記述している場合は、情報を非表示にすることで、インターフェースを理解しやすくなります。 「他の誰か」は、チームの別の開発者、商用で作成したAPIを使用する開発者、または「つまらないことがどのように機能するかを思い出せない」将来の自分である可能性があります。 使用可能なメソッドが4つしかないクラスを操作する方が、40を使用しているクラスよりもはるかに簡単です。
背景については、まず class invariants について読むことをお勧めします。
不変条件は、話を短くするために、クラスの存続期間を通して真のままであると想定される、クラスの状態についての仮定です。
本当に簡単なC#の例を使用してみましょう:
public class EmailAlert
{
private readonly List<string> addresses = new List<string>();
public void AddRecipient(string address)
{
if (!string.IsNullOrEmpty(address))
addresses.Add(address);
}
public void Send(string message)
{
foreach (string address in addresses)
SendTo(address, message);
}
// Details of SendTo not shown
}
何が起きてる?
addresses
は、クラスの構築時に初期化されます。readonly
も作成したので、insideから何も構築後にタッチできません(これは常に正しい/必要なわけではありません) 、しかしそれはここで便利です)。Send
メソッドは、addresses
がnull
になることはないと仮定できます。値を変更できる方法がないため、このチェックを実行する必要はありません。他のクラスがaddresses
フィールドへの書き込みを許可された場合(つまり、public
であった場合)、この仮定は無効になります。 そのフィールドに依存するクラスの他のすべてのメソッドは、明示的なnullチェックを開始する必要があります。そうしないと、プログラムがクラッシュするおそれがあります。
だから、はい、それは「慣習」以上のものです。クラスメンバーのすべてのアクセス修飾子は、その状態をいつどのように変更できるかについて、一連の仮定を集合的に形成します。その後、これらの前提条件は、依存関係にあるメンバーと相互依存関係にあるメンバーとクラスに組み込まれるため、プログラマーはプログラムの状態全体を同時に推論する必要がありません。仮定を立てることは、ソフトウェアの複雑さを管理する上で重要な要素です。
これらの他の質問:
クラスの秘密の状態を攻撃から保護するために使用できますか?
はいといいえ。ほとんどのコードと同様に、セキュリティコードは特定の不変条件に依存します。アクセス修飾子は、trustedの呼び出し元への道標として役立つはずです。 悪意のあるコードは気にしませんが、悪意のあるコードもコンパイラを通過する必要はありません。
リフレクションはこのメカニズムをオーバーライドできますか?
もちろんできます。ただし、リフレクションでは、呼び出し元のコードにその特権/信頼レベルが必要です。完全な信頼や管理者権限で悪意のあるコードを実行している場合、その戦いはすでに終わっています。
コンパイラが情報の非表示を強制する価値があるのは何でしょうか?
コンパイラーはすでに実行します。 .NET、Java、およびその他のそのような環境でのランタイムも同様です。メソッドがプライベートの場合、メソッドの呼び出しに使用されるオペコードは成功しません。その制限を回避する唯一の方法は、信頼された/昇格されたコードを必要とし、昇格されたコードは常にプログラムのメモリに直接書き込むだけです。これは、カスタムオペレーティングシステムを必要とせずに強制することができます(= /// =)。
情報の隠蔽は単なる慣例ではありません。それを回避しようとすると、多くの場合、実際にクラスの機能が破壊される可能性があります。たとえば、private
変数に値を格納し、同じ時間のprotected
またはpublic
プロパティを使用して値を公開し、ゲッターでチェックすることはかなり一般的な方法ですnullの場合、必要な初期化を行います(遅延読み込みなど)。または、何かをprivate
変数に格納し、プロパティを使用して公開し、セッターで値が変更されたかどうかを確認して、PropertyChanging
/PropertyChanged
イベントを発生させます。しかし、内部の実装を見ないと、裏で起こっていることすべてを知ることはできません。
情報の隠蔽は、トップダウンの設計哲学から進化しました。 Pythonはボトムアップ言語と呼ばれています 。
情報の非表示は、Java、C++、およびC#のクラスレベルで適切に適用されるため、このレベルの規則ではありません。パブリックインターフェイスと非表示の(プライベート)詳細を使用して、クラスを「ブラックボックス」にするのは非常に簡単です。
あなたが指摘したように、Pythonでは、すべてが表示されているため、非表示にするものを使用しないという規則に従うのはプログラマの責任です。
Java、C++、またはC#を使用する場合でも、ある時点で情報を非表示にするが規約になります。より複雑なソフトウェアアーキテクチャに関連する抽象化の最高レベルでのアクセス制御はありません。たとえば、Javaでは、 "。internal。" パッケージ名の使用を見つけることができます。これは、この種の情報を簡単に理解できないため、純粋に命名規則ですパッケージのアクセシビリティのみを介して適用される非表示。
アクセスを正式に定義しようとする言語の1つにエッフェルがあります。 この記事 は、Javaなどの言語のその他の情報を隠す弱点を指摘しています。
背景:情報の隠蔽は 1971年にDavid Parnasによって提案されました 。彼はその記事で、他のモジュールに関する情報を使用すると、「システム構造の接続性を壊滅的に増やす可能性がある」と指摘しています。このアイデアによれば、情報の隠蔽の欠如は、維持が難しい密結合システムにつながる可能性があります。彼は続けます:
プログラマーが情報を誤って使用するのではなく、プログラミングを開始する前に設計者がシステムの構造を明示的に決定したいと考えています。
RubyとPHPにはそれがあり、実行時にそれを強制します。
情報隠蔽のポイントは、実際には情報提示です。内部の詳細を「隠す」ことにより、目的は外部の視点から明らかになります。これを採用する言語があります。 Javaでは、デフォルトで内部パッケージにアクセスし、haXeでは保護されています。それらを公開するために明示的にパブリックに宣言します。
これの要点は、非常に一貫性のあるインターフェースのみを公開することにより、クラスを使いやすくすることです。あなたは残りを保護したいので、利口な人があなたの内部状態を邪魔してあなたのクラスをだまして彼が望んでいることをさせないようにします。
また、アクセス修飾子が実行時に適用される場合、canを使用して特定のレベルのセキュリティを適用しますが、これは特に良い解決策ではないと思います。
アクセス修飾子は、慣例ではできないことを間違いなく実行できます。
たとえば、Javaでは、リフレクションを使用しない限り、プライベートメンバー/フィールドにアクセスできません。
したがって、プラグインのインターフェイスを記述し、リフレクションを介してプライベートフィールドを変更する権限を正しく拒否する(およびセキュリティマネージャーを設定する:))場合、誰かが実装した関数にオブジェクトを送信して、そのプライベートフィールドにアクセスできないことを知ることができます。
もちろん、これを克服するためのいくつかのセキュリティバグが存在する可能性がありますが、これは哲学的に重要ではありません(実際には間違いなく重要です)。
ユーザーが自分の環境で私のインターフェイスを実行する場合、ユーザーは制御権を持っているため、アクセス修飾子を回避できます。
これらのキーワードによって引き起こされたコンパイラー・エラーがバグから私を救ったであろう状況に私はありませんでした。
アプリケーションの作成者をバグから救うことは、ライブラリの作成者が実装のどの部分を維持することを約束しているのかを判断させるほどではありません。
図書館がある場合
class C {
public void foo() { ... }
private void fooHelper() { /* lots of complex code */ }
}
foo
の実装を置き換えて、fooHelper
を根本的に変更できるようにしたい場合があります。ドキュメントにあるすべての警告にもかかわらず、多くの人がfooHelper
を使用することに決めた場合、私はそれを行うことができない可能性があります。
private
を使用すると、ライブラリ作成者はライブラリを管理可能なサイズのメソッド(およびprivate
ヘルパークラス)に分割し、内部の詳細を何年も維持することを強いられる心配はありません。
コンパイラが情報の非表示を強制する価値があるのは何でしょうか?
余談ですが、Java private
はコンパイラーによって強制されるのではなく、Java bytecode verifier によって強制されます。
リフレクションはこのメカニズムをオーバーライドできますか?コンパイラが情報の非表示を強制する価値があるのは何でしょうか?
Javaでは、リフレクションだけでこのメカニズムをオーバーライドできません。 Javaにはprivate
の2種類があります。ある外部クラスが別の外部クラスのprivate
メンバーにアクセスできないようにするprivate
の一種で、バイトコードベリファイアによってチェックされますが、内部クラスがパッケージプライベートの合成アクセサーメソッドを介して使用するprivate
sも
public class C {
private int i = 42;
public class B {
public void incr() { ++i; }
}
}
クラスB
(実際の名前はC$B
)がi
を使用するため、コンパイラーは、B
がバイトコード検証を通過するようにC.i
にアクセスできるようにする合成アクセサーメソッドを作成します。残念ながら、ClassLoader
を使用するとbyte[]
からクラスを作成できるため、C
のjarファイルが存在しない場合に可能であるC
のパッケージに新しいクラスを作成することにより、C
が内部クラスに公開しているプライベートを取得するのはかなり簡単です。封印。
適切なprivate
の実施には、クラスローダー、バイトコード検証、およびプライベートへのリフレクトアクセスを防止できるセキュリティポリシー間の調整が必要です。
クラスの秘密の状態を攻撃から保護するために使用できますか?
はい。 「安全な分解」は、プログラマーがそれぞれのモジュールのセキュリティプロパティを維持しながら共同作業できる場合に可能です。モジュールのセキュリティプロパティに違反しないように、別のコードモジュールの作成者を信頼する必要はありません。
Object Capabilities languages like Joe-E 情報を隠すなどの手段を使用して、安全な分解を可能にします。
Joe-Eは、Javaプログラミング言語のサブセットであり、オブジェクト機能の規律に従って安全なプログラミングをサポートするように設計されています。Joe-Eは、安全なシステムの構築とセキュリティの促進を目的としています。 Joe-Eで構築されたシステムのレビュー。
そのページからリンクされた論文は、private
の実施により安全な分解が可能になる例を示しています。
安全なカプセル化を提供します。
図1.追加専用のロギング機能。
public final class Log { private final StringBuilder content; public Log() { content = new StringBuilder(); } public void write(String s) { content.append(s); } }
追加専用のログ機能を構築する方法を示す図1を検討してください。プログラムの残りの部分がJoe-Eで記述されている場合、コードレビュー担当者は、ログエントリを追加することしかできず、変更または削除することはできません。
Log
クラスの検査のみが必要であり、他のコードのレビューを必要としないため、このレビューは実用的です。したがって、このプロパティを確認するには、ログコードに関するローカルな推論のみが必要です。
情報の隠蔽は、優れたソフトウェア設計の主要な関心事の1つです。 70年代後半のDave Parnasの論文をご覧ください。基本的に、モジュールの内部状態が一貫していることを保証できない場合、その動作については何も保証できません。そして、その内部状態を保証できる唯一の方法は、それを非公開にして、提供された手段によってのみ変更できるようにすることです。