Welche Änderungen sind zu groß, um durch richtiges Design einfach gemacht zu werden?

11

Dies ist eine ziemlich vage Frage, aber ich habe nie das Gefühl, dass sie beim Lesen über das richtige Design zufriedenstellend beantwortet wurde.

Wenn Sie etwas über objektorientierte Programmierung, Abstraktion, Ausklammern usw. lernen, ist der heilige Gral des Designs - und der Grund, warum immer behauptet wird, dass Sie die fraglichen Entwicklungstechniken verwenden - im Allgemeinen, dass Ihr Programm dadurch "einfach zu ändern" wird. , "wartbar", "flexibel" oder eines der Synonyme, die verwendet werden, um ein solches produktiv klingendes Konzept auszudrücken. Indem Sie ivars als privat markieren, Code in viele kleine, in sich geschlossene Methoden aufteilen und die Schnittstellen allgemein halten, erhalten Sie angeblich die Möglichkeit, Ihr Programm mit absoluter Leichtigkeit und Anmut zu ändern.

Bei relativ kleinen Änderungen hat dies bei mir gut funktioniert. Änderungen an internen Datenstrukturen, die von einer Klasse zur Leistungssteigerung verwendet werden, waren nie eine große Schwierigkeit, und auch Änderungen an der Benutzeroberfläche, unabhängig von der API, wie die Neugestaltung eines Texteingabesystems oder die Überarbeitung der Grafiken für ein Gameplay-Element .

Alle diese Änderungen scheinen in sich geschlossen zu sein. Keine von ihnen beinhaltet Änderungen am Verhalten oder Design der Komponente Ihres Programms, die geändert wird, soweit es den externen Code betrifft. Unabhängig davon, ob Sie prozedural oder in einem OO-Stil mit großen oder kleinen Funktionen schreiben, können Sie diese Änderungen leicht vornehmen, selbst wenn Sie nur ein mäßig gutes Design haben.

Wenn jedoch die Änderungen groß und haarig werden - dh Änderungen an der API -, kommt keines meiner wertvollen "Muster" jemals zur Rettung. Die große Veränderung bleibt groß, der betroffene Code bleibt betroffen, und viele Stunden Bug-Spawning-Arbeit liegen vor mir.

Meine Frage lautet also: Wie groß ist die Veränderung, die das richtige Design für möglich hält? Gibt es eine weitere Designtechnik, die mir entweder unbekannt ist oder die ich nicht implementiert habe, die eine klebrig klingende Modifikation wirklich einfach macht, oder ist dieses Versprechen (das ich von so vielen verschiedenen Paradigmen gehört habe) nur eine nette Idee? völlig getrennt von den unveränderlichen Wahrheiten der Softwareentwicklung? Gibt es ein "Änderungswerkzeug", das ich meinem Werkzeuggürtel hinzufügen kann?

Das Problem, mit dem ich konfrontiert bin und das mich zu den Foren geführt hat, ist das folgende: Ich habe an der Implementierung einer interpretierten Programmiersprache gearbeitet (implementiert in D, aber das ist nicht relevant), und ich habe entschieden, dass die Argumente meiner Abschlüsse sein sollten eher schlüsselwortbasiert als positionell, wie sie derzeit sind. Dies erfordert das Ändern des gesamten vorhandenen Codes, der anonyme Funktionen aufruft, was zum Glück eher klein ist, da ich mich noch in einem frühen Stadium der Entwicklung meiner Sprache befinde (<2000 Zeilen), aber enorm wäre, wenn ich diese Entscheidung zu einem späteren Zeitpunkt getroffen hätte. Gibt es in einer solchen Situation eine Möglichkeit, diese Modifikation durch richtige Voraussicht im Design zu vereinfachen, oder sind bestimmte (die meisten) Änderungen an sich weitreichend? Ich bin gespannt, ob dies in irgendeiner Weise ein Versagen meiner eigenen Designfähigkeiten ist - wenn ja, ich '

