Clean Codeが保護された変数を避けることを示唆しているのはなぜですか?


168

クリーンコードでは、「書式設定」の章の「垂直距離」セクションで保護された変数を避けることを推奨しています。

密接に関連する概念は、垂直方向に互いに近づける必要があります。明らかに、このルールは別々のファイルに属する概念には機能しません。ただし、非常に適切な理由がない限り、密接に関連する概念を別のファイルに分割しないでください。実際、これは保護された変数を避けるべき理由の1つです

理由は何ですか?


7
あなたそれを必要だと思う場所を示す
gnat

63
私はその本では福音としてあまり取り上げません。
リグ

40
@Rigのようなコメントを付けて、これらの部分で冒とくに接しています。
リャサル

40
大丈夫です。私は本を​​読んだことがあり、それについて自分の意見を持つことができます。
リグ

10
私はこの本を読みました。そこにあるものの大部分には同意しますが、私はそれが福音だとは考えていません。すべての状況は異なると思います。そして、本に示されている正確なガイドラインに固執することは、多くのプロジェクトにとって非常に困難です。
サイモンホワイトヘッド

回答:


277

保護された変数は次の理由で避ける必要があります。

  1. それらはYAGNI問題につながる傾向があります。保護されたメンバーと実際にやり取りする子孫クラスがない限り、プライベートにします。
  2. それらはLSPの問題につながる傾向があります。一般に、保護された変数には、いくつかの固有の不変性が関連付けられています(または公開されます)。継承者はそれらのプロパティを維持する必要があり、人々はそれを台無しにしたり、故意に違反することができます。
  3. OCPに違反する傾向があります。基本クラスが保護されたメンバーに関してあまりにも多くの仮定を行う場合、または継承者がクラスの動作に対して柔軟性が高すぎる場合、その拡張機能によって基本クラスの動作が変更される可能性があります。
  4. それらは、構成ではなく拡張の継承につながる傾向があります。これは、より緊密な結合、より多くのSRP違反、より困難なテスト、および「継承よりも有利な構成」の議論に含まれる他の多くのものにつながる傾向があります。

しかし、ご覧のとおり、これらはすべて「傾向」です。保護されたメンバーが最もエレガントなソリューションである場合があります。そして、保護された機能はこれらの問題の少ない傾向があります。しかし、それらを慎重に扱わせる多くのことがあります。そのような注意を必要とするものは何でも、人々は間違いを犯し、プログラミングの世界ではバグと設計上の問題を意味します。


多くの正当な理由をありがとう。しかし、この本は特にフォーマットと垂直距離のコンテキストでそれについて言及していますが、それは私が疑問に思っている部分です。
マッツマン

2
ボブおじさんは、誰かが同じクラスではなくクラス間の保護されたメンバーを指しているとき、垂直距離を指しているようです。
ロバートハーヴェイ

5
@マッセマン-確かに。本を正確に覚えていれば、そのセクションはコードの可読性と発見可能性に焦点を当てています。変数と関数が概念で機能する場合、それらはコード内で近いはずです。保護されたメンバーは、完全に異なるファイルにあるため、必ずしも保護されたメンバーの近くにあることができない派生型によって使用されます。
テラスティン

2
@ steve314基本クラスとそのすべての継承クラスが1つのファイルにしか存在しないシナリオは想像できません。例がなければ、私はそれを継承の濫用または貧弱な抽象化だと思う傾向があります。
テラスティン

3
YAGNI =あなたはそれを必要としないLSP = Liskov Substitution Principle OCP = Open / Close Principle SRP = Single Responsibility Principle
Bryan Glazer

54

グローバル化を避ける理由は、まさに小規模ですが、それはまさに同じ理由です。それは、変数が読み取られている、またはさらに悪いことに、書き込まれているすべての場所を見つけるのが難しく、使用法の一貫性を保つのが難しいからです。派生クラスの1つの明白な場所で記述され、基本クラスの1つの明白な場所で読み取られるなど、使用が制限されている場合、保護された変数によりコードがより明確で簡潔になります。複数のファイル全体で変数を自由に読み書きしたい場合は、それをカプセル化することをお勧めします。


26

私は本を​​読んでいませんが、ボブおじさんの意図を突き刺すことができます。

protected何かを置くと、クラスがそれを継承できることを意味します。ただし、メンバー変数は、それらが含まれるクラスに属すると想定されています。これは基本的なカプセル化の一部です。protectedメンバー変数を設定すると、派生クラスが基本クラスの実装の詳細にアクセスできるようになるため、カプセル化が解除されます。public通常のクラスで変数を作成するときに発生する問題と同じです。

問題を修正するには、次のように変数を保護されたプロパティにカプセル化できます。

