Grundsatz der Einzelverantwortung - Übernehme ich ihn?

13

Als Referenz - http://en.wikipedia.org/wiki/Single_responsibility_principle

Ich habe ein Testszenario, in dem in einem Anwendungsmodul die Erstellung von Ledger-Einträgen durchgeführt wird. Es gibt drei grundlegende Aufgaben, die ausgeführt werden könnten:

  • Zeigen Sie vorhandene Ledger-Einträge im Tabellenformat an.
  • Legen Sie über die Schaltfläche Anlegen einen neuen Sachkonteneintrag an.
  • Klicken Sie auf einen Ledger-Eintrag in der Tabelle (im ersten Zeiger erwähnt) und sehen Sie sich die Details auf der nächsten Seite an. Sie können einen Ledger-Eintrag auf dieser Seite ungültig machen.

(Auf jeder Seite gibt es noch ein paar Operationen / Validierungen, aber der Kürze halber werde ich es auf diese beschränken.)

Also habe ich beschlossen, drei verschiedene Klassen zu erstellen -

  • LedgerLandingPage
  • CreateNewLedgerEntryPage
  • ViewLedgerEntryPage

Diese Klassen bieten die Dienste an, die auf diesen Seiten ausgeführt werden könnten, und Selentests verwenden diese Klassen, um die Anwendung in einen Zustand zu versetzen, in dem ich bestimmte Behauptungen aufstellen könnte.

Als ich es mit einem meiner Kollegen besprechen ließ, war er überfordert und bat mich, eine einzige Klasse für alle zu machen. Obwohl ich das Gefühl habe, dass mein Design sehr sauber ist, bezweifle ich, dass ich das Prinzip der Einzelverantwortung übertrieben verwende

Tarun
quelle
2
IMHO gibt es keinen Grund zu versuchen, Ihre Frage im Allgemeinen zu beantworten, ohne zwei Dinge zu wissen: Wie groß sind diese Klassen (nach Methodenzahl und LOC)? Und wie viel größer / komplexer werden sie voraussichtlich in absehbarer Zeit wachsen?
Péter Török
2
Jeweils 10 Methoden sind meiner Meinung nach ein klarer Hinweis darauf, den Code nicht mit 30 Methoden einer Klasse zuzuordnen.
Doc Brown
6
Dies ist Grenzgebiet: Eine Klasse von 100 LOC und 10 Methoden ist nicht zu klein, und eine von 300 LOC ist nicht zu groß. 30 Methoden in einer Klasse klingen mir jedoch zu viel. Insgesamt stimme ich @Doc eher darin zu, dass es weniger riskiert, viele kleine Klassen zu haben, als eine einzelne Klasse mit Übergewicht. Vor allem, wenn man berücksichtigt, dass der Unterricht von Natur aus mit der Zeit an Gewicht zunimmt ...
Péter Török,
1
@ PéterTörök - Ich stimme zu, es sei denn, der Code könnte in eine Klasse umgestaltet werden, die intuitiv verwendet werden kann und Code wiederverwendet, anstatt Funktionen zu duplizieren, wie ich es vom OP erwarte.
SoylentGray
1
Vielleicht würde die Anwendung von MVC das alles klären: drei Ansichten, ein Modell (Ledger) und wahrscheinlich drei Controller.
Kevin Cline

Antworten:

19

@YannisRizos hier zitieren :

Dies ist ein instinktiver Ansatz, der wahrscheinlich richtig ist, da die Geschäftslogik der Verwaltung von Ledgern mit größerer Wahrscheinlichkeit geändert werden kann. In diesem Fall wäre es viel einfacher, eine Klasse als drei zu unterhalten.

Ich bin zumindest jetzt nach ungefähr 30 Jahren Programmiererfahrung anderer Meinung. Kleinere Klassen sind meiner Meinung nach in fast allen Fällen, die mir in Fällen wie diesem einfallen, besser zu pflegen . Je mehr Funktionen Sie in eine Klasse einordnen, desto weniger Struktur haben Sie und die Qualität Ihres Codes verschlechtert sich - jeden Tag ein bisschen. Wenn ich Tarun richtig verstanden habe, jede dieser 3 Operationen

