Was sollte Vorrang haben: YAGNI oder Good Design?


76

Ab wann sollte YAGNI Vorrang vor guten Codierungspraktiken haben und umgekehrt? Ich arbeite an einem Projekt bei der Arbeit und möchte meinen Mitarbeitern langsam gute Codestandards vorstellen (derzeit gibt es keine und alles wird nur irgendwie ohne Reim oder Grund zusammen gehackt), aber nach dem Erstellen einer Reihe von Klassen (wir) mache keine TDD oder leider überhaupt keine Unit-Tests) Ich bin einen Schritt zurückgetreten und dachte, es würde gegen YAGNI verstoßen, weil ich ziemlich sicher weiß, dass wir nicht die Notwendigkeit haben, einige dieser Klassen zu erweitern.

Hier ist ein konkretes Beispiel für das, was ich meine: Ich habe eine Datenzugriffsebene, die eine Reihe gespeicherter Prozeduren umschließt, die ein rudimentäres Repository-ähnliches Muster mit grundlegenden CRUD-Funktionen verwendet. Da es eine Handvoll Methoden gibt, die alle meine Repository-Klassen benötigen, habe ich eine generische Schnittstelle für meine Repositorys namens erstellt IRepository. Ich habe dann jedoch eine "Marker" -Schnittstelle (dh eine Schnittstelle, die keine neuen Funktionen hinzufügt) für jeden Repository-Typ (z. B. ICustomerRepository) erstellt, und die konkrete Klasse implementiert dies. Ich habe dasselbe mit einer Factory-Implementierung getan, um die Geschäftsobjekte aus DataReaders / DataSets zu erstellen, die von der gespeicherten Prozedur zurückgegeben wurden. Die Signatur meiner Repository-Klasse sieht normalerweise so aus:

public class CustomerRepository : ICustomerRepository
{
    ICustomerFactory factory = null;

    public CustomerRepository() : this(new CustomerFactory() { }

    public CustomerRepository(ICustomerFactory factory) {
        this.factory = factory;
    }      

    public Customer Find(int customerID)
    {
        // data access stuff here
        return factory.Build(ds.Tables[0].Rows[0]);
    }
}

Ich mache mir Sorgen, dass ich gegen YAGNI verstoße, weil ich mit 99% iger Gewissheit weiß, dass es niemals einen Grund geben wird CustomerFactory, diesem Endlager etwas anderes als ein konkretes zu geben. da wir keine Unit-Tests haben, brauche ich keine MockCustomerFactoryoder ähnliche Dinge, und so viele Schnittstellen könnten meine Mitarbeiter verwirren. Auf der anderen Seite scheint die konkrete Umsetzung der Fabrik ein Designgeruch zu sein.

Gibt es einen guten Weg, um einen Kompromiss zwischen korrektem Software-Design und einer Überarchitektur der Lösung zu finden? Ich frage mich, ob ich alle "einzelnen Implementierungsschnittstellen" haben muss oder ob ich ein bisschen gutes Design opfern und nur zum Beispiel die Basisschnittstelle und dann die einzelne konkrete haben kann und mich nicht um die Programmierung kümmern muss Schnittstelle, wenn die Implementierung jemals verwendet wird.


17
Sie sagen: "Da wir keine Unit-Tests haben, brauche ich kein MockX", was natürlich zu "Ich brauche kein IX, ich brauche nur X" führt. Die Tatsache, dass Sie keine Komponententests haben, unterstreicht die Tatsache, dass Sie IX und MockX benötigen, denn diese Dinge werden Ihnen dabei helfen, Komponententests zu haben . Akzeptieren Sie nicht die Realität, dass Sie keine Tests haben. Behandeln Sie dies als ein vorübergehendes Problem, das über einen (wahrscheinlich guten, langen) Zeitraum hinweg behoben wird.
Anthony Pegram

10
Auch wenn es trivial ist es zu googeln, sollte jemand erwähnen , dass YAGNI steht für „Sie ist nicht gonna es brauchen“
thedaian

1
Ich würde denken, wenn Sie neue Klassen wie diese schreiben, möchten Sie einige Unit-Tests hinzufügen. Auch wenn Ihre Mitarbeiter sie nicht ausführen. Zumindest später können Sie sagen: "Guck mal! Meine Komponententests haben es entdeckt, als du meinen Code gebrochen hast! Sieh, wie großartig Komponententests sind!" In diesem Fall könnte es sich lohnen, es verspottbar zu machen. (Obwohl ich es vorziehen würde, wenn das Objekt verspottet werden könnte, ohne eine Schnittstelle zu definieren)
Winston Ewert

Würden mich die Unit-Tests nicht dazu zwingen, ein Mock-Framework zu erstellen (oder zu verwenden), damit ich keine gespeicherten Live-Prozeduren erreiche? Das ist der Hauptgrund, warum ich keine Tests hinzufüge - wir haben jeweils eine lokale Kopie der Produktionsdatenbank, mit der wir Code testen und schreiben.
Wayne Molina

3
@Anthony Und rechtfertigt das Verspotten immer den damit verbundenen zusätzlichen Aufwand an Komplexität? Das Verspotten ist ein gutes Werkzeug, aber auch sein Nutzen muss gegen die Kosten abgewogen werden, und manchmal wird die Skala durch einen Überschuss an Indirektion in die andere Richtung gekippt. Sicher, es gibt Tools, die bei der zusätzlichen Komplexität helfen, aber sie werden die Komplexität nicht beseitigen. Es scheint einen wachsenden Trend zu geben, „Testen um jeden Preis“ als gegeben zu behandeln. Ich halte das für falsch.
Konrad Rudolph

Antworten:


75

Gibt es einen guten Weg, um einen Kompromiss zwischen korrektem Software-Design und einer Überarchitektur der Lösung zu finden?

YAGNI.

Ich könnte ein bisschen gutes Design opfern

Falsche Annahme.

und die Basisschnittstelle und dann der einzelne Beton,

Das ist kein "Opfer". Das ist gutes Design.


72
Perfektion wird nicht erreicht, wenn nichts mehr hinzuzufügen ist, sondern wenn nichts mehr zum Mitnehmen übrig ist. Antoine De St-Exupery
Newtopian

5
@Newtopian: In Bezug auf Apples neueste Produkte haben die Leute gemischte Gefühle. :)
Roy Tinker