protected string Name
{
    get { return name; }
    private set { name = value; }
}

これによりname、基本クラスの実装の詳細を公開することなく、コンストラクター引数を使用して派生クラスから安全に設定できます。


保護されたセッターでパブリックプロパティを作成しました。保護フィールドは、プライベートセッターを持つ保護プロパティで解決する必要があります。
アンディ

2
+1。セッターも保護できると思いますが、新しい値が有効かどうかによってベースが強制できるポイントID。
アンディ

反対の観点を提供するために-私はすべての変数を基本クラスで保護します。パブリックインターフェイスを介してのみアクセスする場合は、派生クラスではなく、単にベースクラスオブジェクトを含める必要があります。しかし、私はまた、深い階層に2つの以上のクラスを避ける
マーティン・ベケット

2
とメンバーには大きな違いがprotectedありpublicます。BaseTypeパブリックメンバーを公開する場合DerivedTypeBaseType参照を受け取り、そのメンバーの使用を予期するコードにインスタンスが渡される可能性があるため、すべての派生型は同じメンバーが同じように機能する必要があることを意味します。場合はこれとは対照的に、BaseType公開されるprotectedメンバを、その後DerivedTypeにそのメンバーにアクセスすることを期待することがありbaseますが、base何もすることはできません、他のよりBaseType
-supercat

18

ボブおじさんの議論は、主に距離の1つです。クラスにとって重要な概念がある場合は、そのファイル内のクラスと概念をまとめてください。ディスク上の2つのファイルに分割されていません。

保護されたメンバー変数は2つの場所に散在しており、ちょっと魔法のように見えます。この変数を参照していますが、ここで定義されていません...どこで定義されていますか?そして、狩りが始まります。protected彼の主張は完全に避ける方が良い。

今、私は規則が常に手紙に従う必要があるとは思わない(例えば:あなたは使わないprotected)。彼が得ているものの精神を見てください...関連するものを1つのファイルにまとめてください-それを行うためにプログラミング技術と機能を使用してください。分析しすぎて、これに関する詳細に巻き込まれないようにすることをお勧めします。


9

すでに非常に良い答えがここにあります。ただし、追加することが1つあります。

ほとんどの最新のOO言語では、各クラスを独自のファイルに配置するのが良い習慣です(Javaでは必要です)(多くの場合、2つのファイルがあるC ++については気にしないでください)。

したがって、基本クラスの保護された変数にアクセスする派生クラスの関数を、ソースコード内で変数定義に「垂直に」維持することは事実上不可能です。しかし、理想的には、保護された変数は内部使用を目的としているため、そこに保持する必要があります。


1
Swiftではありません。興味深いことに、Swiftには「保護」はありませんが、「保護」とC ++「フレンド」の両方を置き換える「ファイルプライベート」があります。
gnasher729

@ gnasher729-もちろん、Swiftには多くのSmalltalkがあります。Smalltalkには、プライベート変数とパブリックメソッド以外のものはありません。また、Smalltalkのprivateはprivateを意味します。同じクラスの2つのオブジェクトでも互いの変数にアクセスできません。
ジュール

4

基本的な考え方は、保護されていると宣言された「フィールド」(インスタンスレベルの変数)は、おそらく必要以上に見えやすく、必要以上に「保護されていない」ということです。C / C ++ / Java / C#には「同じアセンブリ内の子クラスからのみアクセス可能」に相当するアクセス修飾子がないため、アセンブリ内のフィールドにアクセスできる独自の子を定義する機能が付与されますが、他のアセンブリで作成された子に同じアクセスを許可しない。C#には内部修飾子と保護修飾子がありますが、それらを組み合わせると、アクセスは「内部および保護」ではなく「内部または保護」になります。したがって、保護されたフィールドは、あなたがその子供を書いたのか、他の誰かが書いたのかに関係なく、どの子供からもアクセスできます。したがって、保護はハッカーにとって開かれた扉です。

また、フィールドの定義によると、フィールドの変更に固有の検証はほとんどありません。C#では、1つの読み取り専用を作成できます。これにより、値型は事実上定数になり、参照型は再初期化できません(ただし、非常に変更可能)が、それだけです。そのため、保護されている場合でも、(信頼できない)子はこのフィールドにアクセスし、無効な値に設定して、オブジェクトの状態を一貫性のないもの(回避すべきもの)にすることができます。

フィールドを操作するために受け入れられている方法は、フィールドをプライベートにし、プロパティ、および/またはゲッターとセッターメソッドでアクセスすることです。クラスのすべてのコンシューマーが値を必要とする場合、ゲッターを(少なくとも)公開します。子供だけが必要な場合は、ゲッターを保護してください。