Um klar zu sein, bin ich in keiner Weise skeptisch gegenüber OOP oder einem der anderen üblicherweise verwendeten Muster. Für mich liegen ihre Tugenden jedoch eher im ursprünglichen Schreiben als in der Aufrechterhaltung von Codebasen. Durch Vererbung können Sie sich wiederholende Muster gut abstrahieren, durch Polymorphismus können Sie Code nach vom Menschen verstandenen Funktionen (welche Klasse) und nicht nach maschinenverstandenen Effekten (welcher Zweig der switchAnweisung) trennen , und kleine, in sich geschlossene Funktionen ermöglichen dies Schreiben Sie in einem sehr angenehmen "Bottom-up" -Stil. Ich bin jedoch skeptisch gegenüber ihren Flexibilitätsansprüchen.

existiert für alle
quelle
Ich sehe nicht, wie die Änderung in Ihrer Sprache etwas anderes als den Parser berührt. Könnten Sie das klarstellen?
Scarfridge
In einer smalltalk-ähnliche Sprache, es ist wahr , dass Sie nur den Parser zu ändern brauchen würde, weil es einfach eine Frage der Behandlung wäre , foo metarg1: bar metarg2: bazwie foo.metarg1_metarg2_(bar, baz). Meine Sprache nimmt jedoch eine größere Änderung vor, nämlich listbasierte Parameter zu wörterbuchbasierten Parametern, die sich auf die Laufzeit, aber nicht auf den Parser auswirken. Aufgrund bestimmter Eigenschaften meiner Sprache werde ich im Moment nicht darauf eingehen. Da es keine eindeutige Zuordnung zwischen ungeordneten Schlüsselwörtern und Positionsargumenten gibt, ist die Laufzeit das Hauptproblem.
existiert-für alle

Antworten:

13

Manchmal ist eine Änderung groß genug, dass Sie einen Migrationspfad entwerfen müssen. Selbst wenn der Start- und Endpunkt gut gestaltet sind, kann man oft nicht einfach den kalten Truthahn wechseln. Viele ansonsten gute Designer können keine guten Migrationspfade entwerfen.

Aus diesem Grund denke ich, dass jeder Programmierer eine Stint-Schreibsoftware für 24/7-Produktionsumgebungen erstellen sollte. In der Reihenfolge Ihres Jahresgehalts pro Minute werden Verluste angegeben, die Sie dazu motivieren, das Schreiben eines robusten Migrationscodes zu lernen.

Was die Leute natürlich für eine große Änderung tun, ist, den alten Weg herauszureißen, den neuen Weg einzuschlagen und dann ein paar Tage damit zu verbringen, eine Unmenge von Kompilierungsfehlern zu beheben, bis Ihr Code endlich wieder kompiliert wird, damit Sie mit dem Testen beginnen können. Da der Code zwei Tage lang nicht testbar war, müssen Sie plötzlich ein riesiges Durcheinander von verwickelten Fehlern beseitigen.

Bei einer großen Designänderung wie dem Wechsel von Positionsargumenten zu Schlüsselwörtern besteht ein guter Migrationspfad darin, zunächst Unterstützung für Schlüsselwortargumente hinzuzufügen, ohne die Positionsunterstützung zu entfernen . Anschließend ändern Sie den aufrufenden Code methodisch, um Schlüsselwortargumente zu verwenden. Dies ist ähnlich wie zuvor, außer dass Sie diesmal testen können, da der unveränderte Aufrufcode weiterhin funktioniert. Da Ihre Änderungen zwischen den Tests geringer sind, können die Fehler früher leichter behoben werden. Wenn dann der gesamte aufrufende Code geändert wurde, ist es trivial, die Positionsunterstützung zu entfernen.

Aus diesem Grund lehnen öffentlich veröffentlichte APIs die alten Methoden ab, anstatt sie kalt zu entfernen. Es bietet einen nahtlosen Migrationspfad zum Aufrufen von Code. Es mag sich nach mehr Arbeit anfühlen, beide für eine Weile zu unterstützen, aber Sie machen die Zeit für das Testen wieder gut.

Um Ihre Frage wie ursprünglich formuliert zu beantworten, gibt es fast keine Änderungen, die zu groß sind, um durch das richtige Design einfacher zu werden. Wenn Ihre Änderung zu groß erscheint, müssen Sie nur einige temporäre Zwischenstufen für die Migration Ihres Codes entwerfen.

Karl Bielefeldt
quelle
+1 für die Unterstützung des neuen und des alten Falls, damit Sie jederzeit auslaufen können.
Erik Reppen
9

Ich würde Ihnen empfehlen, den Big Ball of Mud- Aufsatz zu lesen .