14
Ich habe ein großes Problem mit dieser Art von Antworten. Ist "X ist wahr, weil Y es sagt und Y von der Community gut unterstützt wird" eine gültige Argumentation? Wenn jemand im wirklichen Leben gegen YAGNI protestieren würde, würden Sie ihm diese Antwort als Argument zeigen?
vemv

2
@vemv. Niemand hier schimpft gegen YAGNI. Alles, was S.Lott sagte, war, dass der vom OP wahrgenommene Widerspruch zwischen zwei würdigen Zielen ein Fehler ist. Übrigens, kennen Sie einen aktiven Entwickler in der heutigen Software-Welt, der gegen YAGNI protestieren würde? Wenn Sie an echten Projekten arbeiten, wissen Sie, dass sich die Anforderungen ständig ändern und dass Benutzer und Manager im Allgemeinen nicht wissen, was sie tun oder wollen, bis Sie es ihnen vorlegen. Das Notwendige umzusetzen ist VIEL Arbeit - warum Zeit, Energie und Geld verschwenden (oder Ihren Job riskieren), indem Sie Code schreiben, der versucht, die Zukunft vorherzusagen?
Vector

6
Es geht mir nicht um YAGNI, sondern um die Qualität der Antworten. Ich sage nicht, dass jemand besonders schimpfte, es war Teil meiner Argumentation. Bitte lies es nochmal.
Vemv

74

In den meisten Fällen führt die Vermeidung von Code, den Sie nicht benötigen, zu einem besseren Design. Das wartungsfreundlichste und zukunftssicherste Design verwendet die geringste Menge an gut benanntem, einfachem Code, der die Anforderungen erfüllt.

Die einfachsten Designs sind am einfachsten zu entwickeln. Nichts beeinträchtigt die Wartbarkeit so sehr wie nutzlose, überentwickelte Abstraktionsebenen.


8
Ich finde "Vermeiden von YAGNI-Code" beunruhigend vieldeutig. Dies kann bedeuten , dass Sie keinen Code benötigen oder dass der Code dem YAGNI-Prinzip entspricht . (Vgl. "KISS-Code")
siehe

2
@sehe: Es ist überhaupt nicht mehrdeutig (während "sich an das YAGNI-Prinzip hält" völlig widerspricht), wenn Sie es ausdrücken: Was könnte "Sie-werden-es-nicht-brauchen-Code" möglicherweise bedeuten, aber codieren Sie das wirst du nicht brauchen?
Michael Borgwardt

1
Ich werde diese Antwort in einer großen Schrift drucken und sie dort aufhängen, wo alle Architekturastronauten sie lesen können. +1!
kirk.burleson

1
+1. Ich kann mich noch gut an mein erstes Unternehmensprojekt erinnern, das ich unterstützen und einige neue Funktionen hinzufügen durfte. Als erstes entfernte ich 32.000 Zeilen nutzlosen Code aus einem Programm mit 40.000 Zeilen, ohne dass die Funktionalität verloren ging. Der ursprüngliche Programmierer wurde kurz danach abgefeuert.
EJ Brennan

4
@vemv: Lesen Sie "nutzlos" als "derzeit nicht verwendet, außer trivial", dh ein Fall von YAGNI. Und "überentwickelt" ist ein bisschen spezifischer als "schlecht". Konkret bedeutet es "Komplexität, die eher durch theoretische Konzepte oder vorgestellte mögliche Anforderungen als durch konkrete aktuelle Anforderungen verursacht wird".
Michael Borgwardt

62

