Ich arbeite in einem Team, in dem der Teamleiter ein virulenter Verfechter der SOLID-Entwicklungsprinzipien ist. Es fehlt ihm jedoch viel Erfahrung, um komplexe Software aus der Tür zu bekommen.
Wir haben eine Situation, in der er SRP auf eine bereits recht komplexe Codebasis angewendet hat, die jetzt sehr stark fragmentiert und schwer zu verstehen und zu debuggen ist.
Wir haben jetzt ein Problem nicht nur mit der Codefragmentierung, sondern auch mit der Kapselung, da Methoden innerhalb einer Klasse, die möglicherweise privat oder geschützt waren, als „Änderungsgrund“ eingestuft und in öffentliche oder interne Klassen und Schnittstellen extrahiert wurden, die entspricht nicht den Kapselungszielen der Anwendung.
Wir haben einige Klassenkonstruktoren, die über 20 Schnittstellenparameter übernehmen, sodass unsere IoC-Registrierung und -Auflösung zu einem Monster für sich wird.
Ich möchte wissen, ob es einen Ansatz gibt, der sich von SRP unterscheidet und mit dem wir einige dieser Probleme beheben können. Ich habe gelesen, dass es nicht gegen SOLID verstößt, wenn ich eine Reihe leerer gröberkörniger Klassen erstelle, die eine Reihe eng verwandter Klassen "umschließen", um einen zentralen Zugriffspunkt auf die Summe ihrer Funktionen zu bieten (dh eine kleinere nachzubilden) übermäßige Implementierung der SRP-Klasse).
Abgesehen davon kann ich mir keine Lösung vorstellen, die es uns ermöglicht, unsere Entwicklungsbemühungen pragmatisch fortzusetzen und gleichzeitig alle zufrieden zu stellen.
Irgendwelche Vorschläge ?
quelle
ISomething
) zu verwenden. IMHO, diese Ansätze sind viel einfacher zu handhaben als die Abhängigkeitsinjektion und führen zu besser lesbarem Code.Antworten:
Wenn Ihre Klasse 20 Parameter im Konstruktor hat, hört es sich nicht so an, als ob Ihr Team genau weiß, was SRP ist. Wenn Sie eine Klasse haben, die nur eines tut, wie hat sie 20 Abhängigkeiten? Das ist wie eine Angeltour und das Mitbringen einer Angelrute, einer Tackle Box, Quiltzubehör, einer Bowlingkugel, Nunchaks, eines Flammenwerfers usw. Wenn Sie all das brauchen, um angeln zu gehen, gehen Sie nicht nur angeln.
Das heißt, SRP kann, wie die meisten Prinzipien da draußen, übertrieben angewendet werden. Wenn Sie eine neue Klasse zum Inkrementieren von ganzen Zahlen erstellen, dann ist das vielleicht eine einzelne Verantwortung, aber kommen Sie schon. Das ist lächerlich. Wir neigen dazu zu vergessen, dass Dinge wie die SOLID-Prinzipien für einen Zweck da sind. SOLID ist ein Mittel zum Zweck, kein Selbstzweck. Das Ende ist Wartbarkeit . Wenn Sie das Prinzip der Einzelverantwortung so detailliert anwenden möchten, ist dies ein Indikator dafür, dass der Eifer für SOLID das Team für das Ziel von SOLID blind gemacht hat.
Also, ich denke, was ich sage, ist ... Die SRP ist nicht Ihr Problem. Es ist entweder ein Missverständnis der SRP oder eine unglaublich granulare Anwendung. Versuchen Sie, Ihr Team dazu zu bringen, die Hauptsache zur Hauptsache zu machen. Und das Wichtigste ist die Wartbarkeit.
BEARBEITEN
Bringen Sie die Benutzer dazu, Module so zu gestalten, dass sie einfacher zu bedienen sind. Stellen Sie sich jede Klasse als Mini-API vor. Überlegen Sie zunächst, wie diese Klasse verwendet werden soll, und implementieren Sie sie anschließend. Denken Sie nicht nur: "Was muss diese Klasse tun?" Die SRP hat eine große Tendenz, die Verwendung von Klassen zu erschweren, wenn Sie nicht viel über die Benutzerfreundlichkeit nachdenken.
BEARBEITEN 2
Wenn Sie nach Tipps zum Refactoring suchen, können Sie mit den von Ihnen vorgeschlagenen Schritten beginnen: Erstellen Sie grobkörnigere Klassen, um mehrere andere Klassen zusammenzufassen. Stellen Sie sicher, dass die grobkörnigere Klasse immer noch an der SRP festhält , jedoch auf einer höheren Ebene. Dann haben Sie zwei Alternativen:
Wenn Sie mit dem Refactoring fertig sind (aber bevor Sie sich auf das Repository festlegen), überprüfen Sie Ihre Arbeit und fragen Sie sich, ob das Refactoring tatsächlich zu einer Verbesserung der Wartbarkeit und Benutzerfreundlichkeit geführt hat.
quelle
Customer
Klasse zusammenfassen können und deren Code besser gewartet werden kann. Beispiele finden Sie hier: codemonkeyism.com/…Ich denke, es ist in Martin Fowlers Refactoring, dass ich einmal eine Gegenregel zu SRP gelesen habe, die definiert, wo es zu weit geht. Es gibt eine zweite wichtige Frage: "Hat jede Klasse nur einen Grund, sich zu ändern?" und das heißt: "Betrifft jede Änderung nur eine Klasse?"
Wenn die Antwort auf die erste Frage in jedem Fall "Ja" lautet, die zweite Frage jedoch "Nicht einmal annähernd" lautet, müssen Sie sich erneut überlegen, wie Sie SRP implementieren.
Wenn Sie beispielsweise ein Feld zu einer Tabelle hinzufügen, müssen Sie eine DTO- und eine Validierungsklasse sowie eine Persistenzklasse und ein Ansichtsmodellobjekt usw. ändern, und dann haben Sie ein Problem erstellt. Vielleicht sollten Sie überdenken, wie Sie SRP implementiert haben.
Vielleicht haben Sie gesagt, dass das Hinzufügen eines Felds der Grund ist, das Kundenobjekt zu ändern, aber das Ändern der Persistenzschicht (z. B. von einer XML-Datei in eine Datenbank) ist ein weiterer Grund, das Kundenobjekt zu ändern. Sie möchten also auch ein CustomerPersistence-Objekt erstellen. Aber wenn Sie dies so tun, dass das Hinzufügen eines Feldes NOCH eine Änderung des CustomerPersisitence-Objekts erfordert, worum ging es dann? Sie haben immer noch ein Objekt mit zwei Änderungsgründen - es ist einfach kein Kunde mehr.
Wenn Sie jedoch ein ORM einführen, können Sie die Klassen möglicherweise so einrichten, dass beim Hinzufügen eines Felds zum DTO automatisch die zum Lesen dieser Daten verwendete SQL geändert wird. Dann haben Sie gute Gründe, die beiden Anliegen zu trennen.
Zusammenfassend ist hier, was ich tendenziell tue: Wenn es ein ungefähres Gleichgewicht zwischen der Häufigkeit gibt, mit der ich "Nein" sage, gibt es mehr als einen Grund, dieses Objekt zu ändern, und der Häufigkeit, mit der ich "Nein" sage, wird diese Änderung durchgeführt mehr als ein Objekt betreffen ", dann denke ich, dass ich das richtige Gleichgewicht zwischen SRP und Fragmentierung habe. Aber wenn beide immer noch hoch sind, frage ich mich, ob ich Bedenken auf andere Weise trennen kann.
quelle
Nur weil ein System komplex ist, heißt das noch lange nicht, dass Sie es komplizieren müssen . Wenn Sie eine Klasse haben, die zu viele Abhängigkeiten (oder Mitbearbeiter) hat:
... dann wurde es viel zu kompliziert und Sie folgen SRP nicht wirklich , oder? Ich wette, wenn Sie aufschreiben, was
MyAwesomeClass
auf einer CRC-Karte funktioniert , passt es nicht auf eine Karteikarte, oder Sie müssen in wirklich winzigen unleserlichen Buchstaben schreiben.Was Sie hier gesehen haben, ist, dass Ihre Jungs stattdessen nur das Prinzip der Schnittstellentrennung befolgt haben und es vielleicht auf ein Extrem gebracht haben, aber das ist eine ganz andere Geschichte. Sie könnten argumentieren, dass es sich bei den Abhängigkeiten um Domänenobjekte handelt (was jedoch vorkommt). Eine Klasse, die 20 Domänenobjekte gleichzeitig verarbeitet, ist etwas zu umfangreich.
TDD liefert Ihnen einen guten Indikator dafür, wie viel eine Klasse leistet. Unverblümt; Wenn eine Testmethode Setup-Code enthält, dessen Schreiben ewig dauert (auch wenn Sie die Tests überarbeiten), müssen Sie
MyAwesomeClass
wahrscheinlich zu viel tun.Wie lösen Sie dieses Rätsel? Sie verlagern die Zuständigkeiten auf andere Klassen. Es gibt einige Schritte, die Sie für eine Klasse ausführen können, bei der dieses Problem auftritt:
Ein abstraktes Beispiel für die Umgestaltung von Verantwortlichkeiten
Lassen Sie
C
eine Klasse sein , die mehrere Abhängigkeiten hatD1
,D2
,D3
,D4
dass Sie Refactoring müssen weniger verwenden. Wenn wir herausfinden, welche MethodenC
die Abhängigkeiten aufrufen, können wir eine einfache Liste davon erstellen:D1
-performA(D2)
,performB()
D2
-performD(D1)
D3
-performE()
D4
-performF(D3)
Wenn wir uns die Liste ansehen, können wir das sehen
D1
undD2
sind miteinander verwandt, da die Klasse sie irgendwie zusammen braucht. Wir können auch sehen, dassD4
BedürfnisseD3
. Wir haben also zwei Gruppierungen:Group 1
-D1
<->D2
Group 2
-D4
->D3
Die Gruppierungen sind ein Indikator dafür, dass die Klasse jetzt zwei Verantwortlichkeiten hat.
Group 1
- Eine zur Behandlung der aufrufenden zwei Objekte, die sich gegenseitig benötigen. Vielleicht können Sie Ihre KlasseC
die Notwendigkeit beseitigen lassen, beide Abhängigkeiten zu behandeln, und eine von ihnen kann stattdessen diese Aufrufe behandeln. In dieser Gruppierung ist es offensichtlich,D1
dass ein Verweis auf haben könnteD2
.Group 2
- Die andere Verantwortung benötigt ein Objekt, um ein anderes aufzurufen. Kann nichtD4
behandelnD3
statt Klasse? Dann können wir wahrscheinlichD3
aus der KlasseC
streichen, indemD4
wir stattdessen die Anrufe erledigen lassen .Nehmen Sie meine Antwort nicht in Stein gemeißelt, da das Beispiel sehr abstrakt ist und viele Annahmen macht. Ich bin mir ziemlich sicher, dass es weitere Möglichkeiten gibt, dies umzugestalten, aber zumindest könnten die Schritte Ihnen dabei helfen, eine Art Prozess zu entwickeln, um Verantwortlichkeiten zu verschieben, anstatt die Klassen aufzuteilen.
Bearbeiten:
Zu den Kommentaren sagt @Emmad Karem :
Es ist richtig, dass DAO-Objekte in der Regel viele Parameter haben, die Sie in Ihrem Konstruktor festlegen müssen, und die Parameter sind normalerweise einfache Typen wie Zeichenfolgen. Im Beispiel einer
Customer
Klasse können Sie ihre Eigenschaften jedoch auch in anderen Klassen gruppieren, um die Arbeit zu vereinfachen. B. eineAddress
Klasse mit Straßen und eineZipcode
Klasse, die die Postleitzahl enthält und die Geschäftslogik wie die Datenüberprüfung übernimmt:Diese Sache wird im Blog-Beitrag "Niemals, niemals, niemals, niemals in Java (oder zumindest oft) String verwenden" weiter besprochen . Alternativ zur Verwendung von Konstruktoren oder statischen Methoden, um die Erstellung der Unterobjekte zu vereinfachen, können Sie ein Fluid Builder-Muster verwenden .
quelle
Ich bin mit allen Antworten über SRP einverstanden und wie es zu weit geführt werden kann. In Ihrem Beitrag erwähnen Sie, dass Sie aufgrund von "Über-Refactoring" zur Einhaltung von SRP festgestellt haben, dass die Verkapselung unterbrochen oder geändert wurde. Das einzige, was für mich funktioniert hat, ist, immer an den Grundlagen festzuhalten und genau das zu tun, was erforderlich ist, um ein Ziel zu erreichen.
Bei der Arbeit mit Legacy-Systemen ist der "Enthusiasmus", alles zu reparieren, um es zu verbessern, in Team-Leads normalerweise recht hoch, insbesondere bei denjenigen, die für diese Rolle neu sind. SOLID, hat nur kein SRP - Das ist nur das S. Vergewissern Sie sich, dass Sie, wenn Sie SOLID folgen, auch das OLID nicht vergessen.
Ich arbeite gerade an einem Legacy-System und wir sind am Anfang einen ähnlichen Weg gegangen. Was für uns funktioniert hat, war die Entscheidung eines kollektiven Teams , das Beste aus beiden Welten zu machen - SOLID und KISS (Keep It Simple Stupid). Gemeinsam haben wir wichtige Änderungen an der Codestruktur besprochen und den gesunden Menschenverstand bei der Anwendung verschiedener Entwicklungsprinzipien angewendet. Sie eignen sich hervorragend als Richtlinien, nicht als "Gesetze der S / W-Entwicklung". Im Team geht es nicht nur um den Teamleiter, sondern um alle Entwickler im Team. Was mir immer geholfen hat, ist, alle in einen Raum zu locken und gemeinsame Richtlinien zu entwickeln, denen sich Ihr gesamtes Team verpflichtet.
Wenn Sie ein VCS verwenden und Ihrer Anwendung nicht zu viele neue Funktionen hinzugefügt haben, können Sie jederzeit zu einer Codeversion zurückkehren, die das gesamte Team für verständlich, lesbar und wartbar hält. Ja! Ich bitte Sie, die Arbeit wegzuwerfen und von vorne zu beginnen. Dies ist besser, als zu "reparieren", was kaputt war, und es auf etwas zurückzubringen, das bereits existierte.
quelle
Die Antwort ist vor allem Wartbarkeit und Klarheit des Codes. Für mich bedeutet das, weniger Code zu schreiben , nicht mehr. Weniger Abstraktionen, weniger Schnittstellen, weniger Optionen, weniger Parameter.
Wann immer ich eine Code-Umstrukturierung auswerte oder eine neue Funktion hinzufüge, denke ich darüber nach, wie viel Boilerplate im Vergleich zur tatsächlichen Logik benötigt wird. Wenn die Antwort mehr als 50% beträgt, bedeutet dies wahrscheinlich, dass ich darüber nachdenke.
Neben SRP gibt es viele andere Entwicklungsstile. In deinem Fall fehlt es definitiv an YAGNI.
quelle
Viele der Antworten hier sind wirklich gut, konzentrieren sich jedoch auf die technische Seite dieser Ausgabe. Ich füge einfach hinzu, dass es sich so anhört, als ob der Entwickler versucht, dem SRP-Sound zu folgen, als ob er tatsächlich gegen das SRP verstößt.
Sie können Bobs Blog hier sehen über diese Situation sehen, aber er argumentiert, dass, wenn eine Verantwortlichkeit über mehrere Klassen hinweg verschmiert wird, die Verantwortlichkeits-SRP verletzt wird, weil sich diese Klassen parallel ändern. Ich vermute, Ihr Entwickler würde das Design am Anfang von Bobs Blog wirklich mögen, und es könnte ein bisschen enttäuscht sein, es auseinandergerissen zu sehen. Insbesondere, weil es gegen das "Common Closure Principle" verstößt - Dinge, die sich gemeinsam ändern, bleiben zusammen.
Denken Sie daran, dass sich die SRP auf "Änderungsgrund" bezieht und nicht auf "eine Sache tun", und dass Sie sich nicht mit diesem Änderungsgrund befassen müssen, bis eine Änderung tatsächlich eintritt. Der zweite bezahlt für die Abstraktion.
Jetzt gibt es das zweite Problem - den "virulenten Befürworter der SOLID-Entwicklung". Es hört sich sicher nicht so an, als ob Sie eine großartige Beziehung zu diesem Entwickler haben. Daher sind alle Versuche, ihn / sie von den Problemen in der Codebasis zu überzeugen, ratlos. Sie müssen die Beziehung reparieren, damit Sie eine echte Diskussion über die Probleme führen können. Was ich empfehlen würde, ist Bier.
Nein im Ernst - wenn Sie nicht trinken, gehen Sie in ein Café. Verlassen Sie das Büro und entspannen Sie sich an einem Ort, an dem Sie ungezwungen über diese Dinge sprechen können. Anstatt zu versuchen, bei einem Meeting ein Argument zu gewinnen, das Sie nicht wollen, haben Sie eine Diskussion, die irgendwo Spaß macht. Versuchen Sie zu erkennen, dass dieser Entwickler, der Sie in den Wahnsinn treibt, ein tatsächlich funktionierender Mensch ist, der versucht, Software "aus der Tür" zu holen und keinen Mist versenden möchte. Da Sie wahrscheinlich diese Gemeinsamkeiten teilen, können Sie mit der Erörterung beginnen, wie Sie das Design verbessern können, während Sie dennoch die SRP einhalten.
Wenn Sie beide anerkennen können, dass die SRP eine gute Sache ist, dass Sie Aspekte nur unterschiedlich interpretieren, können Sie wahrscheinlich produktive Gespräche beginnen.
quelle
Ich stimme Ihrer Entscheidung des Teamleiters [update = 2012.05.31] zu, dass SRP im Allgemeinen eine gute Idee ist. Aber ich stimme dem Kommentar von @ Spoike -s voll und ganz zu, dass ein Konstruktor mit 20 Schnittstellenargumenten viel zu viel ist. [/ Update]:
Die Einführung von SRP mit IoC verschiebt die Komplexität von einer "Multi-Responsible-Klasse" zu vielen SRP-Klassen und eine viel kompliziertere Initialisierung zum Nutzen von
Ich fürchte, Sie können die Codefragmentierung nicht reduzieren, ohne srp zu opfern.
Sie können den Schmerz der Codeinitialisierung jedoch "lindern", indem Sie eine syntaktische Zuckerklasse implementieren, die die Komplexität der Initialisierung in einem Konstruktor verbirgt.
quelle