LedgerLandingPage

CreateNewLedgerEntryPage

ViewLedgerEntryPage

In einem Anwendungsfall besteht jede dieser Klassen aus mehr als einer Funktion zur Ausführung der zugehörigen Aufgabe, und jede kann separat entwickelt und getestet werden. Das Erstellen von Klassen für jedes von ihnen gruppiert also Dinge, die zusammengehören, was es viel einfacher macht, den zu ändernden Teil des Codes zu identifizieren, wenn sich etwas ändern soll.

Wenn diese drei Klassen gemeinsame Funktionen haben, gehören sie entweder zu einer gemeinsamen Basisklasse, der LedgerKlasse selbst (ich nehme an, Sie haben zumindest eine für die CRUD-Operationen) oder zu einer separaten Hilfsklasse.

Wenn Sie mehr Argumente benötigen, um viele kleinere Klassen zu erstellen, empfehlen wir Ihnen, sich das Buch "Clean Code" von Robert Martin anzusehen .

Doc Brown
quelle
Das ist der ganze Grund, warum ich mir drei verschiedene Klassen ausgedacht habe, anstatt alles in einer Klasse zu pushen. Da es unter diesen keine gemeinsame Funktionalität gibt, habe ich keine gemeinsame Klasse. Danke für den Link.
Tarun
2
"Kleinere Klassen sind in fast allen Fällen, die mir einfallen, besser zu unterhalten." Ich denke nicht einmal, dass Clean Code so weit gehen würde. Würden Sie wirklich argumentieren, dass es in einem MVC-Muster beispielsweise einen Controller pro Seite (oder Anwendungsfall) geben sollte? In Refactoring hat Fowler zwei Code-Gerüche zusammen: einen, der sich auf viele Gründe bezieht, eine Klasse (SRP) zu ändern, und einen, der sich darauf bezieht, viele Klassen für eine Änderung bearbeiten zu müssen.
pdr
@pdr, das OP gibt an, dass es keine gemeinsame Funktionalität zwischen diesen Klassen gibt, daher ist es (für mich) schwer vorstellbar, dass Sie mehr als eine berühren müssen, um eine einzelne Änderung vorzunehmen.
Péter Török
@pdr: Wenn Sie zu viele Klassen haben, um sie für eine Änderung zu bearbeiten, ist dies meistens ein Zeichen dafür, dass Sie die allgemeinen Funktionen nicht an einer anderen Stelle neu ausgerichtet haben. Aber im obigen Beispiel würde dies wahrscheinlich zu einer vierten Klasse führen und nicht nur zu einer.
Doc Brown
2
@pdr: Übrigens, hast du tatsächlich "Clean Code" gelesen? Bob Martin geht sehr weit, um den Code in kleinere Einheiten aufzuteilen.
Doc Brown
11

Es gibt keine endgültige Umsetzung des Grundsatzes der einheitlichen Verantwortung oder eines anderen Grundsatzes. Sie sollten entscheiden, was wahrscheinlicher zu ändern ist.

Wie @Karpie in einer früheren Antwort schreibt :

Sie benötigen nur eine Klasse, und die einzige Verantwortung dieser Klasse besteht in der Verwaltung von Ledgern.

Dies ist ein instinktiver Ansatz, der wahrscheinlich richtig ist, da die Geschäftslogik der Verwaltung von Ledgern mit größerer Wahrscheinlichkeit geändert werden kann. In diesem Fall wäre es viel einfacher, eine Klasse als drei zu unterhalten.

