web-dev-qa-db-ja.com

アクセス修飾子は重要ですか?

理論は、アクセス修飾子は内部状態のカプセル化をサポートするため、コードの安全性を向上させるというものです。 OOPを行うとき、私が使用したすべての言語は、ある種のアクセス制限を実装しています。いくつかのアクセスモデルが他より優れています。

私はJava=開発者のチームです。私たちのプロジェクトは、アクセス修飾子、その適切性、@ VisibleForTesting(a Java注釈)ソースコードの変更が不可能な場合、私たちのプロジェクトでは、サードパーティライブラリ内の何かを最終化または非民営化するのに時間を費やすこともあります。

私は、アクセス修飾子の使用が欠陥密度または実行時エラーの発生にどのように影響するかを示す調査を探しました。私はそれに関する研究を見つけることができません。 Google-Fuが弱いのかもしれません。アクセス修飾子が実際に私たちが想定している利点を提供するという証拠は何ですか?アクセス修飾子の使用方法に関する問題を定量化する研究はどこにありますか?

39
ahoffer

既存の優れた回答 Doc Brownから および Gort the Robot を補足するために、アクセス制限を調べる別の方法は、私たちが書く多くの方法の1つですmodularプログラム。

最も単純なアーキテクチャでは、すべての変数とコード行にすべてのスコープからアクセスできます。これにより、任意の組み合わせでコードをアセンブルできるようになり、プログラムが大きくなるにつれて、組み合わせの数が指数関数的に増加し、プログラムが何をするかを推論することが難しくなります。

これを改善するために、プログラムをさまざまなタイプの再利用可能なモジュールに分割します。各モジュールには、プログラムがモジュールと対話する方法を定義するcontractがあります。 実装は他のコードから隠されています。

モジュールの最も単純なタイプは関数です。関数のシグニチャー(名前、パラメーター、および戻り値のタイプ)がコントラクトを定義します。実装には、ローカル変数と、関数の外部からジャンプできないコード行を含めることができます。可能な組み合わせの数は、変数とコード行のすべての可能な相互作用から、関数のすべての可能な相互作用に減少します。

可視性修飾子は、同じ利点を単純に繰り返します。関数と変数をクラスまたはパッケージにグループ化することにより、各グループにコントラクトと実装を与えることができます。可能な組み合わせの数はさらに減少し、クラスまたはパッケージの有効な相互作用になります。

これはすべて、言語の一部としてではなく、規則によって実装できます。ローカルスコープを持つのではなく、グローバル変数に注意深く名前を付けることができ、可視性を持つのではなく、関数に注意深く名前を付けることができます。これらの規則がより普遍的であるほど、より多くのツールがそれらをサポートできます。オフラインチェッカーは、契約を検証しているかどうかを通知し、IDEは「表示可能な」メンバーのみを提案できます。

真に普遍的なものになるためには、規約は言語に組み込まれ、defaultツールによって強制される必要があります。これは、コードを共有または引き渡すときに最も重要です:ifmostツールが先頭のアンダースコアを「このクラスにプライベート」を意味するものとして読み取るが、default compiler/runtimeはそれを強制しません。他の誰かのコードがそれを「正しく」使用するかどうかを判断する必要があります。 Javaプログラムにprivateが含まれている場合は、契約についてかなり強力な仮定を行うことができます。

結局のところ、それはコードを推論し、維持する機能の柔軟性を犠牲にすることです。関数ではなくgotoを使用すると問題の解決に役立つ場合があるように、プライベートメソッドにアクセスすると処理が完了する場合があります。言語がその強制によってそのトレードオフを行わなかった場合、プログラマはそれをどのように使用するかによって同じトレードオフを行わなければなりません。

11
IMSoP

私が個人的に遭遇したアクセス修飾子が「重要」な場合の実世界の例を挙げましょう。

私たちのソフトウェアは主にpythonであり、pythonが他のほとんどのOO言語と異なる点は、明示的なアクセス修飾子がないことです。代わりに、アンダースコアでプライベートにする必要があるメソッドと属性のプレフィックス。

ある日、開発者が特定の機能に取り組んでいましたが、自分が使用していたオブジェクトのインターフェースで機能させることができませんでした。しかし、プライベートとマークされた特定の属性を使用すると、自分がやりたいことを実行できることに気づきました。それで彼はそれを行い、それをチェックインし、そして(残念ながら)コードレビューをすり抜けてマスターブランチに入れました。

