情報の隠蔽は慣例以上のものですか?


20

Java、C#、およびその他の多くの厳密に型指定された静的にチェックされた言語では、次のようなコードを記述するために使用されます。

public void m1() { ... }
protected void m2() { ... }
private void m2() { ... }
void m2() { ... }

動的にチェックされる言語の中には、特定のクラスメンバーの「プライベート」レベルを表すキーワードを提供せず、代わりにコーディング規約に依存しているものがあります。たとえば、Pythonはプライベートメンバーの前にアンダースコアを付けます:

_m(self): pass

動的にチェックされる言語でこのようなキーワードを提供することは、実行時にのみチェックされるため、ほとんど使用されないと主張できます。

ただし、これらのキーワードを静的にチェックされた言語で提供する正当な理由も見つかりません。protected迷惑で気を散らすようなかなり冗長なキーワードでコードを埋める必要があると思います。これまでのところ、これらのキーワードによって引き起こされるコンパイラエラーがバグから私を救ったという状況にはありませんでした。まったく逆に、間違って配置されprotectedたためにライブラリを使用できない場合がありました。

これを念頭に置いて、私の質問は次のとおりです。

クラスの公式インターフェースの一部を定義するために使用されるプログラマー間の慣習以上の情報が隠されていますか?

クラスの秘密状態を攻撃から保護するために使用できますか?反射はこのメカニズムをオーバーライドできますか?コンパイラが情報の隠蔽を強制することが価値があるのは何ですか?


10
申し訳ありませんが、エリック
リッパー

あなたの質問を理解するのは少し難しいです。基本的に、誰かが外部からプライベートメンバーにアクセスできるかどうかを尋ねていますか?
モーガンハーロッカー


@ironcode:どういうわけか、いくつかの答えから自分自身を十分に明確に表明していないと言えます。何が具体的に理解を難しくしているのかわかりますか?明確にしようとします。
-blubb

2
「コンパイル時に情報を提供し、実行時にのみ情報をチェックするのはナンセンスです」-本当に?これは、現在の.NETフレームワークの重要な部分が静的型付けの選択的​​なオプトインまたはオプトアウトに基づいているためです。これは本当にあなたが言っていることだとは思わない。この質問は「静的」チェックと「動的」チェックについて繰り返し語っていますが、これらは型システムの特徴です。ここで実際に問題になっているのは、コンパイル時実行時のバインディングです。
アーロンノート

回答:


4

私はJava認証について勉強していますが、そのほとんどはアクセス修飾子の管理方法に関するものです。そして、それらは理にかなっており、適切に使用されるべきです。

私はpythonを使用してきましたが、学習の旅でpythonの慣習があると聞いたことがあります。とは_m(self): passいえ、アンダースコアでは、そのフィールドに干渉しないように警告されます。しかし、誰もがその慣習に従うでしょうか?私はjavascript を使用していますが、そうではないと言わなければなりません。時々私は問題をチェックする必要があり、その理由はその人が彼がするはずのないことをしているということでした...

Pythonの主要なアンダースコアに関するこの議論を読んでください

クラスの公式インターフェースの一部を定義するために使用されるプログラマー間の慣習以上の情報が隠されていますか?

私が言ったことから、はい。

クラスの秘密状態を攻撃から保護するために使用できますか?反射はこのメカニズムをオーバーライドできますか?コンパイラによって実施されるより正式なメカニズムによって提供される他の利点はありますか?

はい、それはクラスの秘密の状態を保護するために使用できます。そのために使用するだけでなく、ユーザーがオブジェクトの状態を操作することを防ぐために、オブジェクトの状態を操作する必要があります働いています。開発者は、その動作が改ざんされないように、意味のある方法でクラスを計画および検討し、クラスを設計する必要があります。そしてコンパイラーは、親友として、アクセスポリシーを実施するように設計された方法でコードを維持するのに役立ちます。

編集済み はい、リフレクションでコメントを確認できます

最後の質問は興味深い質問であり、私はそれに関する答えを喜んで読みます。