YAGNI und SOLID (oder eine andere Entwurfsmethode) schließen sich nicht gegenseitig aus. Sie sind jedoch nahpolare Gegensätze. Sie müssen sich auch nicht zu 100% daran halten, aber es wird ein gewisses Geben und Nehmen geben. Je mehr Sie sich ein stark abstrahiertes Muster ansehen, das von einer Klasse an einem Ort verwendet wird, und YAGNI sagen und es vereinfachen, desto weniger FEST wird das Design. Das Gegenteil kann auch der Fall sein; Viele Male in der Entwicklung wird ein Design SOLID "on faith" implementiert. Sie sehen nicht, wie Sie es brauchen, aber Sie haben nur eine Ahnung. Dies könnte der Fall sein (und es wird immer wahrer, je mehr Erfahrung Sie sammeln), aber es könnte Sie auch in so viel technische Schulden stecken wie ein "Do-it-Light" -Ansatz mit einem Schlag. Anstelle einer DIL-Codebasis mit "Spaghetti-Code" erhalten Sie möglicherweise "Lasagne-Code". Das Hinzufügen einer Methode oder eines neuen Datenfelds auf so vielen Ebenen ist ein tagelanger Prozess, bei dem mit nur einer Implementierung Service-Proxys und lose gekoppelte Abhängigkeiten durchlaufen werden. Oder Sie könnten am Ende mit "Ravioli-Code" enden, der in so kleinen, mundgerechten Stücken vorliegt, dass Sie 50 Methoden mit jeweils 3 Zeilen durchlaufen, wenn Sie sich in der Architektur nach oben, unten, links oder rechts bewegen.

Ich habe es in anderen Antworten gesagt, aber hier ist es: Lass es beim ersten Durchgang funktionieren. Machen Sie es im zweiten Durchgang elegant. Machen Sie es beim dritten Durchgang SOLID.

Aufschlüsselung:

Wenn Sie zum ersten Mal eine Codezeile schreiben, muss dies einfach funktionieren. Soweit Sie wissen, handelt es sich an diesem Punkt um eine einmalige Angelegenheit. Sie erhalten also keine Stilpunkte für den Bau einer "Elfenbeinturm" -Architektur, um 2 und 2 hinzuzufügen. Tun Sie, was Sie tun müssen, und gehen Sie davon aus, dass Sie es nie wieder sehen werden.

Wenn Sie das nächste Mal mit dem Cursor in diese Codezeile springen, haben Sie Ihre Hypothese von dem Zeitpunkt an widerlegt, als Sie sie zum ersten Mal geschrieben haben. Sie überarbeiten diesen Code, wahrscheinlich, um ihn zu erweitern oder an anderer Stelle zu verwenden, sodass es sich nicht um einen einmaligen Code handelt. Nun sollten einige Grundprinzipien wie DRY (nicht wiederholen) und andere einfache Regeln für das Code-Design implementiert werden. extrahieren Sie Methoden und / oder bilden Sie Schleifen für wiederholten Code, extrahieren Sie Variablen für allgemeine Literale oder Ausdrücke, fügen Sie möglicherweise einige Kommentare hinzu, aber insgesamt sollte sich Ihr Code selbst dokumentieren. Jetzt ist Ihr Code gut organisiert, wenn auch noch eng miteinander verbunden, und jeder, der ihn ansieht, kann leicht herausfinden, was Sie tun, indem Sie den Code lesen, anstatt ihn Zeile für Zeile zu verfolgen.

Wenn der Cursor zum dritten Mal diesen Code eingibt, ist das wahrscheinlich eine große Sache. Sie erweitern es entweder noch einmal oder es ist an mindestens drei anderen Stellen in der Codebasis nützlich geworden. An diesem Punkt ist es ein Schlüssel-, wenn nicht Kernelement Ihres Systems und sollte als solches strukturiert werden. An diesem Punkt haben Sie in der Regel auch das Wissen darüber, wie es bisher verwendet wurde, sodass Sie gute Entwurfsentscheidungen treffen können, wie der Entwurf entworfen werden soll, um diese und etwaige neue Verwendungen zu rationalisieren. Nun sollten die SOLID-Regeln in die Gleichung eingehen. Extrahieren Sie Klassen mit Code für bestimmte Zwecke, definieren Sie gemeinsame Schnittstellen für Klassen mit ähnlichen Zwecken oder Funktionen, richten Sie lose gekoppelte Abhängigkeiten zwischen Klassen ein und entwerfen Sie die Abhängigkeiten so, dass Sie sie einfach hinzufügen, entfernen oder austauschen können.

Wenn Sie diesen Code von nun an erweitern, erneut implementieren oder erneut verwenden müssen, ist er in dem "Black Box" -Format verpackt und abstrahiert, das wir alle kennen und lieben. Schließen Sie es an, wo immer Sie es benötigen, oder fügen Sie eine neue Variation des Themas als neue Implementierung der Schnittstelle hinzu, ohne die Verwendung dieser Schnittstelle ändern zu müssen.


Ich unterstütze die Großartigkeit.
Filip Dupanović