質問に答える別のアプローチは、自問することです。子メソッドのコードに、状態データを直接変更する機能が必要なのはなぜですか?それはそのコードについて何を言っていますか?それがその顔の「垂直距離」引数です。親の状態を直接変更する必要があるコードが子にある場合、そのコードは最初に親に属している必要がありますか?


4

興味深い議論です。

率直に言って、優れたツールはこれらの「垂直」問題の多くを軽減できます。実際、私の意見では、「ファイル」という概念は実際にはソフトウェア開発を妨げるものです-Light Tableプロジェクトが取り組んでいるもの(http://www.chris-granger.com/2012/04/12/light-テーブル--- a-new-ide-concept /)。

ファイルを写真から外すと、保護されたスコープがより魅力的になります。保護された概念は、継承がクラスの元の概念を単に拡張するSmallTalkのような言語で最も明確になります。親クラスが実行したすべてを実行し、すべてを実行する必要があります。

私の意見では、クラス階層は可能な限り浅く、ほとんどの拡張機能は構成に基づいている必要があります。そのようなモデルでは、プライベート変数が保護されない理由は見当たりません。拡張クラスが親クラスのすべての動作を含む拡張機能を表す必要がある場合(それが必要であり、したがってプライベートメソッドが本質的に悪いと考える)、拡張クラスがそのようなデータのストレージも表すべきではないのはなぜですか?

別の言い方をすれば、プライベート変数が保護されている変数よりも適していると感じる場合、継承はそもそも問題の解決策ではありません。


0

そこにあるように、これは理由の1つにすぎません。つまり、論理的にリンクされたコードは、物理的にリンクされたエンティティ(ファイル、パッケージなど)内に配置する必要があります。小規模では、これは、UIを表示するクラスを含むパッケージ内にDBクエリを作成するクラスを配置しない理由と同じです。

ただし、保護された変数が推奨されない主な理由は、カプセル化を破壊する傾向があるためです。変数とメソッドの可視性はできるだけ制限する必要があります。詳細については、Joshua BlochのEffective Javaの Item 13-「クラスとメンバーのアクセシビリティを最小限に抑える」を参照してください。

ただし、この広告のすべてをギルドラインとしてとるべきではありません。保護された変数が非常に悪い場合、そもそも言語内に配置されていません。保護フィールドimhoの合理的な場所は、テストフレームワークからの基本クラス内です(統合テストクラスはそれを拡張します)。


1
言語がそれを許可しているからといって、それは大丈夫だと思い込まないでください。保護されたフィールドを許可する神の理由が本当にあるかどうかはわかりません。私たちは実際に雇用主でそれらを禁止しています。
アンディ

2
@アンディあなたは私が言ったことを読んでいません。保護されたフィールドは推奨されないことと、その理由を説明しました。保護された変数が必要なシナリオが明らかにあります。サブクラスでのみ一定の最終値が必要な場合があります。
m3th0dman

変数とフィールドを交換可能に使用し、保護されたconstのようなものを例外と見なすことを明示するように見えるので、投稿の一部を言い換えることができます。
アンディ

3
@Andy保護された変数を参照していたので、ローカル変数またはパラメーター変数についてではなく、フィールドについて話していたことは明らかです。また、非定数の保護変数が役立つシナリオについても言及しました。会社にとって、プログラマーがコードを書く方法を強制することは間違っています。
m3th0dman

1
もしそれが明らかだったら、私はあなたを混乱させず、あなたを否定しなかったでしょう。このルールは、StyleCop、FxCopを実行し、ユニットテストとコードレビューを要求するという私たちの決定と同様に、シニア開発者によって作成されました。
アンディ

0

JUnitテストに保護された変数を使用すると便利です。それらをプライベートにすると、テスト中にリフレクションなしでそれらにアクセスできなくなります。これは、多くの状態変更を通じて複雑なプロセスをテストしている場合に役立ちます。

保護されたゲッターメソッドを使用してプライベートにすることもできますが、変数がJUnitテスト中に外部でのみ使用される場合、それがより良いソリューションであるかどうかはわかりません。


-1

はい、私は(私が思い出すように)本がすべての非プライベート変数についても避けるべきものとして議論していることを考えると、それはいくぶん奇妙であることに同意します。

protected修飾子の問題は、メンバーがサブクラスに公開されるだけでなく、パッケージ全体からも見えるようになることだと思います。ボブおじさんがここで例外を取っているのは、この二重の側面だと思います。もちろん、常に保護されたメソッド、したがって保護された変数の修飾を回避することはできません。

もっと面白いアプローチはすべてを公開することだと思います。それは小さなクラスを持つことを強制し、その結果、垂直距離メトリック全体がやや意味のないものになります。

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