Was ist bei der Anwendung des Grundsatzes der einheitlichen Verantwortung eine „Verantwortung“?

198

Es scheint ziemlich klar zu sein, dass "Prinzip der einheitlichen Verantwortung" nicht "nur eine Sache" bedeutet. Dafür sind Methoden da.

public Interface CustomerCRUD
{
    public void Create(Customer customer);
    public Customer Read(int CustomerID);
    public void Update(Customer customer);
    public void Delete(int CustomerID);
}

Bob Martin sagt, dass "Klassen nur einen Grund haben sollten, sich zu ändern." Aber es ist schwierig, sich Gedanken zu machen, wenn Sie ein neuer Programmierer bei SOLID sind.

Ich schrieb eine Antwort auf eine andere Frage , in der ich vorschlug, dass Verantwortlichkeiten wie Berufsbezeichnungen sind, und tanzte um das Thema herum, indem ich eine Restaurantmetapher verwendete, um meinen Standpunkt zu veranschaulichen. Aber das formuliert immer noch keine Prinzipien, die jemand verwenden könnte, um die Verantwortlichkeiten seiner Klassen zu definieren.

Also, wie machst du das? Wie legen Sie fest, welche Verantwortlichkeiten jede Klasse haben soll, und wie definieren Sie eine Verantwortlichkeit im Kontext der SRP?

Robert Harvey
quelle
28
Post to Code Review und auseinandergerissen werden :-D
Jörg W Mittag
8
@ JörgWMittag Hey, jetzt mach keine Angst :)
Flambino
118
Fragen wie diese von erfahrenen Mitgliedern zeigen , dass die Regeln und Prinzipien , die wir versuchen zu halten keineswegs einfach oder einfach . Sie sind [irgendwie] widersprüchlich und mystisch ... wie es ein gutes Regelwerk sein sollte. Und ich würde gerne glauben, dass Fragen wie diese demütig sind, und denjenigen Hoffnung geben, die sich hoffnungslos dumm fühlen. Danke, Robert!
Svidgen
41
Ich frage mich, ob diese Frage sofort heruntergestuft und als Duplikat markiert worden wäre, wenn sie von einem Noob gepostet worden wäre :)
Andrejs
9
@rmunn: oder mit anderen Worten - große Wiederholungen ziehen noch mehr Wiederholungen an, weil niemand grundlegende menschliche Vorurteile beim Stackexchange aufgehoben hat
Andrejs

Antworten:

117

Eine Möglichkeit, sich damit abzufinden, besteht darin, sich mögliche Änderungen der Anforderungen in zukünftigen Projekten vorzustellen und sich zu fragen, was Sie tun müssen, um sie umzusetzen.

Zum Beispiel:

Neue Geschäftsanforderung: Benutzer mit Sitz in Kalifornien erhalten einen Sonderrabatt.

Beispiel für eine "gute" Änderung: Ich muss den Code in einer Klasse ändern, die Rabatte berechnet.

Beispiel für fehlerhafte Änderungen: Ich muss Code in der Benutzerklasse ändern, und diese Änderung wirkt sich kaskadierend auf andere Klassen aus, die die Benutzerklasse verwenden, einschließlich Klassen, die nichts mit Preisnachlässen zu tun haben, z. B. Registrierung, Aufzählung und Verwaltung.

Oder:

Neue nicht funktionsfähige Anforderung: Wir werden Oracle anstelle von SQL Server verwenden

Beispiel für eine gute Änderung: Es muss nur eine einzelne Klasse in der Datenzugriffsebene geändert werden, die festlegt, wie die Daten in den DTOs beibehalten werden sollen.

Schlechte Änderung: Ich muss alle meine Business-Layer-Klassen ändern, da sie SQL Server-spezifische Logik enthalten.

Die Idee ist, den Footprint zukünftiger potenzieller Änderungen zu minimieren und Codeänderungen auf einen Codebereich pro Änderungsbereich zu beschränken.

Zumindest sollten Ihre Klassen logische Bedenken von physischen Bedenken trennen. Eine große Menge von Beispielen in der gefunden werden System.IONamespace: dort können wir eine verschiedene Arten von physikalischen Ströme (zB finden FileStream, MemoryStreamoder NetworkStream) und verschiedene Leser und Schreiber ( BinaryWriter, TextWriter) , dass die Arbeit auf einer logischen Ebene. Durch sie auf diese Weise zu trennen, vermeiden wir kombinatorische Explosion: statt die Notwendigkeit FileStreamTextWriter, FileStreamBinaryWriter, NetworkStreamTextWriter, NetworkStreamBinaryWriter, MemoryStreamTextWriter, und MemoryStreamBinaryWriter, man muss nur den Schreiber anschließen und den Strom und können Sie, was Sie wollen. Später können wir beispielsweise ein hinzufügen, XmlWriterohne es erneut für Speicher, Datei und Netzwerk separat implementieren zu müssen.

John Wu
quelle
34
Ich stimme dem vorausschauenden Denken zu, aber es gibt Prinzipien wie YAGNI und Methoden wie TDD, die das Gegenteil nahelegen.
Robert Harvey
87
YAGNI sagt uns, wir sollen keine Sachen bauen, die wir heute nicht brauchen. Es sagt nicht aus, Dinge nicht auf erweiterbare Weise zu erstellen. Siehe auch Open / Closed-Prinzip , das besagt, dass "Software-Entitäten (Klassen, Module, Funktionen usw.) für Erweiterungen offen, für Änderungen jedoch geschlossen sein sollten".
John Wu
18
@ JohnW: +1 nur für Ihren YAGNI-Kommentar. Ich kann nicht glauben, wie viel ich den Leuten erklären muss, dass YAGNI keine Entschuldigung ist, um ein starres, unflexibles System zu bauen, das nicht auf Veränderungen reagieren kann - ironischerweise das Gegenteil von dem, was SRP und die Open / Closed-Prinzipien anstreben.
Greg Burghardt
36
@JohnWu: Ich bin anderer Meinung, YAGNI sagt uns genau, wir sollen keine Sachen bauen, die wir heute nicht brauchen. Lesbarkeit und Tests zum Beispiel sind etwas, das ein Programm "heute" immer braucht, daher ist YAGNI niemals eine Ausrede, keine Struktur- und Einspritzpunkte hinzuzufügen. Sobald jedoch "Erweiterbarkeit" erhebliche Kosten verursacht, für die die Vorteile "heute" nicht offensichtlich sind, bedeutet YAGNI, diese Art von Erweiterbarkeit zu vermeiden, da letztere zu Überentwicklung führt.
Doc Brown
9
@JohnWu Wir haben von SQL 2008 auf 2012 umgestellt. Insgesamt mussten zwei Abfragen geändert werden. Und von SQL Auth zu Trusted? Warum wäre das überhaupt eine Codeänderung? es reicht aus, den connectionString in der Konfigurationsdatei zu ändern. Wieder YAGNI. YAGNI und SRP konkurrieren manchmal miteinander, und Sie müssen beurteilen, welches die besseren Kosten / Nutzen hat.
Andy
76