Das sollte aber fallweise geprüft und entschieden werden. Sie sollten sich nicht wirklich um Bedenken mit winzigen Änderungsmöglichkeiten kümmern und sich darauf konzentrieren, das Prinzip darauf anzuwenden, was sich mit größerer Wahrscheinlichkeit ändert. Wenn die Logik der Verwaltung von Ledgern ein mehrschichtiger Prozess ist und Layer dazu neigen, sich unabhängig zu ändern, oder wenn es sich um Bedenken handelt, die grundsätzlich getrennt sein sollten (Logik und Darstellung), sollten Sie separate Klassen verwenden. Es ist ein Spiel der Wahrscheinlichkeit.

Eine ähnliche Frage zum Thema übermäßiger Gebrauch von Gestaltungsprinzipien, die Sie meines Erachtens interessant finden: Gestaltungsmuster: Wann man sie verwendet und wann man aufhört, alles mit Mustern zu tun

yannis
quelle
1
SRP ist meines Erachtens nicht wirklich ein Entwurfsmuster.
Doc Brown
@ DocBrown Natürlich kein Designmuster, aber die Diskussion über die Frage ist äußerst relevant ... Guter Fang, ich habe die Antwort aktualisiert
yannis
4

Untersuchen wir diese Klassen:

  • LedgerLandingPage: Die Rolle dieser Klasse besteht darin, eine Liste von Ledgern anzuzeigen. Wenn die Anwendung wächst, möchten Sie wahrscheinlich Methoden zum Sortieren, Filtern, Hervorheben und transparenten Zusammenführen von Daten aus anderen Datenquellen hinzufügen.

  • ViewLedgerEntryPage: Diese Seite zeigt das Hauptbuch im Detail. Scheint ziemlich direkt. Solange die Daten einfach sind. Hier können Sie das Ledger aufheben. Sie können es auch korrigieren. Oder kommentieren Sie es oder hängen Sie eine Datei, eine Quittung oder eine externe Buchungsnummer oder was auch immer an. Und wenn Sie die Bearbeitung zulassen, möchten Sie auf jeden Fall einen Verlauf anzeigen.

  • CreateLedgerEntryPage: Wenn der ViewLedgerEntryPageüber die volle Bearbeitungsfunktion verfügt, kann die Verantwortung für diese Klasse möglicherweise durch Bearbeiten eines "leeren Eintrags" erfüllt werden, was möglicherweise sinnvoll ist oder nicht.

Diese Beispiele scheinen etwas weit hergeholt zu sein, aber der Punkt ist, dass wir hier von einem UX-Feature sprechen. Ein einzelnes Merkmal ist definitiv eine eindeutige, einzelne Verantwortung. Da sich die Anforderungen für diese Funktion ändern können und sich die Anforderungen für zwei verschiedene Funktionen in zwei entgegengesetzte Richtungen ändern können, wünschen Sie sich, Sie wären von Anfang an bei der SRP geblieben.
Umgekehrt können Sie eine Fassade jedoch immer auf drei Klassen aufteilen, wenn eine einzige Benutzeroberfläche aus einem beliebigen Grund bequemer ist.


Es gibt so etwas wie SRP-Überbeanspruchung : Wenn Sie ein Dutzend Klassen benötigen, um den Inhalt einer Datei zu lesen, können Sie davon ausgehen, dass Sie genau das tun.

Wenn Sie die SRP von der anderen Seite betrachten, heißt es eigentlich, dass eine einzelne Klasse für eine einzelne Verantwortung sorgen sollte. Bitte beachten Sie jedoch, dass Verantwortlichkeiten nur innerhalb von Abstraktionsebenen bestehen. Es ist also durchaus sinnvoll, dass eine Klasse einer höheren Abstraktionsebene ihre Verantwortung wahrnimmt, indem sie Arbeit an eine Komponente einer niedrigeren Ebene delegiert, was am besten durch Abhängigkeitsinversion erreicht wird .

back2dos
quelle
Ich glaube sogar, ich hätte die Verantwortung dieser Klassen nicht besser beschreiben können als Sie.
Tarun
2

Haben diese Klassen nur einen Grund, sich zu ändern?