Grundsätzlich der Punkt, an dem sich das Design im Laufe der Entwicklung tendenziell verschlechtert und Sie sich der Eindämmung der Komplexität widmen müssen. Die Komplexität selbst kann nicht beseitigt werden, sondern ist nur enthalten, da sie dem Problem inhärent ist, das Sie lösen möchten.

Dies führt zu den Hauptprinzipien der agilen Entwicklung. Die beiden wichtigsten sind "Sie werden es nicht brauchen", die Ihnen sagen, dass Sie das Design nicht auf Funktionen vorbereiten sollen, die Sie noch nicht implementieren möchten, da es unwahrscheinlich ist, dass Sie das Design sowieso richtig machen, und "gnadenlos umgestalten", dass Sie arbeiten sollen zur Aufrechterhaltung der Vernunft des Codes während des gesamten Projekts.

Es scheint, dass die Antwort lautet, dass kein Design in der Lage ist, Änderungen über erfundene Beispiele hinaus zu ermöglichen, die speziell entwickelt wurden, um zu zeigen, dass das Design stärker ist als es wirklich ist.

Nebenbei bemerkt sollten Sie der objektorientierten Programmierung skeptisch gegenüberstehen. Es ist in vielen Zusammenhängen ein großartiges Werkzeug, aber Sie müssen sich seiner Grenzen bewusst sein. Insbesondere der Missbrauch der Vererbung kann zu unglaublich verschlungenem Code führen. Natürlich sollten Sie jeder anderen Designtechnik ebenso skeptisch gegenüberstehen. Jeder hat seine Verwendung und jeder hat seine Schwachstellen. Es gibt keine Silberkugel.

Jan Hudec
quelle
1
+1: Neuartiges Upfront-Design ist selten erfolgreich. Erfolgreiche Designs sind entweder Rekapitulationen bewährter Designs oder wurden iterativ verfeinert.
Kevin Cline
1
+1 Sie sollten allen Programmierkonzepten skeptisch gegenüberstehen. Nur durch objektive Kritik wenden wir unsere analytischen Fähigkeiten an, um die richtige Lösung zu finden und nicht nur eine Lösung .
Jimmy Hoffa
4

Im Allgemeinen gibt es "Codeteile" (Funktionen, Methoden, Objekte, was auch immer) und "Schnittstellen zwischen Codeteilen" (APIs, Funktionsdeklarationen, was auch immer; einschließlich des Verhaltens).

Wenn ein Codeteil geändert werden kann, ohne die Schnittstelle zu ändern, von der andere Codeteile abhängen, ist die Änderung einfacher (und es spielt keine Rolle, ob Sie OOP verwenden oder nicht).

Wenn ein Codeteil nicht geändert werden kann, ohne die Schnittstelle zu ändern, von der andere Codeteile abhängen, ist die Änderung schwieriger (und es spielt keine Rolle, ob Sie OOP verwenden oder nicht).

Die Hauptvorteile von OOP sind, dass die öffentlichen "Schnittstellen" klar gekennzeichnet sind (z. B. die öffentlichen Methoden eines Objekts) und anderer Code keine internen Schnittstellen verwenden kann (z. B. die privaten Methoden des Objekts). Da die öffentlichen Schnittstellen klar gekennzeichnet sind, neigen die Benutzer dazu, diese öffentlichen Schnittstellen sorgfältiger zu gestalten (was dazu beiträgt, die schwierigeren Änderungen zu vermeiden). Da die internen Schnittstellen nicht von anderem Code verwendet werden können, können Sie sie ändern, ohne sich um anderen Code kümmern zu müssen.

Brendan
quelle
Ein weiterer Vorteil von OOP besteht darin, dass die Kapselung der Daten mit der Logik, die darauf arbeitet, zu kleineren öffentlichen Schnittstellen führt (wenn dies gut gemacht wird).
Michael Borgwardt
2

Es gibt keine Entwurfsmethode, die Sie vor den Problemen einer großen Änderung der Anforderungen / des Umfangs bewahren kann. Es ist einfach unmöglich, sich alle möglichen Änderungen vorzustellen und zu berücksichtigen, an die zu einem späteren Zeitpunkt gedacht werden könnte.