In der Praxis sind die Verantwortlichkeiten an die Dinge gebunden, die sich wahrscheinlich ändern werden. Daher gibt es leider keinen wissenschaftlichen oder formelmäßigen Weg, um zu dem zu gelangen, was eine Verantwortung ausmacht. Es ist ein Urteilsspruch.

Es geht darum, was sich Ihrer Erfahrung nach wahrscheinlich ändern wird.

Wir neigen dazu, die Sprache des Prinzips in einer hyperbolischen, wörtlichen, eifrigen Wut anzuwenden. Wir neigen dazu, Klassen aufzuteilen, weil sie sich ändern könnten oder in einer Richtung, die uns hilft, Probleme zu lösen. (Der letztere Grund ist nicht von Natur aus schlecht.) Aber die SRP existiert nicht für sich selbst; Es ist im Dienst der Erstellung von wartbarer Software.

Wenn die Abteilungen also nicht von wahrscheinlichen Änderungen getrieben werden , sind sie für das SRP 1 nicht wirklich in Betrieb, wenn YAGNI besser anwendbar ist. Beide dienen dem gleichen Endziel. Und beides ist eine Frage des Urteils - ein hoffentlich erfahrenes Urteil.

Wenn Onkel Bob darüber schreibt, schlägt er vor, dass wir an "Verantwortung" denken, wenn es darum geht, "wer nach der Änderung fragt". Mit anderen Worten, wir wollen nicht, dass Partei A ihren Arbeitsplatz verliert, weil Partei B um eine Änderung gebeten hat.

Wenn Sie ein Softwaremodul schreiben, möchten Sie sicherstellen, dass Änderungen nur von einer einzelnen Person oder vielmehr von einer einzelnen eng gekoppelten Gruppe von Personen stammen können, die eine einzelne eng definierte Geschäftsfunktion darstellen, wenn sie angefordert werden. Sie möchten Ihre Module von den Komplexitäten der Organisation als Ganzes isolieren und Ihre Systeme so gestalten, dass jedes Modul für die Anforderungen nur dieser einen Geschäftsfunktion verantwortlich ist (auf diese reagiert). ( Onkel Bob - Das Prinzip der alleinigen Verantwortung )

Gute und erfahrene Entwickler werden ein Gespür dafür haben, welche Änderungen wahrscheinlich sind. Und diese mentale Liste wird je nach Branche und Organisation etwas variieren.

Was macht eine Verantwortung in Ihrer Anwendung, auf Ihre speziellen Organisation, ist letztlich eine Frage des gewürzt Gericht. Es geht darum, was sich wahrscheinlich ändern wird. In gewisser Weise geht es darum, wem die interne Logik des Moduls gehört.


1. Um es klar auszudrücken, heißt das nicht, dass es sich um schlechte Spaltungen handelt. Dies können großartige Bereiche sein, die die Lesbarkeit von Code erheblich verbessern. Es bedeutet nur, dass sie nicht von der SRP gesteuert werden.

Svidgen
quelle
11
Beste Antwort und zitiert tatsächlich Onkel Bobs Gedanken. Was sich wahrscheinlich ändern wird, macht jeder eine große Sache über die E / A. "Was ist, wenn wir die Datenbank ändern?" oder "Was ist, wenn wir von XML zu JSON wechseln?" Ich denke, das ist normalerweise falsch. Die eigentliche Frage sollte lauten: "Was ist, wenn wir dieses int in ein float ändern, ein Feld hinzufügen und diesen String in eine Liste von Strings ändern müssen?"
user949300
2
Das ist Betrug. Die Einzelverantwortung für sich allein ist nur ein vorgeschlagener Weg zur "Isolation des Wandels". Wenn Sie erklären, dass Sie Änderungen isolieren müssen, um die Verantwortung "einzeln" zu halten, wird dies nicht empfohlen, sondern nur die Ursache der Anforderung erläutert.
Basilevs
6
@Basilevs Ich versuche, den Mangel, den Sie in dieser Antwort sehen, zu beheben - ganz zu schweigen von der Antwort von Onkel Bob! Aber vielleicht muss ich klarstellen, dass es bei SRP nicht darum geht, sicherzustellen, dass "eine Änderung" nur eine Klasse betrifft. Es geht darum sicherzustellen, dass jede Klasse nur auf "eine Änderung" reagiert. ... Es geht darum, Ihre Pfeile aus jeder Klasse an einen einzelnen Besitzer zu ziehen. Nicht von jedem Besitzer zu einer einzelnen Klasse.
Svidgen
2
Vielen Dank für Ihre pragmatische Antwort! Sogar Onkel Bob warnt vor dem eifrigen Festhalten an SOLID-Prinzipien in Agile Architecture . Ich habe das Zitat nicht griffbereit, aber er sagt im Grunde, dass die Aufteilung von Verantwortlichkeiten die Abstraktionsebene in Ihrem Code von Natur aus erhöht und dass jede Abstraktion mit Kosten verbunden ist. Stellen Sie also sicher, dass der Nutzen der Befolgung von SRP (oder anderen Prinzipien) die Kosten überwiegt Hinzufügen von mehr Abstraktion. (Fortsetzung nächster Kommentar)
Michael L.
4
Aus diesem Grund sollten wir das Produkt so früh und so oft wie möglich vor den Kunden stellen, damit Änderungen in unserem Design erzwungen werden und wir sehen können, welche Bereiche sich wahrscheinlich in diesem Produkt ändern. Außerdem warnt er, dass wir uns nicht vor jeder Art von Veränderung schützen können . Für jede Anwendung sind bestimmte Arten von Änderungen schwierig durchzuführen. Wir müssen sicherstellen, dass dies die Änderungen sind, die am wenigsten wahrscheinlich sind.
Michael L.
29

Ich folge "Klassen sollten nur einen Grund haben, sich zu ändern".

Für mich bedeutet dies, dass ich über ausgefeilte Konzepte nachdenke, die mir mein Produktbesitzer einfallen könnte ("Wir müssen Mobile unterstützen!", "Wir müssen in die Cloud!", "Wir müssen Chinesisch unterstützen!"). Gute Entwürfe beschränken die Auswirkungen dieser Systeme auf kleinere Bereiche und machen sie relativ einfach zu realisieren. Schlechte Designs bedeuten, viel Code zu verwenden und eine Reihe riskanter Änderungen vorzunehmen.

Erfahrung ist das Einzige, was ich gefunden habe, um die Wahrscheinlichkeit dieser verrückten Pläne richtig einzuschätzen - weil das Vereinfachen zwei andere erschweren könnte - und die Güte eines Entwurfs einzuschätzen. Erfahrene Programmierer können sich vorstellen, was sie tun müssen, um den Code zu ändern, was herumliegt, um sie in den Arsch zu beißen, und welche Tricks die Dinge vereinfachen. Erfahrene Programmierer haben ein gutes Gefühl dafür, wie verrückt sie sind, wenn der Produktbesitzer nach verrückten Dingen fragt.

Praktisch finde ich, dass Unit-Tests hier helfen. Wenn Ihr Code unflexibel ist, wird es schwierig sein, ihn zu testen. Wenn Sie keine Mocks oder anderen Testdaten SupportChineseeinfügen können, können Sie diesen Code wahrscheinlich nicht einfügen.