3
通常、リフレクションはアクセス保護をバイパスできます。JavaとC#の場合、どちらもプライベートデータにアクセスできます。
マークH

ありがとう!ここでの答え:stackoverflow.com/questions/1565734/…説明してください!
wleao

1
Javascriptカルチャは、Pythonカルチャとは大きく異なります。Pythonにはpep-08があり、ほとんどすべてのPython開発者はこれらの規則に従います。PEP-08違反は、多くの場合バグと見なされます。ですから、javascript / php / perl / ruby​​の経験は、この点でPythonの経験にほとんど変換されないと思います。_privateは、Pythonで非常にうまく機能します。
マイクコロボフ

1
一部の(すべてではない)プログラミング言語では、情報の非表示を使用して、悪意のあるコードから保護できます。情報の隠蔽は、悪意のあるユーザーに対する保護には使用できません。
ブライアン

2
@Mike PEP-08違反が非常に頻繁に「バグと見なされ」、Python開発者が規約に従わないことを夢見ていない場合、言語によってそれらを強制することもできます。言語が自動的にそれを行うことができるのに、なぜメニアルな仕事をするのですか
アンドレスF.

27

「プライベート」アクセス指定子は、最初に表示されるときに生成されるコンパイラエラーに関するものではありません。現実にはそれはについてです防止、まだクラスの実装は、プライベートメンバの変更を保持したときに変更されることがあり、何かにアクセスできます。

言い換えれば、まだ機能しているときに使用を許可しない 誤って使用することを防ぎます。

Delnanが以下に述べたように、接頭辞規則は、規則が正しく理解され理解されている限り、変更される可能性のあるメンバーの誤った使用を思いとどまらせる。悪意のある(または無知な)ユーザーの場合、考えられるすべての結果でそのメンバーにアクセスするのを止めることはありません。アクセス指定子の組み込みサポートを備えた言語では、これは無知では発生せず(コンパイラエラー)、悪意のあるときはひどい親指のように目立ちます(プライベートメンバーに到達するための奇妙な構造)。

「保護された」アクセス指定子は別の話です-これを単に「まったく公開されていない」または「非常にプライベートな」と考えないでください。「保護」とは、おそらくあなたが望むことを意味しますあなたが保護されたメンバーを含むクラスから派生したときにその機能を使用します。保護されたメンバーは、既存のクラス自体を変更せずに、既存のクラスの上に機能を追加するために使用する「拡張インターフェイス」の一部です。

だから、短い要約:

  • public:クラスのインスタンスで安全に使用します。クラスの目的は変わりません。
  • protected:クラスを拡張(派生)するときに使用します-実装を大幅に変更する必要がある場合は変更される可能性があります。
  • プライベート:触れないでください!予想されるインターフェースのより良い実装を提供するために、意のままに変更される場合があります。

3
さらに重要なことは、「実装の詳細、変更される可能性がある」ための慣行が強制されていない(たとえば、アンダースコアなどの場合に容易に認識できる)ことも、プライベートメンバーの偶発的な使用を回避することです。たとえば、Pythonでは、正気のプログラマーが外部からプライベートメンバーを使用してコードを書くのは(それでも眉をひそめている)ときは、やむを得ず、自分が何をしているのかを正確に知り、将来のバージョンでの潜在的な破損を受け入れたときだけです。つまり、たとえばJavaプログラマーがリフレクションを使用する状況です。

3
@Simon Stelling-いいえ、私は何かをプライベートにしたクラスの元の実装者が「これを使用しないでください、ここでの動作は将来変更される可能性があります」と言っています。たとえば、2つのポイント間の距離が最初に含まれるクラスのプライベートメンバー変数には、パフォーマンス上の理由から2つのポイント間の距離が2乗されることがあります。古い振る舞いに依存していたものは何でも静かに壊れます。
ジョリスティマーマンズ

1
@MadKeithV:それでは、言語レベルのキーワードが「…に触れないでください」と言っているのは、同じことを意味する慣習とどう違うのですか?コンパイラーのみがそれを強制しますが、それから私たちはあなたがたの古い静的と動的の議論に戻ります。