2
Ja. Wenn ich früher vorab für die Wiederverwendung / Erweiterung entworfen hatte, stellte ich fest, dass die Wiederverwendung oder Erweiterung anders verlief, als ich erwartet hatte. Vorhersagen sind schwierig, insbesondere in Bezug auf die Zukunft. Also unterstütze ich Ihre 3-Strikes-Regel - bis dahin haben Sie eine vernünftige Vorstellung davon, wie sie wiederverwendet / erweitert werden wird. NB: Eine Ausnahme ist, wenn Sie bereits wissen, wie es sein wird (z. B. aus früheren Projekten, Domain-Kenntnissen oder bereits spezifizierten).
13.

Machen Sie es beim vierten Durchgang FANTASTISCH.
Richard Neil Ilagan

@KeithS: Es scheint, dass John Carmack etwas Ähnliches getan hat: "Quellcode von Quake II ... Quake 1, Quake World und QuakeGL in einer schönen Codearchitektur vereinen." fabiensanglard.net/quake2/index.php
13ren

+1 Ich denke, ich werde deine Regel nennen: "Practical Refactoring" :)
Songo

31

Stattdessen bevorzuge ich WTSTWCDTUAWCROT?

(Was ist das Einfachste, was wir tun können, was nützlich ist und was wir am Donnerstag veröffentlichen können?)

Einfachere Akronyme stehen auf meiner Liste, haben aber keine Priorität.


8
Dieses Akronym verstößt gegen das YAGNI-Prinzip :)
riwalk

2
Ich habe genau die Buchstaben verwendet, die ich brauchte - nicht mehr und nicht weniger. Auf diese Weise bin ich ein bisschen wie Mozart. Ja. Ich bin der Mozart der Akronyme.
Mike Sherrill 'Cat Recall'

4
Ich hätte nie gedacht, dass Mozart schreckliche Akronyme herstellen kann. Ich lerne jeden Tag etwas Neues auf einer SE-Site. : P
Cameron MacFarland

3
@Mike Sherrill 'CatRecall': Vielleicht sollte man das auf WTSTWCDTUWCROTAWBOF erweitern. ;-)
Giorgio

1
@ Stargazer712 nein :) es verstößt gegen POLA.
v.oddou

25

YAGNI und gutes Design sind kein Widerspruch. Bei YAGNI geht es darum, zukünftige Bedürfnisse (nicht) zu unterstützen. Gutes Design geht es darum, transparent , was eine Software macht jetzt und wie sie tut dies.

Wird die Einführung einer Factory Ihren bestehenden Code vereinfachen? Wenn nicht, fügen Sie es nicht hinzu. Wenn dies z. B. beim Hinzufügen von Tests der Fall ist (was Sie tun sollten!), Fügen Sie dies hinzu.

Bei YAGNI geht es nicht darum , die Komplexität zu erhöhen, um zukünftige Funktionen zu unterstützen.
Bei gutem Design geht es darum , Komplexität zu beseitigen und gleichzeitig alle aktuellen Funktionen zu unterstützen.


15

Sie sind nicht in Konflikt, Ihre Ziele sind falsch.

Was versuchst du zu erreichen?

Sie möchten qualitativ hochwertige Software schreiben und dazu möchten Sie Ihre Codebasis klein halten und keine Probleme haben.

Jetzt erreichen wir einen Konflikt. Wie decken wir alle Fälle ab, wenn wir keine Fälle schreiben, die wir nicht verwenden werden?

So sieht Ihr Problem aus.

Bildbeschreibung hier eingeben (Interessierte, das nennt man verdunstende Wolken )

Also, was treibt das an?

  1. Sie wissen nicht, was Sie nicht brauchen werden
  2. Sie möchten keine Zeit verschwenden und Ihren Code aufblähen

Welche davon können wir lösen? Nun, es sieht so aus, als würde man keine Zeit verschwenden wollen und Code aufzublähen ist ein großartiges Ziel und macht Sinn. Was ist mit dem ersten? Können wir herausfinden, was wir zum Codieren benötigen?

Ich arbeite an einem Projekt bei der Arbeit und möchte meinen Mitarbeitern langsam gute Codestandards vorstellen (derzeit gibt es keine und alles wird einfach ohne Sinn und Verstand gehackt). [...] Ich trat einen Schritt zurück und dachte, es verstößt gegen YAGNI, weil ich ziemlich sicher weiß, dass wir einige dieser Klassen nicht erweitern müssen.

Lassen Sie uns all das neu formulieren

  • Keine Code-Standards
  • Keine Projektplanung im Gange
  • Cowboys machen überall ihr eigenes Ding (und du versuchst Sheriff im wilden Westen zu spielen), yee haw.

Gibt es einen guten Weg, um einen Kompromiss zwischen korrektem Software-Design und einer Überarchitektur der Lösung zu finden?