Eine weitere grobe Metrik ist der Höhenunterschied. Herkömmliche Stellplätze für Aufzüge lauten: "Wenn Sie mit einem Investor in einem Aufzug waren, können Sie ihm eine Idee verkaufen?". Startups müssen einfache, kurze Beschreibungen ihrer Aktivitäten haben - worauf sie sich konzentrieren. Ebenso sollten Klassen (und Funktionen) eine einfache Beschreibung ihrer Funktionsweise haben . Nicht "Diese Klasse implementiert einige Funktionen, sodass Sie sie in diesen bestimmten Szenarien verwenden können". Etwas, das Sie einem anderen Entwickler mitteilen können: "Diese Klasse erstellt Benutzer". Wenn Sie nicht , dass zu anderen Entwicklern kommunizieren können, sind Sie gehen Bugs zu bekommen.

Telastyn
quelle
Manchmal müssen Sie etwas implementieren, von dem Sie dachten, dass es eine unordentliche Änderung wäre, und es stellt sich als einfach heraus, oder ein kleiner Refaktor vereinfacht es und fügt gleichzeitig nützliche Funktionen hinzu. Aber ja, normalerweise kann man Probleme sehen.
16
Ich bin ein großer Befürworter der Idee "Elevator Pitch". Wenn es schwierig ist zu erklären, was eine Klasse in einem oder zwei Sätzen tut, befinden Sie sich in einem riskanten Gebiet.
Ivan
1
Sie berühren einen wichtigen Punkt: Die Wahrscheinlichkeit dieser verrückten Pläne variiert dramatisch von einem Projektbesitzer zum nächsten. Sie müssen sich nicht nur auf Ihre allgemeinen Erfahrungen verlassen, sondern auch darauf, wie gut Sie den Projektverantwortlichen kennen. Ich habe für Leute gearbeitet, die unsere Sprints auf eine Woche reduzieren wollten und trotzdem mitten im Sprint die Richtung wechseln wollten .
Kevin Krumwiede
1
Zusätzlich zu den offensichtlichen Vorteilen hilft Ihnen die Dokumentation Ihres Codes mithilfe von "Elevator Pitches" auch dabei, mit natürlicher Sprache darüber nachzudenken, was Ihr Code tut.
Alexander
1
@KevinKrumwiede Dafür eignen sich die Methoden "Huhn läuft mit abgeschnittenem Kopf herum" und "Wild Goose Chase"!
26

Niemand weiß. Zumindest können wir uns nicht auf eine Definition einigen. Das ist es, was SPR (und andere SOLID-Prinzipien) ziemlich umstritten macht.

Ich würde argumentieren, dass es eine der Fähigkeiten ist, die Softwareentwickler im Laufe ihrer Karriere erlernen müssen, um herauszufinden, was eine Verantwortung ist oder nicht. Je mehr Code Sie schreiben und überprüfen, desto mehr Erfahrung werden Sie haben, um festzustellen, ob es sich um eine einzelne oder mehrere Aufgaben handelt. Oder wenn eine einzelne Verantwortung auf verschiedene Teile des Codes aufgeteilt ist.

Ich würde argumentieren, dass der Hauptzweck von SRP nicht darin besteht, eine harte Regel zu sein. Es soll uns daran erinnern, auf die Kohäsion im Code zu achten und immer bewusst zu bestimmen, welcher Code kohäsiv ist und welcher nicht.

Euphorisch
quelle
20
Neue Programmierer neigen dazu, SOLID wie eine Reihe von Gesetzen zu behandeln, was es nicht ist. Es ist nur eine Ansammlung von guten Ideen, die helfen sollen, das Design im Unterricht zu verbessern. Leider neigen die Menschen dazu, diese Prinzipien viel zu ernst zu nehmen. Ich habe kürzlich eine Stellenanzeige gesehen, in der SOLID als eine der Stellenanforderungen genannt wurde.
Robert Harvey
9
+42 für den letzten Absatz. Wie @RobertHarvey sagt, sollten Dinge wie SPR, SOLID und YAGNI nicht als " absolute Regeln " verstanden werden, sondern als allgemeine Grundsätze für "gute Ratschläge". Zwischen ihnen (und anderen) sind die Ratschläge manchmal widersprüchlich, aber wenn Sie diese Ratschläge (im Gegensatz zur Befolgung strenger Regeln) gegeneinander abwägen, werden Sie (im Laufe der Zeit, wenn Ihre Erfahrung zunimmt) dazu gebracht, bessere Software zu erstellen. In der Softwareentwicklung sollte es nur eine "absolute Regel" geben: " Es gibt keine absoluten Regeln ".
TripeHound
Dies ist eine sehr gute Klarstellung zu einem Aspekt der SRP. Aber selbst wenn die SOLID-Prinzipien keine harten Regeln sind, sind sie nicht besonders wertvoll, wenn niemand versteht, was sie bedeuten - und noch weniger, wenn Ihre Behauptung, dass "niemand weiß", tatsächlich wahr ist! ... ist es sinnvoll, dass sie schwer zu verstehen sind. Wie bei jeder Fähigkeit gibt es etwas , das das Gute vom weniger Guten unterscheidet! Aber ... "niemand weiß" macht es mehr zu einem schikanierenden Ritual. (Und ich glaube nicht, dass das die Absicht von SOLID ist!)
Svidgen
3
Mit "Niemand weiß" hoffe ich, dass @Euphoric einfach bedeutet, dass es keine genaue Definition gibt, die für jeden Anwendungsfall funktioniert. Es ist etwas, das ein gewisses Maß an Urteilskraft erfordert. Ich denke, eine der besten Möglichkeiten, um zu bestimmen, wo Ihre Verantwortlichkeiten liegen, besteht darin, schnell zu iterieren und sich von Ihrer Codebasis informieren zu lassen . Suchen Sie nach "Gerüchen", nach denen Ihr Code nicht leicht zu pflegen ist. Wenn zum Beispiel eine Änderung an einer einzelnen Geschäftsregel Kaskadeneffekte durch scheinbar nicht verwandte Klassen hervorruft, liegt wahrscheinlich eine Verletzung von SRP vor.
Michael L.
1
Ich habe @TripeHound und andere, die darauf hingewiesen haben, dass all diese "Regeln" nicht existieren, um die eine wahre Religion der Entwicklung zu definieren, sondern um die Wahrscheinlichkeit zu erhöhen, wartbare Software zu entwickeln. Seien Sie sehr vorsichtig, wenn Sie sich nicht erklären können, wie damit wartbare Software gefördert, die Qualität verbessert oder die Effizienz der Entwicklung gesteigert wird.
Michael L.
5

Ich denke, der Begriff "Verantwortung" ist nützlich als Metapher, weil er es uns ermöglicht, mithilfe der Software zu untersuchen, wie gut die Software organisiert ist. Insbesondere würde ich mich auf zwei Prinzipien konzentrieren:

  • Die Verantwortung steht im Einklang mit der Autorität.
  • Keine zwei Entitäten sollten für dasselbe verantwortlich sein.