7
@Simon Stelling(およびDelnan)-私にとって、それは「エンジンリミッターは速度制限標識とどう違うのか」と尋ねるようなものです。
ジョリスティマーマンズ

1
私は違いをよく知っており、違いがないとは言いませんでした。あなたの答えとあなたのフォローアップコメントのほとんどは、規約言語レベルのキーワードで行うことができるメンバーとメソッドへの注釈の種類を説明するだけで、実際の違い(言語レベルの強制の存在)は読者によって推測されます。

11

他の人が使用するコードを作成している場合、情報の非表示により、理解しやすいインターフェイスを提供できます。「他の誰か」は、あなたのチームの別の開発者、開発者が商業的に作成したAPIを消費するもの、あるいは「ちょっとしたことのしくみを思い出せない」将来の自分でさえあります。メソッドが4つしかないクラスを使用する方が、40があるメソッドよりも簡単です。


2
私は完全に同意しますが、これは私の質問に答えません。クラスを使用する_memberprivate member、クラスで使用するかは、他のプログラマーがpublic接頭辞のないメンバーだけを見るべきだと理解している限り、ごくわずかです。
-blubb

6
はい。ただし、動的言語で記述されたクラスの公開ドキュメントでも、これらの36個のプライベートパーツがスローされません(少なくとも既定ではありません)。

3
@Simon「単なる慣習」であるという声明は、本当の利点があるため誤解を招きます。「ただの慣習」は、次の行の中括弧のようなものです。利点のある慣習は、意味のある名前を使用するようなものです。何かをプライベートに設定すると、誰かがあなたに「秘密の方法」を見ることを止めますか?いいえ、決まっていない場合。
モーガンハーロッカー

1
@ Simon-また、安全に使用できるメソッドを公開するだけで、クラスを使用しようとしてもユーザーが何かを壊さないという可能性が高まります。千回呼び出された場合、ウェブサーバーをクラッシュさせる可能性のあるメソッドが簡単にあります。APIのコンシューマーがそれをできるようにしたくありません。抽象化は単なる慣習ではありません。
モーガンハーロッカー

4

背景については、クラス不変式について読むことから始めることをお勧めします。

不変式とは、長い話を短くするために、クラスの生涯を通じて真実であると想定されるクラスの状態に関する仮定です。

本当に簡単な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、構築後に内部から何も触れることはできません(これは常に正しい/必要なわけではありませんが、ここでは役立ちます)。
  • したがって、このSendメソッドaddressesは、決してそうなることはないという仮定を立てることができますnull。値を変更する方法ないため、そのチェックを実行する必要はありません

他のクラスがaddressesフィールドへの書き込みを許可された場合(つまり、そうであった場合public)、この仮定は無効になります。 そのフィールドに依存するクラス内の他のすべてのメソッドは、明示的なnullチェックの実行を開始するか、プログラムがクラッシュするリスクがあります。

はい、それは「慣習」以上のものです。クラスメンバーのすべてのアクセス修飾子は、その状態をいつどのように変更できるかに関する一連の仮定をまとめて形成します。これらの前提は、プログラマーがプログラムの状態全体について同時に推論する必要がないように、依存および相互依存のメンバーとクラスにその後組み込まれます。仮定を立てる能力は、ソフトウェアの複雑さを管理する重要な要素です。

これらの他の質問へ:

クラスの秘密状態を攻撃から保護するために使用できますか?

はいといいえ。セキュリティコードは、ほとんどのコードと同様に、特定の不変式に依存します。アクセス修飾子は、それを台無しにすべきではない信頼できる呼び出し元への道しるべとして確かに役立ちます。 悪意のあるコードは気にしませんが、悪意のあるコードはコンパイラを通過する必要はありません。

反射はこのメカニズムをオーバーライドできますか?

もちろんできます。ただし、リフレクションでは、呼び出し元のコードにその特権/信頼レベルが必要です。完全な信頼および/または管理者権限で悪意のあるコードを実行している場合、すでにその戦いに負けています。

