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?
quelle
Antworten:
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:
Oder:
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.IO
Namespace: dort können wir eine verschiedene Arten von physikalischen Ströme (zB findenFileStream
,MemoryStream
oderNetworkStream
) 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 NotwendigkeitFileStreamTextWriter
,FileStreamBinaryWriter
,NetworkStreamTextWriter
,NetworkStreamBinaryWriter
,MemoryStreamTextWriter
, undMemoryStreamBinaryWriter
, 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,XmlWriter
ohne es erneut für Speicher, Datei und Netzwerk separat implementieren zu müssen.quelle
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.
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.
quelle
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
SupportChinese
einfü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.
quelle
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.
quelle
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:
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:
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.
quelle
In dieser Konferenz in Yale gibt Onkel Bob dieses lustige Beispiel:
Er sagt, das
Employee
hat drei Gründe, sich zu ändern, drei Ursachen für Änderungsanforderungen und gibt diese humorvolle und humorvolle , aber dennoch anschauliche Erklärung: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.
quelle
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.
quelle
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.
quelle
"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:
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:
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.
quelle
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.
quelle
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:
Vermutlich haben Sie mehrere Klassen, die der
CustomerCRUD
Schnittstelle entsprechen (andernfalls ist eine Schnittstelle nicht erforderlich), und einige Funktionendo_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
Read
Methode zur Verfügung. Sie möchten jedoch eine Funktion schreibendo_query_stuff(customer: ???)
, die Operatoren für vollständige Tabellen oder Sichten transparent macht. es wird schließlich nur dieRead
Methode verwendet .Also erstelle eine Schnittstelle
public Interface CustomerReader {public Customer Read (customerID: int)}
und faktoriere dein
CustomerCrud
Interface als: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
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
customer
Typ 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
CustomerReader
undCustomerWriter
? 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 :)quelle
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.
quelle
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:
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.
quelle
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.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
self
zur Parameterliste in Python erledigt ist ;) - verwende ichSRP
wie 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,
exporter
in 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 .
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 ;)
quelle
Was ich versuche, um Code zu schreiben, der der SRP folgt:
Beispiel:
Problem: Nimm zwei Zahlen vom Benutzer, berechne ihre Summe und gib das Ergebnis an den Benutzer aus:
Versuchen Sie als Nächstes, Verantwortlichkeiten basierend auf den Aufgaben zu definieren, die ausgeführt werden müssen. Extrahieren Sie daraus die entsprechenden Klassen:
Dann wird das überarbeitete Programm:
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.
quelle
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:
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.
quelle