Diese beiden Prinzipien lassen uns die Verantwortung sinnvoll austeilen, weil sie sich gegenseitig ausspielen. Wenn Sie einem Teil des Codes die Möglichkeit geben, etwas für Sie zu tun, muss er die Verantwortung dafür tragen, was er tut. Dies führt zu der Verantwortung, die eine Klasse möglicherweise tragen muss, und erweitert ihren "einen Grund für Änderungen" auf immer größere Bereiche. Wenn Sie jedoch die Dinge erweitern, stoßen Sie auf natürliche Weise auf Situationen, in denen mehrere Entitäten für dasselbe verantwortlich sind. Dies ist mit Problemen in der realen Verantwortung behaftet, daher ist es sicherlich auch ein Problem in der Codierung. Infolgedessen wird der Geltungsbereich durch dieses Prinzip eingeschränkt, da Sie die Zuständigkeit in nicht duplizierte Pakete unterteilen.

Zusätzlich zu diesen beiden erscheint ein dritter Grundsatz sinnvoll:

  • Verantwortung kann delegiert werden

Betrachten Sie ein frisch geprägtes Programm ... eine leere Tafel. Zunächst haben Sie nur eine Entität, nämlich das gesamte Programm. Es ist verantwortlich für ... alles. Natürlich werden Sie irgendwann anfangen, die Verantwortung an Funktionen oder Klassen zu delegieren. An diesem Punkt kommen die ersten beiden Regeln ins Spiel, die Sie dazu zwingen, diese Verantwortung auszugleichen. Das Programm der obersten Ebene ist nach wie vor für die Gesamtleistung verantwortlich, genau wie ein Manager für die Produktivität seines Teams verantwortlich ist, aber jeder Untereinheit wurde die Verantwortung übertragen, und mit ihr die Befugnis, diese Verantwortung wahrzunehmen.

Als zusätzlichen Bonus ist SOLID damit besonders kompatibel mit jeder Unternehmenssoftwareentwicklung, die erforderlich ist. Jedes Unternehmen auf dem Planeten hat ein Konzept, wie man Verantwortung delegiert, und sie sind sich nicht alle einig. Wenn Sie die Verantwortung in Ihrer Software auf eine Weise delegieren, die an die Ihrer Firma erinnert, wird es für zukünftige Entwickler viel einfacher sein, sich mit der Vorgehensweise in dieser Firma vertraut zu machen.

Cort Ammon
quelle
Ich bin nicht zu 100% sicher, dass dies alles erklärt. Aber ich denke, "Verantwortung" in Bezug auf "Autorität" zu erklären, ist ein aufschlussreicher Weg, es auszudrücken! (+1)
svidgen
Pirsig sagte: "Sie neigen dazu, Ihre Probleme in die Maschine einzubauen", was mir zu denken gibt.
@nocomprende Sie neigen auch dazu, Ihre Stärken in die Maschine einzubauen. Ich würde behaupten, wenn Ihre Stärken und Ihre Schwächen dasselbe sind, wird es dann interessant.
Cort Ammon
5

In dieser Konferenz in Yale gibt Onkel Bob dieses lustige Beispiel:

Geben Sie hier eine Bildbeschreibung ein

Er sagt, das Employeehat drei Gründe, sich zu ändern, drei Ursachen für Änderungsanforderungen und gibt diese humorvolle und humorvolle , aber dennoch anschauliche Erklärung:

  • Wenn die CalcPay()Methode einen Fehler aufweist und das Unternehmen Millionen US-Dollar kostet, wird Sie der CFO entlassen .

  • Wenn die ReportHours()Methode einen Fehler aufweist und das Unternehmen Millionen US-Dollar kostet, wird der COO Sie entlassen .

  • Wenn die WriteEmmployee(Methode einen Fehler aufweist, der zum Löschen vieler Daten führt und das Unternehmen Millionen von US-Dollar kostet, werden Sie vom CTO entlassen .

Wenn Sie also drei verschiedene C-Level-Execs haben, die Sie möglicherweise wegen kostspieliger Fehler in derselben Klasse entlassen , hat die Klasse zu viele Verantwortlichkeiten.

Er gibt diese Lösung an, die die Verletzung von SRP behebt, aber die Verletzung von DIP, die im Video nicht gezeigt wird, noch lösen muss.

Geben Sie hier eine Bildbeschreibung ein

Tulains Córdova
quelle
Dieses Beispiel ähnelt eher einer Klasse mit falschen Verantwortlichkeiten.
Robert Harvey
4
@RobertHarvey Wenn eine Klasse zu viele Verantwortlichkeiten hat, bedeutet dies, dass die zusätzlichen Verantwortlichkeiten die falschen Verantwortlichkeiten sind.
Tulains Córdova
5
Ich höre, was Sie sagen, aber ich finde es nicht zwingend. Es gibt einen Unterschied zwischen einer Klasse, die zu viele Verantwortlichkeiten hat, und einer Klasse, die etwas tut, was überhaupt nichts zu tun hat. Es mag gleich klingen, ist es aber nicht; Erdnüsse zu zählen ist nicht dasselbe wie Walnüsse zu nennen. Es ist das Prinzip von Onkel Bob und das Beispiel von Onkel Bob, aber wenn es ausreichend beschreibend wäre, würden wir diese Frage überhaupt nicht brauchen.
Robert Harvey
@ Robert Harvey, was ist der Unterschied? Diese Situationen erscheinen mir isomorph.
Paul Draper
3

Ich denke, ein besserer Weg, Dinge als "Gründe für eine Änderung" zu unterteilen, besteht darin, zunächst zu überlegen, ob es Sinn macht, diesen Code, der zwei (oder mehr) Aktionen ausführen muss, dazu zu zwingen, einen separaten Objektverweis zu führen für jede Aktion, und ob es nützlich wäre, ein öffentliches Objekt zu haben, das eine Aktion ausführen könnte, aber nicht die andere.

Wenn beide Fragen mit Ja beantwortet werden, sollten die Aktionen von verschiedenen Klassen durchgeführt werden. Wenn die Antworten auf beide Fragen Nein lauten, würde dies bedeuten, dass es aus öffentlicher Sicht eine Klasse geben sollte. Wenn der Code dafür unhandlich wäre, könnte er intern in private Klassen unterteilt werden. Wenn die Antwort auf die erste Frage Nein lautet, die zweite jedoch Ja lautet, sollte es für jede Aktion eine separate Klasse sowie eine zusammengesetzte Klasse geben, die Verweise auf Instanzen der anderen enthält.

Wenn es separate Klassen für die Tastatur, den Piepser, die numerische Anzeige, den Belegdrucker und die Kassenschublade einer Registrierkasse gibt und keine zusammengesetzte Klasse für eine vollständige Registrierkasse, wird der Code, der eine Transaktion verarbeiten soll, möglicherweise versehentlich in a aufgerufen Diese Methode nimmt Eingaben von der Tastatur eines Geräts entgegen, erzeugt Geräusche vom Piepser eines zweiten Geräts, zeigt Zahlen auf dem Display eines dritten Geräts an, druckt eine Quittung auf dem Drucker eines vierten Geräts und öffnet die Kassenschublade eines fünften Geräts. Jede dieser Unterfunktionen wird möglicherweise von einer separaten Klasse behandelt, es sollte jedoch auch eine zusammengesetzte Klasse vorhanden sein, die sie verbindet. Die zusammengesetzte Klasse sollte so viel Logik wie möglich an die konstituierenden Klassen delegieren.

Man könnte sagen, dass die "Verantwortung" jeder Klasse darin besteht, entweder eine echte Logik zu integrieren oder einen gemeinsamen Anknüpfungspunkt für mehrere andere Klassen zu schaffen, aber es ist wichtig, sich in erster Linie darauf zu konzentrieren, wie der Client-Code eine Klasse anzeigen soll. Wenn es für Client-Code sinnvoll ist, etwas als einzelnes Objekt anzuzeigen, sollte Client-Code es als einzelnes Objekt anzeigen.

Superkatze
quelle
Das ist ein guter Rat. Es kann erwähnenswert sein, dass Sie die Zuständigkeiten nach mehr Kriterien als nur nach dem SRP aufteilen.
Jørgen Fogh
1
Auto-Analogie: Ich muss nicht wissen, wie viel Benzin sich im Tank eines anderen befindet, oder ich möchte die Scheibenwischer eines anderen einschalten. (aber das ist die Definition des Internets) (Shh! Sie werden die Geschichte ruinieren)
1
@nocomprende - „Ich brauche nicht zu wissen , wie viel Gas in den Tank jemand anderes ist“ - es sei denn , Sie ein Teenager sind , die versuchen , die von der Familie des Autos zu entscheiden , zu „borgen“ für Ihre nächste Reise ...;)
alephzero
3