Wenn Sie es (noch) nicht wissen, sind Sie möglicherweise in die spekulative Design- / Over-Engineering-Falle geraten (zu viele Klassen, zu komplexe Designmuster angewendet). Sie haben YAGNI nicht zufriedengestellt . In frühen Phasen des Software-Lebenszyklus sind (einige) Anforderungen möglicherweise nicht klar. Daher ist die Richtung der Änderung für Sie derzeit nicht sichtbar. Wenn Sie das bemerken, behalten Sie es in einer oder einer minimalen Anzahl von Klassen. Dies ist jedoch mit einer Haftung verbunden: Sobald die Anforderungen und die Richtung der Änderung klarer sind, müssen Sie das aktuelle Design überdenken und eine Umgestaltung vornehmen, um den Grund der Änderung zu erfüllen.

Wenn Sie bereits wissen, dass es drei verschiedene Gründe für eine Änderung gibt und dass sie diesen drei Klassen zugeordnet sind, haben Sie einfach die richtige SRP-Dosis angewendet. Auch dies ist mit einer gewissen Haftung verbunden: Wenn Sie sich irren oder zukünftige Anforderungen sich unerwartet ändern, müssen Sie das aktuelle Design überdenken und ein Refactoring durchführen, um den Änderungsgrund zu erfüllen.

Der springende Punkt ist:

  • Behalten Sie die Treiber im Auge, um Veränderungen zu erzielen.
  • Wählen Sie ein flexibles Design, das überarbeitet werden kann.
  • Seien Sie bereit, den Code umzugestalten.

Wenn Sie diese Einstellung haben, werden Sie den Code ohne große Angst vor spekulativem Design / Over Engineering formen.

Es gibt jedoch immer den Faktor Zeit: Für viele Änderungen haben Sie nicht die Zeit, die Sie benötigen. Refactoring kostet Zeit und Mühe. Ich habe oft Situationen erlebt, in denen ich wusste, dass ich das Design ändern musste, aber aus Zeitgründen musste ich es verschieben. Dies wird passieren und kann nicht vermieden werden. Ich fand, dass es einfacher ist, unter Engineered Code Refactoring durchzuführen, als über Engineered Code. YAGNI gibt mir einen guten Leitfaden: Halten Sie im Zweifelsfall die Anzahl der Klassen und die Anwendung von Entwurfsmustern auf ein Minimum.

Theo Lenndorff
quelle
1

Es gibt drei Gründe, SRP als Grund für das Teilen einer Klasse aufzurufen:

  • Damit zukünftige Änderungen der Anforderungen einige Klassen unverändert lassen können
  • Damit kann ein Bug schneller isoliert werden
  • um die Klasse kleiner zu machen

Es gibt drei Gründe, eine Aufteilung einer Klasse abzulehnen:

  • Damit zukünftige Änderungen der Anforderungen nicht dazu führen, dass Sie dieselbe Änderung in mehreren Klassen vornehmen
  • Damit bei der Fehlersuche keine langen Wege der Indirektion zurückgelegt werden müssen
  • Kapselungstechniken (Eigenschaften, Freunde, was auch immer) zu widerstehen, die Informationen oder Methoden für alle zugänglich machen, wenn sie nur von einer verwandten Klasse benötigt werden

Wenn ich mir Ihren Fall ansehe und mir Änderungen vorstelle, werden einige von ihnen Änderungen an mehreren Klassen hervorrufen (z. B. ein Feld zum Hauptbuch hinzufügen, alle drei müssen geändert werden), viele jedoch nicht - zum Beispiel das Hinzufügen einer Sortierung zur Liste der Hauptbücher oder Ändern der Validierungsregeln beim Hinzufügen eines neuen Ledger-Eintrags. Die Reaktion Ihres Kollegen ist wahrscheinlich, weil es schwierig ist, herauszufinden, wo Sie nach einem Fehler suchen können. Wenn etwas auf der Liste der Hauptbücher nicht stimmt, liegt es daran, dass es falsch hinzugefügt wurde, oder stimmt etwas mit der Liste nicht? Wenn es eine Diskrepanz zwischen der Zusammenfassung und den Details gibt, was ist falsch?