コンパイラが情報の隠蔽を強制することが価値があるのは何ですか?

コンパイラはすでにありません、それを強制します。.NET、Java、および他のそのような環境のランタイムも同様です。メソッドがプライベートの場合、メソッドの呼び出しに使用されるオペコードは成功しません。その制限を回避する唯一の方法は、信頼できる/昇格されたコードを必要とし、昇格されたコードは常にプログラムのメモリに直接書き込むことができます。カスタムオペレーティングシステムを必要とせずに実施できる限り実施されます。


1
@サイモン:ここで「無知」を明確にする必要があります。メソッドにプレフィックスを付ける_と、契約が記載されますが、強制されません。それは契約について無知であることを難しくするかもしれませんが、特に退屈で、しばしばReflectionのような制限的な方法と比較したとき、契約を破ることを難しくしません。規則では、「あなたが、言うべきではない、これを破ります」。アクセス修飾子は、「これを破ることはできません」と言います。あるアプローチが他のアプローチより優れていると言うつもりはありません -どちらもあなたの哲学次第ですが、それでも非常に異なるアプローチです。
アーロンノート

2
アナロジーはどうですか:A private/ protectedアクセス修飾子はコンドームのようなものです。使用する必要はありませんが、使用する予定がない場合は、タイミングとパートナーを確認することをお勧めします。悪意のある行為を防ぐことはできず、毎回機能しないこともありますが、不注意によるリスクを確実に低減し、「注意してください」と言うよりもはるかに効果的です。ああ、もしあなたが持っていてもそれを使わないなら、私からの同情を期待しないでください。
アーロンノート

3

情報の隠蔽は単なる慣習ではありません。それを回避しようとすると、多くの場合、クラスの機能を実際に破壊する可能性があります。たとえば、private変数に値を保存し、同時にprotectedor publicプロパティを使用して値を公開し、ゲッターでnullを確認し、必要な初期化(つまり、遅延読み込み)を行うのは非常に一般的な方法です。またはINストア何かprivate変数は、それがプロパティを使用して、そしてセッターで、値が変更かどうかをチェックし、火災公開PropertyChanging/ PropertyChangedイベント。しかし、内部実装を見ることなく、舞台裏で起こっていることすべてを知ることはできません。


3

情報の隠蔽は、トップダウンの設計哲学から発展しました。Pythonはボトムアップ言語と呼ばれています

情報の隠蔽は、Java、C ++、およびC#のクラスレベルで適切に実施されるため、このレベルでは実際には慣習ではありません。クラスを「ブラックボックス」にするのは非常に簡単で、パブリックインターフェイスと非表示(プライベート)の詳細があります。

あなたが指摘したように、Pythonではすべてが見えるため、隠されることを意図したものを使用しないという慣習に従うのはプログラマー次第です。

Java、C ++、またはC#であっても、ある時点で情報の隠蔽慣例になります。より複雑なソフトウェアアーキテクチャに関与する抽象化の最高レベルでのアクセス制御はありません。たとえば、Javaでは「.internal」の使用を見つけることができますパッケージ名。これは、この種の情報隠蔽をパッケージのアクセシビリティだけで強制することは容易ではないため、純粋に命名規則です。

アクセスを正式に定義しようとする言語の1つがエッフェルです。この記事では、Javaなどの言語のその他の情報隠蔽の弱点をいくつか指摘します。

背景:情報隠蔽は1971年にDavid Parnasによって提案されました。彼はその記事で、他のモジュールに関する情報の使用が「システム構造の接続性を悲惨に高める」ことができると指摘しています。この考えによれば、情報隠蔽の欠如は、保守が難しい密結合システムにつながる可能性があります。彼は続けます:

プログラマーが情報を誤って使用するのではなく、プログラミングを開始する前に、設計者がシステムの構造を明示的に決定したいと考えています。


1
「プログラマーによる情報の使用」という引用の+1、それだけです(最後に余分なコンマがありますが)。
jmoreno