SRP ist schwer zu bekommen. Es geht hauptsächlich darum, Ihrem Code "Jobs" zuzuweisen und sicherzustellen, dass jedes Teil klare Verantwortlichkeiten hat. Wie im wirklichen Leben kann es in einigen Fällen ganz natürlich sein, die Arbeit auf die Menschen aufzuteilen, in anderen Fällen kann es jedoch sehr schwierig sein, vor allem, wenn Sie sie (oder die Arbeit) nicht kennen.

Ich empfehle immer, dass Sie nur einfachen Code schreiben, der zuerst funktioniert , und dann ein wenig überarbeiten: Sie werden nach einer Weile feststellen, wie sich der Code auf natürliche Weise zusammenballt. Ich halte es für einen Fehler, Verantwortlichkeiten zu erzwingen, bevor Sie den Code (oder die Personen) und die zu erledigende Arbeit kennen.

Eine Sache, die Sie bemerken werden, ist, wenn das Modul zu viel zu tun beginnt und schwer zu debuggen / warten ist. Dies ist der Moment der Umgestaltung; Was sollte der Kernjob sein und welche Aufgaben könnten einem anderen Modul übertragen werden? Sollte es beispielsweise Sicherheitsüberprüfungen und andere Aufgaben behandeln oder sollten Sie Sicherheitsüberprüfungen zuerst an einer anderen Stelle durchführen, oder wird der Code dadurch komplexer?

Verwenden Sie zu viele Indirektionen, und es wird wieder zu einem Chaos ... was andere Prinzipien angeht, steht diese in Konflikt mit anderen, wie KISS, YAGNI usw. Alles ist eine Frage des Gleichgewichts.

Christophe Roussy
quelle
Ist SRP nicht einfach Constantines Kohäsion?
Nick Keighley
Sie werden diese Muster natürlich finden, wenn Sie lange genug codieren, aber Sie können das Lernen beschleunigen, indem Sie sie benennen, und es hilft bei der Kommunikation ...
Christophe Roussy
@NickKeighley Ich denke, es ist Zusammenhalt, nicht so sehr groß geschrieben, aber aus einer anderen Perspektive betrachtet.
Sdenham
3

"Prinzip der einmaligen Verantwortung" ist vielleicht ein verwirrender Name. "Nur ein Grund für eine Änderung" ist eine bessere Beschreibung des Prinzips, die jedoch leicht missverstanden werden kann. Wir sprechen nicht darüber, wie Objekte zur Laufzeit ihren Zustand ändern. Wir beschäftigen uns damit, was Entwickler in Zukunft veranlassen könnten, den Code zu ändern.

Sofern wir keinen Fehler beheben, ist die Änderung auf eine neue oder geänderte Geschäftsanforderung zurückzuführen. Sie müssen außerhalb des Codes selbst denken und sich vorstellen, welche externen Faktoren dazu führen können, dass sich Anforderungen unabhängig voneinander ändern . Sagen:

  • Steuersätze ändern sich aufgrund einer politischen Entscheidung.
  • Marketing beschließt, die Namen aller Produkte zu ändern
  • Die Benutzeroberfläche muss neu gestaltet werden, um zugänglich zu sein
  • Die Datenbank ist überlastet, daher müssen Sie einige Optimierungen vornehmen
  • Sie müssen eine mobile App unterbringen
  • und so weiter...

Idealerweise möchten Sie, dass unabhängige Faktoren verschiedene Klassen beeinflussen. Da sich die Steuersätze unabhängig von den Produktnamen ändern, sollten sich Änderungen nicht auf dieselben Klassen auswirken. Andernfalls laufen Sie Gefahr, dass eine Steueränderung zu einem Fehler bei der Produktbezeichnung führt. Dies ist die Art der engen Kopplung, die Sie mit einem modularen System vermeiden möchten.

Konzentrieren Sie sich also nicht nur auf das, was sich ändern könnte - irgendetwas könnte sich möglicherweise in Zukunft ändern. Konzentrieren Sie sich darauf, was sich unabhängig ändern könnte . Änderungen sind normalerweise unabhängig, wenn sie von verschiedenen Akteuren verursacht werden.

Ihr Beispiel mit Berufsbezeichnungen ist auf dem richtigen Weg, aber Sie sollten es wörtlich nehmen! Wenn Marketing Änderungen am Code und Finanzen andere Änderungen verursachen könnte, sollten diese Änderungen nicht denselben Code betreffen, da dies buchstäblich unterschiedliche Berufsbezeichnungen sind und Änderungen daher unabhängig voneinander erfolgen.

Um Onkel Bob zu zitieren , der den Begriff erfunden hat:

Wenn Sie ein Softwaremodul schreiben, möchten Sie sicherstellen, dass Änderungen nur von einer einzelnen Person oder vielmehr von einer einzelnen eng gekoppelten Gruppe von Personen stammen können, die eine einzelne eng definierte Geschäftsfunktion darstellen, wenn sie angefordert werden. Sie möchten Ihre Module von den Komplexitäten der Organisation als Ganzes isolieren und Ihre Systeme so gestalten, dass jedes Modul für die Anforderungen nur dieser einen Geschäftsfunktion verantwortlich ist (auf diese reagiert).

Zusammenfassend lässt sich sagen: Eine "Verantwortung" erfüllt eine einzelne Geschäftsfunktion. Wenn mehr als ein Akteur dazu führen könnte, dass Sie eine Klasse wechseln müssen, dann verstößt die Klasse wahrscheinlich gegen dieses Prinzip.

