Warum schlägt Clean Code vor, geschützte Variablen zu vermeiden?

168

Clean Code empfiehlt, geschützte Variablen im Abschnitt "Vertikaler Abstand" des Kapitels "Formatierung" zu vermeiden:

Konzepte, die eng miteinander verbunden sind, sollten vertikal nahe beieinander gehalten werden. Offensichtlich funktioniert diese Regel nicht für Konzepte, die in separate Dateien gehören. Aber eng verwandte Konzepte sollten nicht in verschiedene Dateien aufgeteilt werden, es sei denn, Sie haben einen sehr guten Grund. Dies ist einer der Gründe, warum geschützte Variablen vermieden werden sollten .

Was ist die Begründung?

Matsemann
quelle
7
zeigt ein Beispiel , wo Sie denken , Sie brauchen es
gnat
63
Ich würde nicht viel in diesem Buch als Evangelium sehen.
Rig
40
@ Rig du grenzst an Blasphemie in diesen Teilen mit einem Kommentar wie diesem.
Ryathal
40
Das ist ok für mich. Ich habe das Buch gelesen und kann meine eigene Meinung dazu haben.
Rig
10
Ich habe das Buch gelesen und obwohl ich mit den meisten Dingen einverstanden bin, würde ich nicht sagen, dass ich es auch als Evangelium betrachte. Ich denke, dass jede Situation anders ist. Und sich an die genauen Richtlinien zu halten, die im Buch dargestellt sind, ist für viele Projekte ziemlich schwierig.
Simon Whitehead

Antworten:

277

Geschützte Variablen sollten vermieden werden, weil:

  1. Sie führen in der Regel zu YAGNI- Problemen. Wenn Sie keine abgeleitete Klasse haben, die tatsächlich mit dem geschützten Member arbeitet, machen Sie sie privat.
  2. Sie neigen dazu, zu LSP- Problemen zu führen . Mit geschützten Variablen ist im Allgemeinen eine gewisse intrinsische Invarianz verbunden (oder sie wären öffentlich). Inheritors müssen dann jene Eigenschaften beibehalten, die Leute vermasseln oder vorsätzlich verletzen können.
  3. Sie neigen dazu, OCP zu verletzen . Wenn die Basisklasse zu viele Annahmen über das geschützte Element macht oder der Erbe zu flexibel im Verhalten der Klasse ist, kann dies dazu führen, dass das Verhalten der Basisklasse durch diese Erweiterung geändert wird.
  4. Sie führen eher zur Vererbung als zur Komposition. Dies führt tendenziell zu einer engeren Kopplung, zu mehr Verstößen gegen die SRP , zu schwierigeren Tests und zu einer Reihe anderer Faktoren, die in die Diskussion über die Bevorzugung der Komposition gegenüber der Vererbung fallen.

Aber wie Sie sehen, sind alle diese "tendenziell". Manchmal ist ein geschütztes Mitglied die eleganteste Lösung. Geschützte Funktionen weisen in der Regel weniger dieser Probleme auf. Es gibt jedoch eine Reihe von Faktoren, die zu einer sorgfältigen Behandlung führen. Mit allem, was diese Art von Sorgfalt erfordert, machen die Leute Fehler und in der Programmierwelt bedeutet dies Fehler und Designprobleme.

Telastyn
quelle
Danke für viele gute Gründe. In dem Buch wird es jedoch speziell im Zusammenhang mit Formatierung und vertikaler Distanz erwähnt. Es ist der Teil, über den ich mich wundere.
Matsemann
2
Es scheint, dass sich Onkel Bob auf die vertikale Distanz bezieht, wenn sich jemand auf ein geschütztes Mitglied zwischen Klassen bezieht und nicht in derselben Klasse.
Robert Harvey
5
@Matsemann - sicher. Wenn ich mich genau an das Buch erinnere, konzentriert sich dieser Abschnitt auf die Lesbarkeit und Entdeckbarkeit von Code. Wenn Variablen und Funktionen an einem Konzept arbeiten, sollten sie im Code nahe beieinander liegen. Geschützte Member werden von abgeleiteten Typen verwendet, die sich nicht unbedingt in der Nähe des geschützten Members befinden müssen, da sie sich in einer völlig anderen Datei befinden.
Telastyn
2
@ steve314 Ich kann mir kein Szenario vorstellen, in dem die Basisklasse und alle ihre Erben immer nur in einer Datei leben werden. Ohne ein Beispiel neige ich dazu, es für einen Missbrauch der Erbschaft oder eine schlechte Abstraktion zu halten.
Telastyn
3
YAGNI = Du wirst es nicht brauchen LSP = Liskov Substitution Prinzip OCP = Open / Close Prinzip SRP = Single Responsibility Prinzip
Bryan Glazer
54