Sie brauchen keinen Kompromiss, Sie brauchen jemanden, der das Team leitet, kompetent ist und eine Vision für das gesamte Projekt hat. Sie brauchen jemanden, der planen kann, was Sie brauchen, anstatt jeden von Ihnen in Dinge zu werfen, die Sie NICHT brauchen, weil Sie sich der Zukunft so unsicher sind, weil ... warum? Ich sage dir warum, es liegt daran, dass niemand unter euch allen einen verdammten Plan hat. Sie versuchen, Codestandards einzuführen, um ein völlig separates Problem zu beheben. Ihr primäres Problem, das Sie lösen müssen, ist eine klare Roadmap und ein klares Projekt. Wenn Sie das haben, können Sie sagen: "Codestandards helfen uns, dieses Ziel als Team effektiver zu erreichen." Dies ist die absolute Wahrheit, liegt jedoch außerhalb des Rahmens dieser Frage.

Besorgen Sie sich einen Projekt- / Teammanager, der diese Aufgaben ausführen kann. Wenn Sie eine haben, müssen Sie sie nach einer Karte fragen und das YAGNI-Problem erklären, das auftritt, wenn keine Karte vorhanden ist. Wenn sie grob inkompetent sind, schreiben Sie den Plan selbst und sagen Sie: "Hier ist mein Bericht über die Dinge, die wir brauchen. Bitte überprüfen Sie ihn und teilen Sie uns Ihre Entscheidung mit."


Leider haben wir keine. Der Entwicklungsmanager kümmert sich entweder mehr um schnelle Abläufe als um Standards / Qualität, oder er lässt die Standardpraktiken wie die Verwendung von unformatierten Datensätzen, die überall direkt aus Klassen zurückgegeben werden, VB6-Codierung und alles Code-Behind mit doppelter Logik, die alle kopiert und eingefügt werden Über.
Wayne Molina

Das ist in Ordnung, es wird etwas langsamer, aber Sie müssen dann mit ihren Sorgen sprechen. Erklären Sie, dass dieses YAGNI / Useless-Code-Problem Zeit verschwendet, schlagen Sie die Roadmap vor, geben Sie ihm eine Roadmap und erklären Sie, dass es die Arbeit beschleunigen wird. Wenn er zugekauft ist, kommen Sie mit den Problemen, die schlechte Standards für die Geschwindigkeit der Entwicklung haben, und schlagen Sie die besseren vor. Sie wollen, dass das Projekt erledigt wird, sie wissen einfach nicht, wie sie mit dem Ding umgehen sollen. Entweder müssen Sie ihn ersetzen oder ihn klein machen ... oder aufhören.
Inkognito

Ähm ..... Ich denke, das treibende Ziel wäre "Sie möchten ein gutes, wertvolles Produkt haben"?
Siehe

@sehe das ist nicht seine entscheidung: p.
Inkognito

3
Gute Antwort. Er steht vor einem harten Kampf. Es wird schwierig, dies zu erreichen, wenn kein Management Buy-In vorliegt. Und Sie haben Recht, diese Frage ist verfrüht und geht nicht auf das eigentliche Problem ein.
Seth Spearman

10

Das Ermöglichen, dass Ihr Code durch Komponententests erweitert wird, wird unter YAGNI niemals abgedeckt, da Sie es benötigen werden. Ich bin jedoch nicht davon überzeugt, dass Ihre Designänderungen an einer Schnittstelle mit nur einer Implementierung die Testbarkeit des Codes tatsächlich erhöhen, da CustomerFactory bereits von einer Schnittstelle erbt und ohnehin jederzeit gegen eine MockCustomerFactory ausgetauscht werden kann.


1
+1 für hilfreiche Kommentare zum eigentlichen Problem und nicht nur zum kosmischen Kampf und / oder zur Liebesbeziehung zwischen Good Design und YAGNI.
PSR

10

Die Frage ist ein falsches Dilemma. Die ordnungsgemäße Anwendung des YAGNI-Prinzips ist keine unabhängige Sache. Das ist ein Aspekt von gutem Design. Jedes der SOLID-Prinzipien ist auch ein Aspekt guten Designs. Sie können nicht immer jedes Prinzip in jeder Disziplin vollständig anwenden. Probleme in der realen Welt bringen eine Menge Kräfte in Ihren Code, und einige davon gehen in entgegengesetzte Richtungen. Prinzipien des Designs müssen all das berücksichtigen, aber keine Handvoll Prinzipien kann in alle Situationen passen.

Schauen wir uns nun jedes Prinzip mit dem Verständnis an, dass sie zwar manchmal in verschiedene Richtungen ziehen, aber keineswegs inhärent miteinander in Konflikt stehen.