JacquesB
quelle
Nach seinem Buch "Clean Architecture" ist dies genau richtig. Geschäftsregeln sollten aus einer Quelle stammen und nur aus einer Quelle. Dies bedeutet, dass HR, Operations und IT alle zusammenarbeiten müssen, um Anforderungen in einer "Einzelverantwortung" zu formulieren. Und das ist das Prinzip. +1
Benny Skogberg
2

Ein guter Artikel, der die SOLID-Programmierprinzipien erklärt und Beispiele für Code gibt, der diesen Prinzipien folgt oder nicht folgt, ist https://scotch.io/bar-talk/solid-the-first-five-principles-of-object-oriented- Design .

In dem SRP-Beispiel gibt er ein Beispiel für einige Formklassen (Kreis und Quadrat) und eine Klasse an, mit der die Gesamtfläche mehrerer Formen berechnet werden kann.

In seinem ersten Beispiel erstellt er die Flächenberechnungsklasse und lässt sie als HTML ausgeben. Später entscheidet er, dass er es stattdessen als JSON anzeigen möchte und muss seine Flächenberechnungsklasse ändern.

Das Problem bei diesem Beispiel ist, dass seine Klasse zur Flächenberechnung für die Berechnung der Fläche von Formen UND die Anzeige dieser Fläche verantwortlich ist. Dann geht er einen besseren Weg, um dies mit einer anderen Klasse zu tun, die speziell für die Anzeige von Bereichen entwickelt wurde.

Dies ist ein einfaches Beispiel (und das Lesen des Artikels wird einfacher, da es Codefragmente enthält), zeigt jedoch die Kernidee von SRP.

blitz1616
quelle
0

Zuallererst haben Sie tatsächlich zwei separate Probleme: das Problem, welche Methoden in Ihre Klassen eingefügt werden sollen, und das Problem, dass die Schnittstelle aufgebläht ist.

Schnittstellen

Sie haben diese Schnittstelle:

public Interface CustomerCRUD
{
  public void Create(Customer customer);
  public Customer Read(int CustomerID);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}

Vermutlich haben Sie mehrere Klassen, die der CustomerCRUDSchnittstelle entsprechen (andernfalls ist eine Schnittstelle nicht erforderlich), und einige Funktionen do_crud(customer: CustomerCRUD), die ein konformes Objekt aufnehmen. Aber Sie haben die SRP bereits gebrochen: Sie haben diese vier verschiedenen Vorgänge miteinander verknüpft.

Nehmen wir an, Sie würden später Datenbankansichten bearbeiten. In einer Datenbankansicht steht nur die ReadMethode zur Verfügung. Sie möchten jedoch eine Funktion schreiben do_query_stuff(customer: ???), die Operatoren für vollständige Tabellen oder Sichten transparent macht. es wird schließlich nur die ReadMethode verwendet .

Also erstelle eine Schnittstelle

public Interface CustomerReader {public Customer Read (customerID: int)}

und faktoriere dein CustomerCrudInterface als:

public interface CustomerCRUD extends CustomerReader
{
  public void Create(Customer customer);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}

Aber ein Ende ist nicht in Sicht. Es könnte Objekte geben, die wir erstellen, aber nicht aktualisieren können usw. Dieses Kaninchenloch ist zu tief. Der einzig vernünftige Weg, sich an das Prinzip der einheitlichen Verantwortung zu halten, besteht darin, dass alle Ihre Schnittstellen genau eine Methode enthalten . Go folgt tatsächlich dieser Methodik, die ich gesehen habe, wobei die überwiegende Mehrheit der Schnittstellen eine einzige Funktion enthält. Wenn Sie eine Schnittstelle angeben möchten, die zwei Funktionen enthält, müssen Sie umständlich eine neue Schnittstelle erstellen, die beide Funktionen kombiniert. Sie erhalten bald eine kombinatorische Explosion von Schnittstellen.

Der Ausweg aus diesem Durcheinander ist die Verwendung struktureller Untertypen (implementiert in z. B. OCaml) anstelle von Schnittstellen (die eine Form nominaler Untertypen sind). Wir definieren keine Schnittstellen; Stattdessen können wir einfach eine Funktion schreiben

let do_customer_stuff customer = customer.read ... customer.update ...

das ruft alle Methoden auf, die wir mögen. OCaml verwendet die Typinferenz, um zu bestimmen, dass jedes Objekt übergeben werden kann, das diese Methoden implementiert. In diesem Beispiel würde bestimmt werden, dass customerTyp hat <read: int -> unit, update: int -> unit, ...>.

Klassen

Dies löst das Interface- Chaos. Es müssen jedoch noch Klassen implementiert werden, die mehrere Methoden enthalten. Sollen wir zum Beispiel zwei verschiedene Klassen erstellen CustomerReaderund CustomerWriter? Was ist, wenn wir ändern möchten, wie Tabellen gelesen werden (z. B. werden unsere Antworten vor dem Abrufen der Daten in Redis zwischengespeichert), aber jetzt, wie sie geschrieben werden? Wenn Sie dieser Argumentationskette bis zu ihrem logischen Ende folgen , werden Sie untrennbar zur funktionalen Programmierung geführt :)

Gartenkopf
quelle
4
"Meaningless" ist ein bisschen stark. Ich könnte hinter "mystisch" oder "Zen" kommen. Aber nicht gleich bedeutungslos!
Svidgen
Können Sie etwas näher erläutern, warum strukturelle Untertypen eine Lösung sind?
Robert Harvey
@RobertHarvey Meine Antwort wurde erheblich
umstrukturiert
4
Ich benutze Schnittstellen, auch wenn ich nur eine einzige Klasse habe, die sie implementiert. Warum? Verspotten in Unit-Tests.
Eternal21
0

In meinen Augen ist das, was einem SRP am nächsten kommt, ein Nutzungsfluss. Wenn Sie für eine bestimmte Klasse keinen eindeutigen Verwendungsablauf haben, hat Ihre Klasse wahrscheinlich einen Designgeruch.

Ein Verwendungsablauf wäre eine bestimmte Reihenfolge von Methodenaufrufen, die zu einem erwarteten (also testbaren) Ergebnis führen würde. Grundsätzlich definieren Sie eine Klasse mit den IMHO-Anwendungsfällen. Aus diesem Grund konzentriert sich die gesamte Programmmethodik auf Schnittstellen und nicht auf die Implementierung.

Arthur Havlicek
quelle
0

Um diese mehrfachen Anforderungsänderungen zu erreichen, muss Ihre Komponente nicht geändert werden .

Aber viel Glück beim Verstehen, dass auf den ersten Blick, wenn Sie zum ersten Mal über SOLID hören.


Ich sehe viele Kommentare, die besagen, dass sich SRP und YAGNI widersprechen können, aber YAGN, das ich von TDD (GOOS, London School) erzwungen habe , hat mich gelehrt, meine Komponenten aus der Perspektive eines Kunden zu betrachten und zu entwerfen. Ich habe angefangen, meine Benutzeroberflächen so zu gestalten, wie es von keinem Client gewünscht wird und wie wenig es tun sollte . Und diese Übung kann ohne TDD-Kenntnisse durchgeführt werden.