2年早送りします。その開発者は先に進みました。基盤となるライブラリを新しいバージョンに更新しました。確実に突然停止していたコード。その結果、別のタイムゾーンにいる別のチームとの間で、多くのデバッグメッセージとやり取りメッセージが発生しました。

最終的に私たちは問題を見つけました。その基礎となるオブジェクトを所有する開発者は、それが非常に微妙に機能する方法を変更しました。例外がスローされないほど微妙で、他のエラーは発生しませんでした。ライブラリは不安定になりました。これは、そのライブラリの開発者が誰にも問題を引き起こすようなことをしているという手がかりがなかったために起こりました。彼らはインターフェースではなく内部の何かを変更していました。

そのため、本来行うべきことを行った後、ライブラリの開発者に、オブジェクトの内部をいじるのではなく、問題を解決するパブリックメソッドを追加するように依頼しました。

これが、アクセス修飾子が防ぐことです。それらは、インターフェースと実装の分離が明確であることを保証します。これにより、ユーザーはクラスで何が安全にできるかを正確に知ることができ、クラスの開発者はユーザーのソフトウェアを壊すことなく内部を変更できます。

pythonが示すように、強制ではなく、すべて慣習でこれを行うことができますが、それが慣例である場合でも、そのパブリック/プライベートの分離は保守性にとって大きな恩恵です。

68
Gort the Robot

私の経験では、アクセス修飾子の主な利点は、それらが防止するエラーの数ではないため、これについて統計を作成しようとしてもあまり意味がありません。

本当の利点は、特に大規模なコードベースでは、コードベースへの変更の影響分析を数桁容易にすることです。それは私が毎日経験していることです。プライベートなものにのみ影響を与える変更を行う場合、結果は通常、コードベースの比較的小さな部分に絞ることができます。 1つのモジュールでパブリック関数を変更すると、理論的には依存するすべてのモジュールに影響を与える可能性があるため、パブリックパーツに変更を加えると、分析が非常に困難になることがよくあります。

もちろん、いくつかのプライベート関数への変更でさえ、より大きなスケールで結果をもたらす可能性があり、パブリックメソッドへのすべての変更がコードベース全体に影響するわけではありません。ただし、アクセス修飾子を使用すると、変更の大部分が必要な効果または不要な効果を何がどこにもたらすか(そうではないか)をより適切に推論できるため、そのような変更をより効率的な方法で正しく実装できます。

それを私の経験に加えてみましょう。たとえば、コードが2K行未満の小さなプログラムの場合、効果は小さいですが、20万行を超えるコードがあるシステムで作業すると、状況が異なります。

26
Doc Brown

アクセス修飾子はencapsulation。を実装するための手法です。カプセル化によって提供される利点を求める場合は、これらを使用してください。

一般に、ソフトウェアエンジニアリングのベストプラクティスは、学術研究や二重盲検の査読付き実験によって決定されません。彼らは実用主義と経験によって決定されます。証拠は、「実際に機能するか」ということです。または(実用的)「メリットはコストを上回りますか?」

それはあなたの目標が何であるかにも依存します。明快さよりも簡潔さが重視される背景から来た場合、アクセス修飾子がまったく役に立たないことがあります。しかし、その選択ができない場合もあります。 Java開発者のチームで作業する場合、正しいことは、通常、Javaの感性に合うものです。

ローマにいるとき...

17
Robert Harvey

安全性と保守性はさておき、あなたは正気を保ちます。

豪華なマンボジャンボなしで、この現実の世界を作ってみましょう。あなたがあなたの仕事をするのをサポートするクラスライブラリを手に入れるとしましょう。あなたはそれが何をすることになっているのか知っています、あなたはまだそれで働いていません。それを開発環境に追加して、そこに何があるかを調べます。

うわー! 1200クラス!何だ…これらの人たちは何を考えていたの?私は5、10クラスのトップでうまくやれるはずなので、合理的に何でもする必要があります。そして、彼らは私にこれらすべてを与えます。私はこれの99%は完全に私には無関係であるに違いない、彼らは本当に私にとって本当に重要なクラスを私に見せてはどうですか?!

したがって、名前空間を参照して、有望に見えるクラス名を見つけます。これは、ThisIsTheOneYouActuallyWantToUseと呼ばれます。なんて都合のいい。次のように入力します。

ThisIsTheOneYouActuallyWantToUse instance = new ThisIsTheOneYouActuallyWantToUse();
instance.

intelliSenseは、このクラスが提供する必要があるすべてのメソッドとプロパティを示します。うわー!メンバー200人!これらの人たちは何を考えていましたか?!それらすべてが必要ですか?私は違います。ユーザーとして重要なものだけが表示されなかったのはなぜですか?