Es ist wirklich der gleiche Grund, warum Sie Globals vermeiden, nur in kleinerem Maßstab. Das liegt daran, dass es schwierig ist, überall zu finden, wo eine Variable gelesen oder noch schlimmer geschrieben wird, und dass es schwierig ist, die Verwendung konsistent zu machen. Wenn die Verwendung begrenzt ist, wie an einer offensichtlichen Stelle in der abgeleiteten Klasse geschrieben und an einer offensichtlichen Stelle in der Basisklasse gelesen , können geschützte Variablen den Code klarer und prägnanter machen. Wenn Sie versucht sind, eine Variable in mehreren Dateien beliebig oft zu lesen und zu schreiben, ist es besser, sie zu kapseln.

Karl Bielefeldt
quelle
26

Ich habe das Buch nicht gelesen, aber ich kann einen Stich in die Bedeutung von Onkel Bob machen.

Wenn Sie protectedetwas anlegen , bedeutet dies, dass eine Klasse es erben kann. Mitgliedsvariablen sollen jedoch zu der Klasse gehören, in der sie enthalten sind. Dies ist Teil der grundlegenden Kapselung. Wenn Sie protectedeine Membervariable hinzufügen, wird die Kapselung unterbrochen, da jetzt eine abgeleitete Klasse Zugriff auf die Implementierungsdetails der Basisklasse hat. Es ist dasselbe Problem, das auftritt, wenn Sie eine Variable publicfür eine gewöhnliche Klasse erstellen.

Um das Problem zu beheben, können Sie die Variable wie folgt in eine geschützte Eigenschaft einkapseln:

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

Dies ermöglicht namedas sichere Festlegen einer abgeleiteten Klasse mithilfe eines Konstruktorarguments, ohne dass Implementierungsdetails der Basisklasse verfügbar gemacht werden.

Robert Harvey
quelle
Sie haben eine öffentliche Eigenschaft mit einem geschützten Setter erstellt. Das Schutzfeld sollte mit einer geschützten Eigenschaft mit privatem Setter gelöst werden.
Andy
2
+1 jetzt. Setter könnte vermutlich auch geschützt werden, aber die Punkt-IDs, die die Basis erzwingen kann, ob der neue Wert gültig ist oder nicht.
Andy
Nur um eine gegenteilige Sichtweise zu bieten - ich mache alle meine Variablen in einer Basisklasse geschützt. Wenn auf sie nur über eine öffentliche Schnittstelle zugegriffen werden soll, sollte es sich nicht um eine abgeleitete Klasse handeln, sondern nur um ein Basisklassenobjekt. Aber ich vermeide auch Hierarchien, die mehr als 2 Klassen tief sind
Martin Beckett
2
Es gibt einen großen Unterschied zwischen protectedund publicMitgliedern. Wenn BaseTypeein öffentliches Mitglied verfügbar gemacht wird, bedeutet dies, dass alle abgeleiteten Typen dasselbe Mitglied haben müssen, das auf die gleiche Weise funktioniert, da eine DerivedTypeInstanz möglicherweise an den Code übergeben wird, der eine BaseTypeReferenz empfängt und die Verwendung dieses Mitglieds erwartet. Wenn im Gegensatz dazu BaseTypeein protectedMitglied verfügbar gemacht wird , DerivedTypekann erwartet werden, dass es auf dieses Mitglied zugreift base, basekann jedoch nichts anderes als a sein BaseType.
Supercat
18

Onkel Bobs Argument ist in erster Linie die Distanz: Wenn Sie ein Konzept haben, das für eine Klasse wichtig ist, bündeln Sie das Konzept zusammen mit der Klasse in dieser Datei. Nicht auf zwei Dateien auf der Festplatte aufgeteilt.

Geschützte Member-Variablen sind an zwei Stellen verteilt und sehen irgendwie wie Magie aus. Sie verweisen auf diese Variable, aber sie ist hier nicht definiert. Wo ist sie definiert? Und so beginnt die Jagd. Vermeiden Sie lieber protectedsein Argument.

Jetzt glaube ich nicht, dass diese Regel die ganze Zeit eingehalten werden muss (wie: du sollst nicht verwenden protected). Schauen Sie sich den Geist dessen , was er ist immer auf ... bündeln verwandte Dinge zusammen in einer Datei - verwenden Techniken und Funktionen Programmierung zu tun. Ich würde empfehlen, dass Sie nicht zu viel analysieren und sich in die Details darüber vertiefen.

J. Polfer
quelle
9

Einige sehr gute Antworten schon hier. Aber eins noch:

In den meisten modernen OO-Sprachen ist es eine gute Angewohnheit (in Java AFAIK ist dies erforderlich), jede Klasse in eine eigene Datei zu packen (bitte machen Sie sich keine Gedanken über C ++, wo Sie oft zwei Dateien haben).