Ich mag die von Onkel Bob beschriebene Technik (ich kann mich leider nicht erinnern, woher sie stammt), die ungefähr so ​​aussieht:

Fragen Sie sich, was macht diese Klasse?

Enthält Ihre Antwort entweder Und oder Oder?

Wenn ja, extrahieren Sie diesen Teil der Antwort, es liegt in Ihrer eigenen Verantwortung

Diese Technik ist absolut, und wie @svidgen sagte, ist SRP ein Urteilsspruch, aber wenn man etwas Neues lernt, ist Absolutes das Beste, es ist einfacher, einfach immer etwas zu tun. Stellen Sie sicher, dass der Grund, warum Sie sich nicht trennen, der ist. eine gebildete Schätzung, und nicht, weil Sie nicht wissen, wie es geht. Das ist die Kunst, und es braucht Erfahrung.


Ich denke, viele der Antworten scheinen ein Argument für die Entkopplung zu sein, wenn es um SRP geht .

SRP soll nicht sicherstellen, dass sich eine Änderung nicht im Abhängigkeitsdiagramm ausbreitet.

Theoretisch hätten Sie ohne SRP keine Abhängigkeiten ...

Eine Änderung sollte nicht viele Stellen in der Anwendung verändern, aber wir haben andere Prinzipien dafür. SRP verbessert jedoch das Open Closed-Prinzip . Bei diesem Prinzip geht es eher um Abstraktion. Kleinere Abstraktionen lassen sich jedoch leichter erneut implementieren .

Also , wenn SOLID als Ganz Lehre, seien Sie vorsichtig zu lehren , dass SRP ermöglicht es Ihnen , ändern weniger Code , wenn die Anforderungen ändern, wenn in der Tat, es erlaubt Ihnen , weniger zu schreiben neuen Code.

Chris Wohlert
quelle
3
When learning something new, absolutes are the best, it is easier to just always do something.- Nach meiner Erfahrung sind neue Programmierer viel zu dogmatisch. Absolutismus führte zu nichtdenkenden Entwicklern und Cargo-Kult-Programmen. "Tu das einfach" zu sagen ist in Ordnung, solange du verstehst, dass die Person, mit der du sprichst , später verlernen muss, was du ihnen beigebracht hast.
Robert Harvey
@RobertHarvey, Ganz richtig, es erzeugt dogmatisches Verhalten, und Sie müssen verlernen / neu lernen, wenn Sie Erfahrung sammeln. Das ist jedoch mein Punkt. Wenn ein neuer Programmierer versucht, Entscheidungen ohne Begründung zu treffen, erscheint dies als unbrauchbar, da er nicht weiß, warum es funktioniert hat, wenn es funktioniert hat. Indem die Leute dazu gebracht werden , es zu übertreiben , lernen sie, nach Ausnahmen zu suchen, anstatt unqualifizierte Vermutungen anzustellen. Alles, was Sie über Absolutismus gesagt haben, ist richtig, weshalb es nur ein Ausgangspunkt sein sollte.
Chris Wohlert
@RobertHarvey, Ein schnelles Beispiel aus dem wirklichen Leben : Sie können Ihren Kindern beibringen, immer ehrlich zu sein, aber wenn sie älter werden, werden sie wahrscheinlich einige Ausnahmen bemerken, bei denen die Menschen ihre ehrlichsten Gedanken nicht hören wollen. Es ist bestenfalls optimistisch, von einem Fünfjährigen zu erwarten, dass er ein korrektes Urteil darüber abgibt, dass er ehrlich ist. :)
Chris Wohlert
0

Darauf gibt es keine eindeutige Antwort. Obwohl die Frage eng ist, sind die Erklärungen nicht.

Für mich ist es so etwas wie Occams Rasiermesser, wenn Sie wollen. Es ist ein Ideal, an dem ich versuche, meinen aktuellen Code zu messen. Es ist schwer, es in einfachen Worten festzunageln. Eine andere Metapher wäre »ein Thema«, das so abstrakt, dh schwer zu fassen ist wie »einzelne Verantwortung«. Eine dritte Beschreibung wäre »sich mit einer Abstraktionsebene befassen«.

Was heißt das konkret?

In letzter Zeit verwende ich einen Codierungsstil, der hauptsächlich aus zwei Phasen besteht:

Phase I lässt sich am besten als kreatives Chaos beschreiben. In dieser Phase schreibe ich Code auf, während die Gedanken fließen - dh roh und hässlich.

Phase II ist das genaue Gegenteil. Es ist wie nach einem Hurrikan aufzuräumen. Dies erfordert die meiste Arbeit und Disziplin. Und dann betrachte ich den Code aus der Sicht eines Designers.

Ich arbeite jetzt hauptsächlich in Python, so dass ich später an Objekte und Klassen denken kann. Erste Phase I - Ich schreibe nur Funktionen und verteile sie fast zufällig in verschiedenen Modulen. In Phase II , nachdem ich die Dinge in Gang gesetzt habe, sehe ich mir genauer an, welches Modul sich mit welchem ​​Teil der Lösung befasst. Und während ich durch die Module blättere, tauchen für mich Themen auf. Einige Funktionen sind thematisch verwandt. Das sind gute Kandidaten für Klassen . Und nachdem ich Funktionen in Klassen umgewandelt habe - was fast mit Einrücken und Hinzufügen selfzur Parameterliste in Python erledigt ist ;) - verwende ich SRPwie Occam's Razor, um Funktionen für andere Module und Klassen zu entfernen.

Ein aktuelles Beispiel ist möglicherweise das Schreiben einer kleinen Exportfunktion .

Es wurden CSV- , Excel- und kombinierte Excel-Tabellen in einem Reißverschluss benötigt.

Die einfache Funktionalität wurde jeweils in drei Ansichten (= Funktionen) durchgeführt. Jede Funktion verwendete eine gemeinsame Methode zum Bestimmen von Filtern und eine zweite Methode zum Abrufen der Daten. In jeder Funktion fand dann die Vorbereitung des Exports statt und wurde als Antwort vom Server geliefert.

Es waren zu viele Abstraktionsebenen verwechselt:

I) Umgang mit eingehenden / ausgehenden Anfragen / Antworten

II) Bestimmen von Filtern

III) Abrufen von Daten

IV) Umwandlung von Daten

Der einfache Schritt bestand darin, exporterin einem ersten Schritt mit einer Abstraktion ( ) die Schichten II-IV zu behandeln.

Es blieb nur das Thema Anfragen / Antworten . Auf der gleichen Abstraktionsebene werden Anforderungsparameter extrahiert, was in Ordnung ist. Also ich hatte für diese Ansicht eine "Verantwortung".

Zweitens musste ich den Exporteur auflösen, der, wie wir sahen, aus mindestens drei weiteren Abstraktionsebenen bestand.

Das Bestimmen von Filterkriterien und der tatsächliche Abruf erfolgen nahezu auf derselben Abstraktionsebene (die Filter werden benötigt, um die richtige Teilmenge der Daten zu erhalten). Diese Ebenen wurden in so etwas wie eine Datenzugriffsschicht gelegt .