等。あなたは一日の残りのために不平を言い、疲れて不満を抱いて家に帰ります。

[編集]

これは、サードパーティのライブラリを使用して、関連するクラスで有用なメソッドを見つけようとするアプリケーション開発者の経験を示しています。この問題はすべてのレベルで発生することに注意してください。チームの1人の開発者が別の開発者の作業をレビューするときに問題になる可能性があります。数百のアイテムがすべて1つのレベルにフラット化されたメニューを取得するエンドユーザーを考えてみます。または、章や段落のない、句読点のみの文書。 1つの通りの名前を持つ都市のマップ(家番号に整数を使用すると、物事を大幅に簡略化できますが、とにかくこれらすべての愚かなセクターと通りの名前が必要ですか?).

他の人の作品のユーザーとして、何かを表すメインチャンクの階層を常に探します。何かを理解し、いくつかのステップで必要なものにズームインするには、木から森を見る必要があります。アクセス修飾子は、組織、システム、アプリケーション、モジュール、名前空間、クラス、およびメンバーのチェーンの1つのレベルのみを表します。

8
Martin Maat

特定の研究については知りませんが、失敗するソフトウェアプロジェクトが山ほどあります。それらの一部 の複雑さのために失敗します。 モノリシックソフトウェアの複雑さは、サイズとともに指数関数的に増加します。すべての指数関数的な成長と同様に、複雑さは最終的に利用可能なリソースを超えます。プロジェクトにますます多くの人員を投入しても、構造化されておらず、大きすぎる場合、プロジェクトを保存できません。

ほとんどの人が生まれる前に考案されたソフトウェアエンジニアリングパラダイムにより、複雑さが軽減されます。 Divide et impera。

このため、パーツは疎結合である必要があります。そうでない場合、実際の独立したパーツではありません。

これですべてです。アクセス修飾子は、メンバーの定義されたサブセットのみを外部から利用できるようにすることで、密結合の1つの方法を防ぎます。プログラマーは自己規律の欠如で悪名高く、コーディングルールは厳密なレビューによってのみ実施されるため、これをコーディングルールにするのではなく言語レベルで適用する必要があります。

他の人が言ったことすべてに加えて、アクセス修飾子は重要なドキュメントです。彼らは意図されたAPIが何であるかを文書化します。多くの場合、意図したAPIは、定義されているすべてのメソッドのほんの一部にすぎません。 「クライアントによる使用を意図していない」と明確にラベル付けされたメソッドがあると、間違いを回避し、使用する正しいメソッドを見つけるのに役立ちます。さらに、その契約はコンパイラーによってチェックされます。

3
kutschkem

アクセス修飾子は重要ですか?はい、彼らがやります。 pythonの例の答えは十分に説明に役立ちます。ただし、特にJavaの世界でどのように使用されるかを考えると、これにはいくつかの落とし穴があります:

  • アクセス修飾子は正直でなければなりません。
  • パブリックなものは、クラスのすべてのユーザーによってパブリックとして扱われます。クラスのユーザーが制御下にない場合、パブリックメソッドを変更または削除できない可能性があります
  • 可視性だけでなく、可変性も重要です。
  • 大規模なシステムでは、デカップリングは、パブリックメソッドのみを公開し、メンバーや具体的な実装さえも公開しないインターフェースを使用することですでに行われていることがよくあります。

自明な、多くの場合自動生成されるゲッターとセッターを持つ10のメンバーよりもクラスを複雑にするものはありません。変数fooをプライベートにして、次のように直接、チェックされていないセッターを提供することは、どのような意味でも役に立ちません。

    class Foobar {
      private Foo bar;

      public void setBar(Foo bar) { this.bar=bar; }
      public Foo getBar() { return bar; }
    }

この場合、パブリック変数では何も得られていませんが、クラスに2つのメソッドが追加されています。そして、誰もが(ほぼ)すべてのメンバーを直接設定できる場合、いかなる方法でも分離していません。同時に、何かが不変であることを知っていると、呼び出し元としての生活が楽になります。

だから私は以下をお勧めします

  • プリミティブ型があり、それが不変である場合、それをパブリックかつファイナルにします
  • メソッドについては、個々のクラスではなく、情報を隠すためにインターフェースに依存しようとします。外部に依存することなく、多数のコラボレーションクラスを交換できます。
  • 複合型の変数の場合、(可能な場合)構築中にそれらを渡そうとします。ささいなゲッターとセッターをできるだけ避けます
2
Manziel