YAGNI wurde entwickelt, um Entwicklern dabei zu helfen, eine bestimmte Art von Nacharbeit zu vermeiden: die, die aus dem Bauen der falschen Sache resultiert. Dies geschieht, indem es uns anleitet, Fehlentscheidungen zu vermeiden, die auf Annahmen oder Vorhersagen darüber beruhen, was sich unserer Meinung nach in Zukunft ändern oder erforderlich sein wird. Die kollektive Erfahrung zeigt uns, dass wir uns in der Regel irren, wenn wir dies tun. Zum Beispiel würde YAGNI Sie auffordern, keine Schnittstelle zum Zwecke der Wiederverwendbarkeit zu erstellen , es sei denn, Sie wissen gerade, dass Sie mehrere Implementierer benötigen. Ebenso würde YAGNI sagen, dass Sie keinen "ScreenManager" zum Verwalten eines einzelnen Formulars in einer Anwendung erstellen sollten, es sei denn, Sie wissen gerade, dass Sie mehr als einen Bildschirm haben werden.

Im Gegensatz zu dem, was viele Leute denken, geht es bei SOLID nicht um Wiederverwendbarkeit, Generizität oder gar Abstraktion. SOLID soll Ihnen dabei helfen, Code zu schreiben, der für Änderungen vorbereitet ist , ohne etwas darüber zu sagen, was für eine bestimmte Änderung dies sein könnte. Die fünf Prinzipien von SOLID schaffen eine Strategie zum Erstellen von Code, die flexibel ist, ohne zu generisch zu sein, und einfach, ohne naiv zu sein. Die ordnungsgemäße Anwendung von SOLID-Code erzeugt kleine, fokussierte Klassen mit genau definierten Rollen und Grenzen. Das praktische Ergebnis ist, dass eine Mindestanzahl von Klassen berührt werden muss, damit sich die Anforderungen ändern können. Und in ähnlicher Weise gibt es für jede Codeänderung eine minimierte Menge an "Ripple" bis zu anderen Klassen.

Schauen wir uns die Beispielsituation an, die Sie haben, und lassen Sie uns sehen, was YAGNI und SOLID zu sagen haben. Sie ziehen eine gemeinsame Repository-Schnittstelle in Betracht, da alle Repositorys von außen gleich aussehen. Der Wert einer gemeinsamen, generischen Schnittstelle ist jedoch die Fähigkeit, einen der Implementierer zu verwenden, ohne wissen zu müssen, um welche es sich handelt. Sofern es in Ihrer App keine Stelle gibt, an der dies notwendig oder nützlich wäre, sagt YAGNI, dass Sie dies nicht tun sollten.

Es gibt 5 SOLID-Prinzipien zu betrachten. S ist Einzelverantwortung. Dies sagt nichts über die Schnittstelle aus, aber es könnte etwas über Ihre konkreten Klassen aussagen. Es könnte argumentiert werden, dass die Verarbeitung des Datenzugriffs selbst am besten in die Verantwortung einer oder mehrerer anderer Klassen fällt, während die Verantwortung der Repositorys darin besteht, aus einem impliziten Kontext (CustomerRepository ist ein Repository, das implizit für Entitäten des Kunden bestimmt ist) explizite Aufrufe an das zu übertragen Allgemeine Datenzugriffs-API, die den Entitätstyp des Kunden angibt.

O ist offen-geschlossen. Hier geht es hauptsächlich um Vererbung. Dies würde zutreffen, wenn Sie versuchen, Ihre Repositorys von einer gemeinsamen Basis abzuleiten, die gemeinsame Funktionen implementiert, oder wenn Sie erwarten, dass Sie weitere Informationen von den verschiedenen Repositorys ableiten. Aber das bist du nicht, also nicht.

L ist Liskov Substituierbarkeit. Dies gilt, wenn Sie die Repositorys über die gemeinsame Repository-Schnittstelle verwenden möchten. Es enthält Einschränkungen für die Schnittstelle und die Implementierungen, um die Konsistenz zu gewährleisten und eine spezielle Behandlung für verschiedene Impelementer zu vermeiden. Der Grund dafür ist, dass eine solche spezielle Behandlung den Zweck einer Schnittstelle untergräbt. Es kann nützlich sein, dieses Prinzip in Betracht zu ziehen, da es Sie möglicherweise davor warnt, die gemeinsame Repository-Schnittstelle zu verwenden. Dies stimmt mit den Leitlinien von YAGNI überein.

Ich bin Schnittstellentrennung. Dies kann zutreffen, wenn Sie beginnen, Ihren Repositorys verschiedene Abfragevorgänge hinzuzufügen. Die Schnittstellentrennung wird angewendet, wenn Sie die Mitglieder einer Klasse in zwei Teilmengen aufteilen können, wobei eine von bestimmten Verbrauchern und die andere von anderen Verbrauchern verwendet wird, aber wahrscheinlich kein Verbraucher beide Teilmengen verwendet. Die Anleitung besteht darin, statt einer gemeinsamen Schnittstelle zwei separate Schnittstellen zu erstellen. In Ihrem Fall ist es unwahrscheinlich, dass das Abrufen und Speichern einzelner Instanzen von demselben Code verbraucht wird, der für allgemeine Abfragen verwendet wird. Daher ist es möglicherweise hilfreich, diese in zwei Schnittstellen zu unterteilen.