Daher ist es praktisch unmöglich, die Funktionen in einer abgeleiteten Klasse so zu halten, dass sie auf eine geschützte Variable einer Basisklasse zugreifen, die im Quellcode der Variablendefinition "vertikal nahe" ist. Idealerweise sollten sie jedoch dort aufbewahrt werden, da geschützte Variablen für den internen Gebrauch vorgesehen sind, was den Zugriff auf Funktionen häufig zu einem "eng verwandten Konzept" macht.

Doc Brown
quelle
1
Nicht in Swift. Interessanterweise hat Swift nicht "protected", sondern "fileprivate", das sowohl "protected" als auch "friend" in C ++ ersetzt.
gnasher729
@ gnasher729 - natürlich hat Swift eine Menge Smalltalk in seinem Erbe. Smalltalk hat nichts anderes als private Variablen und öffentliche Methoden. Und privat in Smalltalk bedeutet privat: Zwei Objekte derselben Klasse können nicht auf die Variablen des anderen zugreifen.
Jules
4

Die Grundidee ist, dass ein "Feld" (Variable auf Instanzebene), das als geschützt deklariert ist, wahrscheinlich sichtbarer als es sein muss und weniger "geschützt" ist, als Sie vielleicht möchten. In C / C ++ / Java / C # gibt es keinen Zugriffsmodifikator, der der Entsprechung von "Nur für untergeordnete Klassen in derselben Assembly zugänglich" entspricht. Auf diese Weise können Sie eigene untergeordnete Klassen definieren, die auf das Feld in Ihrer Assembly zugreifen können Kindern, die in anderen Baugruppen erstellt wurden, nicht den gleichen Zugriff gewähren; C # hat interne und geschützte Modifikatoren, aber wenn Sie diese kombinieren, wird der Zugriff "intern oder geschützt", nicht "intern und geschützt". Auf ein geschütztes Feld kann also jedes Kind zugreifen, unabhängig davon, ob Sie das Kind geschrieben haben oder es jemand anderes getan hat. Geschützt ist damit eine offene Tür für einen Hacker.

Außerdem haben Felder per Definition so gut wie keine Validierung, wenn sie geändert werden. In C # können Sie einen Readonly-Wert erstellen, wodurch Werttypen effektiv konstant und Referenztypen nicht neu initialisiert werden können (aber immer noch sehr wandelbar sind), aber das war es auch schon. Selbst geschützt haben Ihre Kinder (denen Sie nicht vertrauen können) Zugriff auf dieses Feld und können es auf einen ungültigen Wert setzen, wodurch der Zustand des Objekts inkonsistent wird (etwas, das vermieden werden muss).

Die akzeptierte Möglichkeit, mit Feldern zu arbeiten, besteht darin, sie privat zu machen und mit einer Eigenschaft und / oder einer Get- und Setter-Methode darauf zuzugreifen. Wenn alle Konsumenten der Klasse den Wert benötigen, machen Sie den Getter (zumindest) öffentlich. Wenn nur Kinder es brauchen, machen Sie den Getter geschützt.

Ein anderer Ansatz, der die Frage beantwortet, ist, sich selbst zu fragen. Warum muss Code in einer untergeordneten Methode die Möglichkeit haben, meine Statusdaten direkt zu ändern? Was sagt das über diesen Code aus? Das ist das "vertikale Distanz" -Argument. Wenn ein untergeordnetes Element Code enthält, der den Status des übergeordneten Elements direkt ändern muss, sollte dieser Code möglicherweise zunächst dem übergeordneten Element gehören.

KeithS
quelle
4

Es ist eine interessante Diskussion.