Es ist auch möglich, dass Ihr Kollege der Ansicht ist, dass die Änderungen der Anforderungen, die bedeuten, dass Sie alle drei Klassen ändern müssen, viel wahrscheinlicher sind als die Änderungen, bei denen Sie nur eine ändern müssen. Das könnte ein wirklich nützliches Gespräch sein.

Kate Gregory
quelle
Gut, eine andere Perspektive zu sehen
Tarun
0

Ich denke, wenn das Hauptbuch eine Tabelle in db war, schlage ich vor, diese Thress-Aufgabe einer Klasse zuzuordnen (z. B. DAO). Wenn das Hauptbuch mehr Logik hat und keine Tabelle in db war, schlage ich vor, wir sollten mehr Klassen erstellen, um diese Aufgaben auszuführen (kann zwei oder mehr sein) und bieten eine Fassadenklasse, die einfach für den Kunden zu tun ist.

Mark xie
quelle
Nun, Ledger ist eine Funktion in der Benutzeroberfläche und es gibt viele damit verbundene Operationen, die von verschiedenen Seiten aus ausgeführt werden können.
Tarun
0

IMHO sollten Sie überhaupt keine Klassen verwenden. Hier gibt es keine Datenabstraktionen. Die Ledger sind ein konkreter Datentyp. Die Methoden zur Bearbeitung und Anzeige sind Funktionen und Prozeduren. Es wird sicherlich viele häufig verwendete Funktionen geben, die gemeinsam genutzt werden können, z. B. das Formatieren eines Datums. In den Anzeige- und Auswahlroutinen ist nur wenig Platz, um Daten zu abstrahieren und in einer Klasse zu verbergen.

Es gibt einige Fälle, in denen der Status in einer Klasse für die Ledger-Bearbeitung ausgeblendet werden muss.

Unabhängig davon, ob Sie die SRP missbrauchen oder nicht, missbrauchen Sie etwas Grundlegenderes: Sie überbeanspruchen die Abstraktion. Es ist ein typisches Problem, das durch die mythische Vorstellung hervorgerufen wird, dass OO ein vernünftiges Entwicklungsparadigma ist.

Die Programmierung ist zu 90% konkret: Die Verwendung der Datenabstraktion sollte selten und sorgfältig geplant werden. Funktionale Abstraktion sollte dagegen häufiger vorkommen, da Sprachdetails und die Wahl der Algorithmen von der Semantik der Operation getrennt werden müssen.

Yttrill
quelle
-1

Sie benötigen nur eine Klasse, und die einzige Verantwortung dieser Klasse besteht in der Verwaltung von Ledgern .

Sevenseacat
quelle
2
Für mich zeigt eine '... Manager'-Klasse normalerweise, dass man sich nicht die Mühe machen konnte, sie weiter aufzuschlüsseln. Was verwaltet die Klasse? Die Präsentation, die Ausdauer?
Sebastiangeiger
-1. 3 oder mehr Use Cases in eine Klasse einzuteilen, ist genau das, was die SRP zu vermeiden versucht - und das aus guten Gründen.
Doc Brown
@sebastiangeiger Also, was machen die drei Klassen des OP? Präsentation oder Ausdauer?
Sevenseacat
@ Karpie Ich hoffe ehrlich Präsentation. Zugegeben, das Beispiel ist nicht wirklich gut, aber ich würde erwarten, dass "... Seite" auf einer Website etwas Ähnliches wie eine Ansicht tut.
Sebastiangeiger
2
Tatsächlich brauchen Sie nur eine Klasse für die gesamte Anwendung, mit der alleinigen Verantwortung , die Benutzer glücklich zu machen ;-)
Péter Török