Wo ein gutes Design macht Ihnen helfen, helfen Sie ist zu verstehen , wie alle Teile zusammenpassen und was wird durch die vorgeschlagene Änderung nicht betroffen.
Darüber hinaus kann ein gutes Design die Teile einschränken, die von den meisten vorgeschlagenen Änderungen betroffen sind.

Kurz gesagt, alle Änderungen werden durch ein gutes Design erleichtert, aber gutes Design kann aus einer großen Änderung keine kleine machen. (Ein schlechtes / kein Design hingegen kann jede kleine Änderung in eine große verwandeln.)

Bart van Ingen Schenau
quelle
1

Da das Design ein Entwicklungsprojekt von Null auf ein funktionierendes Endprodukt (zumindest das Design für eines) bringen soll und dies die größte Änderung an einem Projekt ist, bezweifle ich, dass es so etwas wie gibt Eine Veränderung, die zu groß ist, um sie zu bewältigen.

Wenn überhaupt, ist es umgekehrt. Einige Änderungen sind zu klein, um sich mit "Design" oder ausgefallenem Denken zu beschäftigen. Zum Beispiel einfache Fehlerbehebungen.

Denken Sie daran, dass Entwurfsmuster dazu beitragen sollen, klar zu denken und gute Entwurfslösungen für häufig auftretende Situationen zu finden, z. B. das Erstellen einer großen Anzahl kleiner Objekte identischen Typs. Keine Liste von Mustern ist vollständig. Sie und der ganze Rest von uns werden Designprobleme haben, die nicht häufig sind und in keine Schublade passen. Der Versuch, jeden kleinen Schritt in einem Softwareentwicklungsprozess nach dem einen oder anderen offiziellen Muster zu machen, ist eine übermäßig religiöse Art, Dinge zu tun, und führt zu nicht zu wartenden Problemen.

Alle Arbeiten in Software sind natürlich Fehler. Fußballspieler bekommen Verletzungen. Musiker bekommen je nach Instrument Schwielen oder Lippenkrämpfe. (Wenn Sie beim Gitarrenspiel Lippenkrämpfe bekommen, machen Sie es falsch.) Softwareentwickler bekommen Fehler. Wir lieben es, Wege zu finden, um ihre Laichrate zu reduzieren, aber es wird niemals Null sein.

DarenW
quelle
3
Die Ausgangsposition kann auch negativ sein. Unterschätze nicht die Macht des Vermächtnisses :)
Maglob
Das Entwerfen eines Projekts von Null ist der einfache Teil. Aber selbst wenn Sie ein neues Projekt von Grund auf neu starten, was an sich selten ist, werden Sie in den ersten Tagen nur Spaß daran haben, aus dem Nichts zu bauen. Die erste anfängliche Entwurfsentscheidung hat sich als falsch herausgestellt und von dort aus geht es erst noch bergab. Design von Null ist in der Praxis völlig irrelevant.
Jan Hudec
1

Die Kosten für umfassende Änderungen sind häufig proportional zur Größe der Codebasis. Daher sollte die Reduzierung der Größe der Codebasis immer eines der Ziele eines ordnungsgemäßen Entwurfs sein.

In Ihrem Beispiel hat das richtige Design Ihre Arbeit bereits erleichtert: Sie müssen Änderungen in einer Codebasis von 2000 Zeilen anstelle einer Codebasis von 20000 oder 200000 vornehmen.

Durch das richtige Design werden umfassende Änderungen reduziert, aber nicht beseitigt.

Umfassende Änderungen sind recht einfach zu automatisieren, wenn die Sprachanalyse-Tools sie ordnungsgemäß unterstützen. Eine pauschale Refactoring-Änderung kann in einem Such- und Ersetzungsstil auf die gesamte Codebasis angewendet werden (unter Beachtung der Sprachregeln). Der Programmierer muss nur für jeden Suchtreffer ein Ja / Nein-Urteil abgeben.

rwong
quelle
+1 für Automatisierungsvorschlag. Ich denke, ich werde das tatsächlich für viele meiner Bibliotheksfunktionen verwenden, die Closures aufrufen - es ist eine einfache Sache, zu erkennen, welche Funktionen Blöcke benötigen, und sie so zu ändern, dass sie einen zusätzlichen Symbolparameter für den Argumentnamen haben, an den der Wert übergeben wird.
existiert-für alle