D ist Abhängigkeitsinjektion. Hier kommen wir zum selben Punkt wie das S. Wenn Sie Ihren Verbrauch der Datenzugriffs-API in ein separates Objekt aufgeteilt haben, besagt dieses Prinzip, dass Sie es beim Erstellen übergeben sollten, anstatt nur eine Instanz dieses Objekts neu zu erstellen ein Repository. Auf diese Weise können Sie die Lebensdauer der Datenzugriffskomponente einfacher steuern und Verweise auf diese Komponente zwischen Ihren Repositorys austauschen, ohne sie zu einem Singleton machen zu müssen.

Es ist wichtig zu beachten, dass die meisten SOLID-Prinzipien nicht unbedingt in diesem bestimmten Stadium der Entwicklung Ihrer App gelten. Ob Sie beispielsweise den Datenzugriff unterbrechen sollten, hängt davon ab, wie kompliziert er ist, und ob Sie Ihre Repository-Logik testen möchten, ohne auf die Datenbank zuzugreifen. Es hört sich so an, als wäre dies unwahrscheinlich (meiner Meinung nach leider), also ist es wahrscheinlich nicht notwendig.

Nach all diesen Überlegungen stellen wir fest, dass YAGNI und SOLID tatsächlich einen gemeinsamen soliden, sofort relevanten Ratschlag geben: Es ist wahrscheinlich nicht erforderlich, eine gemeinsame generische Repository-Schnittstelle zu erstellen.

All diese sorgfältigen Überlegungen sind äußerst nützlich als Lernübung. Es ist zeitaufwändig, wie Sie lernen, aber im Laufe der Zeit entwickeln Sie Intuition und werden sehr schnell. Sie wissen, was zu tun ist, müssen aber nicht über all diese Wörter nachdenken, es sei denn, jemand fragt Sie, warum.


Ich denke, die meisten Diskussionen auf dieser Seite lassen zwei große Konkurrenten "niedrige Kopplung" und "hohe Kohäsion" der GRAS-Prinzipien außer Acht. Die kostspieligeren Entwurfsentscheidungen ergeben sich aus dem Prinzip der "geringen Kopplung". Zum Beispiel, wenn SRP + ISP + DIP für niedrige Kopplung "aktiviert" werden soll. Beispiel: eine Klasse -> 3 Klassen im MVC-Muster. Oder noch teurer: in .dll / .so Module / Assemblys aufteilen. Das ist sehr kostspielig, da es Auswirkungen auf den Build, Projekte, Makellisten, Buildserver und Dateizusätze mit Quellcode gibt ...
v.oddou

6

Sie scheinen zu glauben, dass „gutes Design“ bedeutet, eine Art Idealogie und formale Regeln zu befolgen, die immer angewendet werden müssen, auch wenn sie unbrauchbar sind.

IMO das ist schlechtes Design. YAGNI ist ein Bestandteil guten Designs, niemals ein Widerspruch dazu.


2

In Ihrem Beispiel würde ich sagen, dass sich YAGNI durchsetzen sollte. Es kostet Sie nicht so viel, wenn Sie später Schnittstellen hinzufügen müssen. Übrigens, ist es wirklich gut, ein Interface pro Klasse zu haben, wenn es überhaupt keinem Ziel dient?

Ein weiterer Gedanke, vielleicht ist das, was Sie manchmal brauchen, nicht gutes Design, sondern ausreichendes Design. Hier ist eine sehr interessante Abfolge von Beiträgen zum Thema:


Ihr erster und dritter Link gehen an dieselbe Stelle.
David Thornley

2

Einige Leute argumentieren, dass Schnittstellennamen nicht mit I beginnen sollten. Insbesondere liegt ein Grund darin, dass Sie tatsächlich die Abhängigkeit davon verlieren, ob der angegebene Typ eine Klasse oder eine Schnittstelle ist.

Was hindert Sie daran CustomerFactory, zuerst eine Klasse zu sein und sie später in eine Schnittstelle umzuwandeln, die entweder von DefaultCustormerFactoryoder implementiert wird UberMegaHappyCustomerPowerFactory3000? Das einzige, was Sie ändern müssen, ist der Ort, an dem die Implementierung instanziiert wird. Und wenn Sie ein weniger gutes Design haben, dann ist dies höchstens eine Handvoll Orte.

Refactoring ist ein Teil der Entwicklung. Besser wenig Code, der leicht umzugestalten ist, als eine Schnittstelle und eine Klasse für jede einzelne Klasse deklarieren zu lassen, sodass Sie jeden Methodennamen an mindestens zwei Stellen gleichzeitig ändern müssen.

Der eigentliche Punkt bei der Verwendung von Schnittstellen ist die Modularität, die möglicherweise die wichtigste Säule für gutes Design ist. Beachten Sie jedoch, dass ein Modul nicht nur durch seine Entkopplung von der Außenwelt definiert ist (auch wenn wir es so aus der Außenperspektive betrachten), sondern auch durch seine innere Arbeitsweise.
Ich möchte darauf hinweisen, dass das Entkoppeln von Dingen, die von Natur aus zusammengehören, wenig Sinn macht. In gewisser Weise ist es so, als hätte man einen Schrank mit einem individuellen Regal pro Tasse.