Im nächsten Schritt zerlegte ich die eigentlichen Exportmechanismen: Wo das Schreiben in eine temporäre Datei erforderlich war, zerlegte ich das in zwei "Verantwortlichkeiten": eine für das eigentliche Schreiben der Daten auf die Festplatte und eine andere, die sich mit dem eigentlichen Format befasste.

Mit der Bildung der Klassen und Module wurde klarer, was wohin gehörte. Und immer die latente Frage, ob die Klasse zu viel macht .

Wie legen Sie fest, welche Verantwortlichkeiten jede Klasse haben soll, und wie definieren Sie eine Verantwortlichkeit im Kontext der SRP?

Es ist schwer, ein Rezept zu geben, dem man folgen kann. Natürlich könnte ich die kryptische »Eine Abstraktionsebene« wiederholen - Regel, wenn das hilft.

Meistens ist es für mich eine Art "künstlerische Intuition", die zum aktuellen Design führt; Ich modelliere Code, wie ein Künstler Ton formen oder malen kann.

Stell dir mich als Coding vor Bob Ross ;)

Thomas Junk
quelle
0

Was ich versuche, um Code zu schreiben, der der SRP folgt:

  • Wählen Sie ein bestimmtes Problem aus, das Sie lösen möchten.
  • Schreiben Sie Code, der es löst, schreiben Sie alles in einer Methode (zB: main);
  • Analysieren Sie den Code sorgfältig und versuchen Sie, basierend auf dem Geschäft die Verantwortlichkeiten zu definieren, die in allen durchgeführten Vorgängen sichtbar sind (dies ist der subjektive Teil, der auch vom Geschäft / Projekt / Kunden abhängt).
  • Bitte beachten Sie, dass alle Funktionen bereits implementiert sind. Als Nächstes folgt nur die Organisation des Codes (in diesem Ansatz werden von nun an keine zusätzlichen Funktionen oder Mechanismen mehr implementiert).
  • Extrahieren Sie basierend auf den Verantwortlichkeiten, die Sie in den vorherigen Schritten definiert haben (die auf der Grundlage des Geschäfts und der Idee "Ein Grund zur Änderung" definiert wurden), für jeden eine separate Klasse oder Methode.
  • Bitte beachten Sie, dass dieser Ansatz nur das SPR betrifft. idealerweise sollten hier zusätzliche schritte unternommen werden, um auch die anderen grundsätze einzuhalten.

Beispiel:

Problem: Nimm zwei Zahlen vom Benutzer, berechne ihre Summe und gib das Ergebnis an den Benutzer aus:

//first step: solve the problem right away
static void Main(string[] args)
{
    Console.WriteLine("Number 1: ");
    int firstNumber = Convert.ToInt32(Console.ReadLine());

    Console.WriteLine("Number 2: ");
    int secondNumber = Convert.ToInt32(Console.ReadLine());

    int result = firstNumber + secondNumber;

    Console.WriteLine("Hi there! The result is: {0}", result);

    Console.ReadLine();
}

Versuchen Sie als Nächstes, Verantwortlichkeiten basierend auf den Aufgaben zu definieren, die ausgeführt werden müssen. Extrahieren Sie daraus die entsprechenden Klassen:

//Responsible for getting two integers from the user
class Input {
    public int FirstNumber { get; set; }
    public int SecondNumber { get; set; }
    public void Read() {
        Console.WriteLine("Number 1: ");
        FirstNumber = Convert.ToInt32(Console.ReadLine());

        Console.WriteLine("Number 2: ");
        SecondNumber = Convert.ToInt32(Console.ReadLine());
    }
}

//Responsible for calculating the sum of two integers
class SumOperation {
    public int Result { get; set; }
    public void Calculate(int a, int b) {
        Result = a + b;
    }
}

//Responsible for the output of some value to the user
class Output {
    public void Write(int result) {
        Console.WriteLine("Hello! The result is: {0}", result);
    }
}

Dann wird das überarbeitete Programm:

//Program: responsible for main execution.
//Gets two numbers from user and output their sum.
static void Main(string[] args)
{
    var input = new Input();
    input.Read();

    var operation = new SumOperation();
    operation.Calculate(input.FirstNumber, input.SecondNumber);

    var output = new Output();
    output.Write(operation.Result);

    Console.ReadLine();
}

Hinweis: Dieses sehr einfache Beispiel berücksichtigt nur das SRP-Prinzip. Die Verwendung der anderen Prinzipien (zB: Der "L" -Code sollte eher von Abstraktionen als von Konkretisierungen abhängen) würde dem Code mehr Nutzen bringen und ihn für geschäftliche Änderungen besser pflegbar machen.

Emerson Cardoso
quelle
1
Ihr Beispiel ist zu einfach, um SRP angemessen zu veranschaulichen. Niemand würde dies im wirklichen Leben tun.
Robert Harvey
Ja, in realen Projekten schreibe ich einen Pseudocode, anstatt den genauen Code wie in meinem Beispiel zu schreiben. Nach dem Pseudocode versuche ich, die Zuständigkeiten so aufzuteilen, wie ich es im Beispiel getan habe. Jedenfalls mache ich das so.
Emerson Cardoso
0

Aus Robert C. Martins Buch Clean Architecture: Ein Handbuch für Handwerker zu Softwarestruktur und -design , veröffentlicht am 10. September 2017, schreibt Robert auf Seite 62 Folgendes:

In der Vergangenheit wurde die SRP folgendermaßen beschrieben:

Ein Modul sollte nur einen Grund für eine Änderung haben

Softwaresysteme werden geändert, um Benutzer und Stakeholder zufriedenzustellen. Diese Benutzer und Stakeholder sind der "Grund zur Veränderung". dass das Prinzip spricht. In der Tat können wir das Prinzip folgendermaßen umformulieren:

Ein Modul sollte einem und nur einem Benutzer oder Stakeholder verantwortlich sein

Leider sind die Begriffe "Benutzer" und "Stakeholder" hier nicht richtig. Es wird wahrscheinlich mehr als einen Benutzer oder Stakeholder geben, der eine vernünftige Änderung des Systems wünscht. Stattdessen beziehen wir uns wirklich auf eine Gruppe - eine oder mehrere Personen, die diese Änderung benötigen. Wir werden diese Gruppe als Schauspieler bezeichnen .

Somit ist die endgültige Version des SRP:

Ein Modul sollte einem und nur einem Akteur verantwortlich sein.

Es geht also nicht um Code. Bei der SRP geht es darum, den Fluss von Anforderungen und Geschäftsanforderungen zu steuern, die nur aus einer Quelle stammen können.

Benny Skogberg
quelle
Ich bin nicht sicher, warum Sie den Unterschied machen, dass es nicht um Code geht. Natürlich geht es um Code; Das ist Softwareentwicklung.
Robert Harvey
@RobertHarvey Mein Punkt ist, dass der Fluss der Anforderungen aus einer Hand kommt, dem Akteur. Benutzer und Stakeholder stehen nicht für Code, sondern für Geschäftsregeln, die für uns als Anforderungen gelten. Die SRP ist also ein Prozess zur Steuerung dieser Anforderungen, was für mich kein Code ist. Es ist Softwareentwicklung (!), Aber kein Code.
Benny Skogberg