2

RubyとPHPにはそれがあり、実行時に適用されます。

情報隠蔽のポイントは、実際に情報を表示することです。内部の詳細を「隠す」ことにより、外部の観点から目的が明らかになります。これを受け入れる言語があります。Javaでは、デフォルトでアクセスはパッケージ内部に、haXeでは保護されます。それらを公開するには、それらを明示的に宣言します。

これの全体的なポイントは、非常に一貫性のあるインターフェイスのみを公開することにより、クラスを使いやすくすることです。賢明な人が来て、あなたの内部状態をいじってクラスをだまして彼のやりたいことをさせないように、残りを保護したい。

また、実行時にアクセス修飾子が適用される場合、それらを使用して特定のレベルのセキュリティを適用できますが、これは特に良いソリューションではないと思います。


1
あなたが働いている同じ会社であなたのライブラリが使用されているなら、あなたは気になります...彼が彼のアプリをクラッシュさせるならば、あなたはそれを修正するのに苦労するでしょう。
wleao

1
なぜ安全ベルトを着用できるふりをするのですか?すべての情報が利用可能な場合、コンパイラーが強制しない理由はありますか?その安全ベルトを着用する価値があるかどうかを調べるためにクラッシュするまで待ちたくない。
マークH

1
@Simon:なぜ人々が使いたくないものでインターフェースを乱雑にしているのか。C ++では、すべてのメンバーがヘッダーファイルのクラス定義に存在する必要があることを嫌います(理由はわかります)。インターフェイスは他の人のためのものであり、使用できないものを散らかしてはいけません。
phkahler

1
@phkahler: pydoc_prefixedMembersインターフェイスから削除します。雑然としたインターフェイスは、コンパイラがチェックするキーワードの選択とは関係ありません。
-blubb

2

アクセス修飾子は間違いなく、慣例ではできないことを実行できます。

たとえば、Javaでは、リフレクションを使用しない限り、プライベートメンバー/フィールドにアクセスできません。

したがって、プラグインのインターフェイスを作成し、リフレクションによってプライベートフィールドを変更する権限を正しく拒否した場合(およびセキュリティマネージャーを設定する場合:))、誰でも実装された関数にオブジェクトを送信し、プライベートフィールドにアクセスできないことを知ることができます。

もちろん、彼がこれを克服することを可能にするいくつかのセキュリティバグが存在する可能性がありますが、これは哲学的に重要ではありません(実際には間違いなく重要です)。

ユーザーが自分の環境で私のインターフェイスを実行している場合、ユーザーは制御権を持っているため、アクセス修飾子を回避できます。


2

これらのキーワードによって引き起こされるコンパイラエラーがバグから私を救ったような状況にはありませんでした。

ライブラリの作成者が実装のどの部分を保守するかを決定できるようにするため、アプリケーションの作成者をバグから救うことはそれほど重要ではありません。

図書館がある場合

class C {
  public void foo() { ... }

  private void fooHelper() { /* lots of complex code */ }
}

fooの実装を置き換え、場合によってfooHelperは根本的な方法で変更できるようにしたい場合があります。fooHelper私のドキュメントのすべての警告にもかかわらず多くの人々が使用することに決めた場合、私はそれをすることができないかもしれません。

privateライブラリ作成者は、ライブラリのprivate内部の詳細を何年も維持することを余儀なくされることなく、ライブラリを管理可能なサイズのメソッド(およびヘルパークラス)に分割できます。


コンパイラが情報の隠蔽を強制することが価値があるのは何ですか?

サイドノートでは、Javaでprivateコンパイラによって強制されていませんが、Javaによる検証バイトコード


反射はこのメカニズムをオーバーライドできますか?コンパイラが情報の隠蔽を強制することが価値があるのは何ですか?

Javaでは、リフレクションだけでこのメカニズムをオーバーライドできません。privateJavaには2種類あります。これprivateにより、ある外部クラスprivateが、バイトコード検証によってチェックされる別の外部クラスのメンバーにアクセスできなくなります。またprivate

 public class C {
   private int i = 42;

   public class B {
     public void incr() { ++i; }
   }
 }