Die Bedeutung liegt darin, ein großes, komplexes Problem in kleinere, einfachere Teilprobleme zu zerlegen. Und Sie müssen an dem Punkt anhalten, an dem sie ohne weitere Unterteilung einfach genug werden, sonst werden sie tatsächlich komplizierter. Dies könnte als eine Folge von YAGNI gesehen werden. Und es bedeutet definitiv gutes Design.

Das Ziel ist nicht, ein lokales Problem mit einem einzigen Repository und einer einzigen Fabrik irgendwie zu lösen. Das Ziel ist, dass diese Entscheidung keinen Einfluss auf den Rest Ihrer Bewerbung hat. Darum geht es bei Modularität.
Sie möchten, dass Ihre Mitarbeiter auf Ihr Modul schauen, eine Fassade mit einer Handvoll selbsterklärender Anrufe sehen und sich sicher sind, dass sie sie verwenden können, ohne sich um alle potenziell raffinierten Inneninstallationen kümmern zu müssen.


0

Sie erstellen eine interfaceVielzahl von Implementierungen für die Zukunft. Sie könnten genauso gut eine I<class>für jede Klasse in Ihrer Codebasis haben. Nicht.

Verwenden Sie einfach die Einzelbetonklasse nach YAGNI. Wenn Sie zu Testzwecken ein "Mock" -Objekt benötigen, wandeln Sie die ursprüngliche Klasse in eine abstrakte Klasse mit zwei Implementierungen um, eine mit der ursprünglichen konkreten Klasse und die andere mit der Mock-Implementierung.

Sie müssen natürlich alle Instanziierungen der ursprünglichen Klasse aktualisieren, um die neue konkrete Klasse zu instanziieren. Sie können das umgehen, indem Sie einen statischen Konstruktor verwenden.

YAGNI sagt, dass Code nicht geschrieben werden soll, bevor er geschrieben werden muss.

Gutes Design sagt Abstraktion.

Sie können beides haben. Eine Klasse ist eine Abstraktion.


0

Warum die Markerschnittstellen? Es fällt mir auf, dass es nichts weiter tut als "Markieren". Wozu dient ein anderes "Tag" für jeden Fabriktyp?

Der Zweck einer Schnittstelle besteht darin, einer Klasse ein Verhalten zu geben, das dem Verhalten einer Klasse ähnlich ist, und ihnen sozusagen Repository-Fähigkeit zu verleihen. Wenn sich also alle Ihre konkreten Repository-Typen wie das gleiche IRepository verhalten (alle implementieren IRepository), können sie alle von anderem Code auf die gleiche Weise behandelt werden - von genau demselben Code. An diesem Punkt ist Ihr Design erweiterbar. Hinzufügen konkreterer Repository-Typen, die alle als generische IRepositorys behandelt werden - derselbe Code behandelt alle konkreten Typen als "generische" Repositorys.

Schnittstellen dienen zur Behandlung von Dingen, die auf Gemeinsamkeiten basieren. Benutzerdefinierte Markierungsschnittstellen a) fügen jedoch kein Verhalten hinzu. und b) zwinge dich, mit ihrer Einzigartigkeit umzugehen.

In dem Maße, in dem Sie nützliche Schnittstellen entwerfen, haben Sie die OO-Güte, keinen speziellen Code schreiben zu müssen, um spezielle Klassen, Typen oder benutzerdefinierte Markierungsschnittstellen zu verarbeiten, die eine 1: 1-Korrelation zu konkreten Klassen aufweisen. Das ist sinnlose Redundanz.

Off Hand kann ich eine Markierungsoberfläche sehen, wenn Sie beispielsweise eine stark typisierte Sammlung von vielen verschiedenen Klassen benötigen. In der Sammlung sind sie alle "ImarkerInterface", aber wenn Sie sie herausziehen, müssen Sie sie in die richtigen Typen umwandeln.


0

Können Sie jetzt ein vage vernünftiges ICustomerRepository aufschreiben? ZB haben Ihre Kunden in der Vergangenheit immer PayPal verwendet (was hier wirklich ein schlechtes Beispiel ist)? Oder jeder in der Firma ist begeistert von Alibaba? In diesem Fall möchten Sie möglicherweise jetzt das komplexere Design verwenden und Ihren Vorgesetzten gegenüber weitsichtig erscheinen. :-)

Ansonsten warte. Das Erraten einer Schnittstelle, bevor Sie eine oder zwei tatsächliche Implementierungen haben, schlägt normalerweise fehl. Mit anderen Worten, verallgemeinern / abstrahieren / verwenden Sie ein ausgefallenes Entwurfsmuster erst, wenn Sie ein paar Beispiele zur Verallgemeinerung haben.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.