Um ehrlich zu sein, können gute Tools viele dieser "vertikalen" Probleme lindern. Meiner Meinung nach ist der Begriff "Datei" tatsächlich etwas, das die Softwareentwicklung behindert - etwas, woran das Light Table-Projekt arbeitet (http://www.chris-granger.com/2012/04/12/light- Tabelle --- ein-neues-Ide-Konzept /).

Nehmen Sie Dateien aus dem Bild, und der geschützte Bereich wird attraktiver. Das geschützte Konzept wird am deutlichsten in Sprachen wie SmallTalk - wo jede Vererbung einfach das ursprüngliche Konzept einer Klasse erweitert. Es sollte alles tun und alles haben, was die Elternklasse getan hat.

Meiner Meinung nach sollten Klassenhierarchien so flach wie möglich sein, wobei die meisten Erweiterungen aus der Komposition stammen. In solchen Modellen sehe ich keine Gründe dafür, dass private Variablen stattdessen nicht geschützt werden. Wenn die erweiterte Klasse eine Erweiterung darstellen soll, die das gesamte Verhalten der übergeordneten Klasse umfasst (was meiner Meinung nach und daher private Methoden von Natur aus als schlecht erachten), warum sollte die erweiterte Klasse dann nicht auch die Speicherung solcher Daten darstellen?

Anders ausgedrückt: In jedem Fall, in dem ich der Meinung bin, dass eine private Variable besser geeignet ist als eine geschützte, ist die Vererbung nicht die Lösung des Problems.

Spronkey
quelle
0

Wie es dort heißt, ist dies nur einer der Gründe, weshalb logisch verknüpfter Code in physisch verknüpften Entitäten (Dateien, Pakete und andere) platziert werden sollte. In kleinerem Maßstab entspricht dies dem Grund, warum Sie keine Klasse einfügen, die DB-Abfragen in das Paket mit Klassen aufnimmt, die die Benutzeroberfläche anzeigen.

Der Hauptgrund dafür, dass geschützte Variablen nicht empfohlen werden, besteht darin, dass sie dazu neigen, die Kapselung zu unterbrechen. Variablen und Methoden sollten so wenig wie möglich sichtbar sein. Weitere Informationen finden Sie in Joshua Blochs Effective Java , Punkt 13 - "Minimieren Sie die Zugänglichkeit von Klassen und Mitgliedern".

Sie sollten jedoch nicht das gesamte Ad-Litteram als Guildline verwenden. Wenn geschützte Variablen sehr schlecht sind, wurden sie nicht an erster Stelle in die Sprache eingefügt. Ein sinnvoller Ort für geschützte Felder ist imho innerhalb der Basisklasse ein Testframework (Integrationstestklassen erweitern es).

m3th0dman
quelle
1
Gehen Sie nicht davon aus, dass eine Sprache dies zulässt. Ich bin mir nicht sicher, ob es wirklich einen guten Grund gibt, geschützte Felder zuzulassen. wir verbieten sie tatsächlich bei meinem Arbeitgeber.
Andy
2
@ Andy Du hast nicht gelesen, was ich gesagt habe. Ich sagte, dass geschützte Felder nicht empfohlen werden und Gründe dafür. Es gibt eindeutig Szenarien, in denen geschützte Variablen benötigt werden. Möglicherweise möchten Sie einen konstanten Endwert nur in den Unterklassen.
m3th0dman
Vielleicht möchten Sie dann einen Teil Ihres Beitrags umformulieren, da Sie Variable und Feld austauschbar zu verwenden scheinen und klarstellen, dass Sie Dinge wie geschützte Konstanten als Ausnahme betrachten würden.
Andy
3
@Andy Es ist ziemlich offensichtlich, dass ich, da ich mich auf geschützte Variablen bezog, nicht über lokale Variablen oder Parametervariablen sprach, sondern über Felder. Ich erwähnte auch ein Szenario, in dem nicht konstante geschützte Variablen nützlich sind. imho ist es falsch für ein Unternehmen, die Art und Weise zu erzwingen, wie die Programmierer Code schreiben.
m3th0dman
1
Wenn es offensichtlich gewesen wäre, wäre ich nicht verwirrt und hätte Sie herabgestimmt. Die Regel wurde von unseren erfahrenen Entwicklern festgelegt, ebenso wie unsere Entscheidung, StyleCop und FxCop auszuführen und Komponententests und Codeüberprüfungen erforderlich zu machen.
Andy
0

Ich finde, dass die Verwendung von geschützten Variablen für JUnit-Tests nützlich sein kann. Wenn Sie sie als privat kennzeichnen, können Sie während des Testens nicht ohne Reflektion darauf zugreifen. Dies kann hilfreich sein, wenn Sie einen komplexen Prozess durch viele Statusänderungen testen.

Sie könnten sie mit geschützten Getter-Methoden privat machen, aber wenn die Variablen nur während eines JUnit-Tests extern verwendet werden, bin ich mir nicht sicher, ob das eine bessere Lösung ist.

Nick
quelle
-1

Ja, ich stimme zu, dass dies etwas seltsam ist, da (wie ich mich erinnere) in dem Buch auch alle nicht-privaten Variablen als etwas behandelt werden, das vermieden werden sollte.

Ich denke, das Problem mit dem protected-Modifikator ist, dass das Mitglied nicht nur Subklassen ausgesetzt ist, sondern auch für das gesamte Paket sichtbar ist. Ich denke, es ist dieser doppelte Aspekt, den Onkel Bob hier ausnimmt. Natürlich kann man geschützte Methoden und damit die geschützte Variablenqualifikation nicht immer umgehen.

Ich denke, ein interessanterer Ansatz ist es, alles öffentlich zu machen, was Sie zu kleinen Klassen zwingt, was wiederum die gesamte vertikale Distanzmetrik etwas umstritten macht.

CurtainDog
quelle