クラスB(実際の名前C$B)はを使用するiため、コンパイラは合成アクセサメソッドを作成し、バイトコード検証を通過する方法でBアクセスできるようにしますC.i。残念ながら、からClassLoaderクラスを作成できるため、で新しいクラスを作成することで内部クラスに公開さbyte[]れたプライベートを取得するのは非常に簡単CですCCのjarがシールされていない場合に可能のパッケージにです。

適切なprivate施行には、クラスローダー、バイトコード検証、およびプライベートへのリフレクトアクセスを防止できるセキュリティポリシー間の調整が必要です。


クラスの秘密状態を攻撃から保護するために使用できますか?

はい。「安全な分解」は、プログラマがモジュールのセキュリティプロパティを保持しながら協力できる場合に可能になります。モジュールのセキュリティプロパティに違反しないように、別のコードモジュールの作成者を信頼する必要はありません。

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を検討してください。図1は、追加専用のログ機能を構築する方法を示しています。プログラムの残りの部分がJoe-Eで記述されている場合、コードレビュアーは、ログエントリは追加のみ可能で、変更または削除はできないと確信できます。このレビューはLog クラスの検査のみを必要とし、他のコードのレビューを必要としないため、実用的です。したがって、このプロパティを検証するには、ログコードに関するローカルな推論のみが必要です。


1

情報の隠蔽は、優れたソフトウェア設計の主要な関心事の1つです。70年代後半のDave Parnasの論文をご覧ください。基本的に、モジュールの内部状態が一貫していることを保証できない場合、その動作について何も保証できません。また、内部状態を保証できる唯一の方法は、プライベート状態を維持し、ユーザーが指定した方法でのみ変更できるようにすることです。


1

保護を言語の一部にすることにより、何かを得ることができます:合理的な保証。

変数をプライベートにすると、合理的な保証がありますそのクラス内のコードまたはそのクラスの明示的に宣言されたフレンドによってのみ変更されることをできます。その値に合理的に触れることができるコードの範囲は制限され、明示的に定義されています。

構文保護を回避する方法はありますか?絶対に; ほとんどの言語にはそれらがあります。C ++では、いつでもクラスを他の型にキャストして、その一部を突くことができます。JavaとC#では、自分自身をそれに反映させることができます。などなど。

ただし、これを行うのは困難です。あなたがやるべきでないことをしていることは明らかです。偶然にそれを行うことはできません(C ++での野生の書き込み以外)。「コンパイラーからは言われなかったものに触れるつもりです」と喜んで考えなければなりません。あなたは理不尽なことを進んでしなければなりません。

構文保護がなければ、プログラマは偶然物事を台無しにするだけです。ユーザーに規約を教える必要があり、ユーザーは毎回その規約に従う必要があります。そうでない場合、世界は非常に危険になります。

構文保護がなければ、責任は間違った人々、つまりクラスを使用する多くの人々にあります。それらは慣習に従わなければならず、さもないと不特定の悪が発生します。スクラッチ:不特定の悪発生する可能性あります。

APIほど悪いことはありません。間違ったことをすると、すべてが機能する可能性があります。これは、ユーザーが正しいことをしたこと、すべてが正常であることなどを誤って安心させるものです。

そして、その言語の新しいユーザーは何ですか?彼らは実際の構文(コンパイラーによって強制される)を学び、それに従う必要があるだけでなく、この規則(せいぜいピアによって強制される)に従う必要があります。そして、そうでなければ、悪いことは何も起こらないかもしれません。プログラマーが慣例が存在する理由を理解しないとどうなりますか?彼が「それをねじ込む」と言って、あなたのプライベートを突くとどうなりますか?そして、すべてが機能し続けると彼はどう思いますか?

彼は大会は愚かだと考えています。そして、彼は二度とそれに従いません。そして、彼は彼の友人全員にあまり気にしないように言います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.