Auf einen Blick: Das Verständnis der Unterscheidung zwischen Mustern und Anti-Mustern ist in der Softwareentwicklung entscheidend. Muster bieten effektive Lösungen, während Anti-Muster zwar vorteilhaft erscheinen, aber zu negativen Ergebnissen führen können.
- Designmuster umfassen Konzepte wie Fabriken und agile Methoden.
- Häufige Anti-Muster entstehen aus guten Absichten, führen aber zu Ineffizienzen, wie zum Beispiel Moral Hazard.
Fachjargon durchdringt die Softwareentwicklungsbranche. Best Practices. Artefakte. Scope Creep. Viele dieser Begriffe sind so verbreitet, dass sie als überbenutzt bezeichnet werden, und es ist leicht anzunehmen, dass wir sie verstehen, weil sie so offensichtlich erscheinen. Dennoch finden wir manchmal neue Tiefe, wenn wir sie genau betrachten. In diesem Beitrag wollen wir über das "Muster" und sein etwas weniger bekanntes Gegenstück, das "Anti-Pattern", nachdenken.
Was sind Designmuster?
Wir alle wissen, was Muster in der allgemeinen Sprache sind, aber um ihre Bedeutung in der Softwareentwicklung zu verstehen, ist es wichtig, zunächst Algorithmen zu diskutieren. Ein Algorithmus ist einfach eine Möglichkeit, eine gemeinsame Aufgabe zu erfüllen, wie das Sortieren einer Liste von Elementen, das Speichern von Daten für effiziente Abrufe oder das Zählen von Vorkommen eines Elements in einem Datensatz. Algorithmen sind eines der ältesten und grundlegendsten Konzepte der Softwareentwicklung. Tatsächlich liegt auf dem Schreibtisch dieses Autors eine Kopie dessen, was viele als eines der wegweisendsten Werke zu diesem Thema ansehen: Fundamental Algorithms von Donald Knuth. Die erste Auflage dieses kleinen Werkes von etwas über 600 Seiten wurde erstmals 1968, also vor 50 Jahren, urheberrechtlich geschützt. Der Text wäre für einen modernen Programmierer kaum wiederzuerkennen, da er hauptsächlich auf calculusbasierte Beweise seiner Lösungen legt und seine einzigen Codebeispiele in obskuren, veralteten Sprachen wie Algol oder MIX Assembly bereitgestellt werden. Trotzdem wird vieles von dem, was behandelt wurde, heute noch verwendet: einzelne und doppelt verknüpfte Listen, Bäume, Müllabfuhr usw. Die Details sind oft in praktischen Bibliotheken verborgen, aber die Konzepte sind dieselben. Diese Algorithmen sind seit mehr als fünf Jahrzehnten gültige Lösungen für gängige Software-Engineering-Probleme und bestehen weiterhin stark. Die Diskussion über Designmuster ist unvollständig, ohne auf die Bedeutung guter Designprinzipien einzugehen. Lesen Sie mehr über solide Designprinzipien.
Arten von Mustern
Ein "Pattern" kann als eine allgemeinere Form eines Algorithmus betrachtet werden. Wo sich ein Algorithmus auf eine bestimmte Programmieraufgabe konzentriert, kann ein Pattern Herausforderungen jenseits dieses Bereichs berücksichtigen und in Bereiche wie die Reduzierung von Fehlerraten, die Erhöhung der Wartbarkeit von Code oder die effektivere Zusammenarbeit großer Teams einbeziehen. Einige gängige Muster sind:
- Factories – Eine Weiterentwicklung früher objektorientierter Programmierkonzepte, die es dem Ersteller eines Objekts überflüssig machte, alles darüber im Voraus zu wissen. Eine Flussdiagrammanwendung könnte erweiterbare Schablonenbibliotheken unterstützen, indem sie sich auf das Erstellen und Organisieren von "Formen" konzentriert und es den Schablonen ermöglicht, die Details der Erstellung eines einfachen Quadrats vs. eines komplexen Netzwerkrouter-Symbols zu verwalten.
- Pub/Sub – Ein Mechanismus zur "Entkopplung" von Anwendungen. Anstatt dass ein Sender Nachrichten direkt an einen Empfänger sendet, "veröffentlicht" der Absender die Nachrichten an ein Thema oder eine Warteschlange. Ein oder mehrere Empfänger können "abonnieren", um diese Nachrichten zu empfangen, und die Nachrichtenwarteschlange behandelt Details wie Übertragungsfehler oder das erneute Senden von Nachrichten. Dies vereinfacht sowohl die sendenden als auch empfangenden Anwendungen.
- Public-Key-Kryptographie – Ein Mechanismus, bei dem zwei Parteien sicher und ohne Abhören kommunizieren können, ohne jedoch einen vorab verschlüsselten Austausch geheimer Schlüssel arrangieren zu müssen. Jede Partei bewahrt ein Schlüsselpaar (öffentlich und privat), und der öffentliche Schlüssel kann oft bei Bedarf beschafft werden, anstatt im Voraus ausgetauscht zu werden.
- Agile – Eine Philosophie, die eine Reihe von Leitprinzipien für die Softwareentwicklung umfasst, die Kundenzufriedenheit betonen, Flexibilität und Zusammenarbeit annehmen und die Einführung einfacher, nachhaltiger Entwicklungspraktiken fördern.
Dies sind nur vier der vielen gängigen Muster in der Branche, und selbst in dieser Mischung sehen wir, wie sie von hoch technisch bis hin zu breiteren, prozessorientierteren Aspekten reichen. Fabriken sind ein sehr codeorientiertes Muster, während Pub/Sub eher architektonischer Natur ist. Und obwohl Public-Key-Kryptographie weitreichende Auswirkungen hat, stehen Bibliotheken zur Unterstützung ihrer Abläufe für nahezu jede heute gebräuchliche Programmiersprache zur Verfügung, was die Implementierung im Allgemeinen einfach macht.
Missverständnis von agilen Mustern
Am anderen Ende des Spektrums bleibt "Agile" etwas schwer fassbar: gleichzeitig ein Sammelpunkt und ein Instrument der Spaltung unter Entwicklern, Projektmanagern und anderen Interessengruppen darüber, was es genau bedeutet und wie es umgesetzt werden sollte. Es ist ein großartiges Beispiel für einen überstrapazierten, aber wenig verstandenen Begriff. Die Begriffe "Waterfall" oder "Stand ups" im selben Satz wie "Agile" zu sehen, ist fast immer ein Beispiel für Missbrauch. Agile ist eine Philosophie, keine Softwareentwicklungsmethodik, daher kann es nicht direkt mit Waterfall verglichen werden, noch legt es Prozesskomponenten wie Stand-ups direkt dar. (Diese sind ein Bestandteil von Scrum, einer Methodik, die agile Prinzipien umsetzt, aber Agile selbst nicht repräsentiert.) Egal ob eng oder breit, technisch oder prozessorientiert, ein gutes Wissen über diese Muster ist ein wesentlicher Bestandteil im Werkzeugkasten eines Technologen.
Was ist ein Anti-Muster?
Wenn ein "Muster" einfach eine bewährte Lösung für ein häufiges Software-Engineering-Problem ist, wäre dann ein "Anti-Pattern" nicht genau das Gegenteil? Eine nicht-agile Entwicklungsmethodik oder eine eng gekoppelte Anwendung? Tatsächlich beinhalten Anti-Patterns nicht nur das Konzept des Versagens, das Richtige zu tun, sondern auch eine Reihe von Entscheidungen, die auf den ersten Blick richtig erscheinen, aber langfristig zu Problemen führen. Wikipedia definiert die Bedeutung von "Anti-Pattern" wie folgt:
"Ein Anti-Pattern ist eine häufige Reaktion auf ein wiederkehrendes Problem, das meist ineffektiv ist und das Risiko birgt, hochgradig kontraproduktiv zu sein."
Beachten Sie den Hinweis auf "eine gemeinsame Antwort". Anti-Muster sind keine gelegentlichen Fehler, sondern häufige und beginnen fast immer mit guten Absichten. Wie bei regulären Mustern können Anti-Muster breit oder sehr spezifisch sein, und im Bereich der Programmiersprachen und -rahmenwerke gibt es buchstäblich Hunderte zu berücksichtigen. Hier sind nur einige der hochrangigen, persönlichen Lieblingsbeispiele dieses Autors.
Whiteboard-Programmierherausforderungen in Softwareinterviews
David Hansson, Schöpfer von Ruby on Rails und Gründer sowie CTO von Basecamp, twitterte einmal: "Hallo, mein Name ist David. Ich würde es versäumen, Bubble Sort auf ein Whiteboard zu schreiben. Ich suche ständig im Internet nach Code. Ich löse keine Rätsel." Das Anti-Muster hier ist die Bewertung der falschen Kennzahlen während eines Vorstellungsgesprächs, etwa wenn eine typische Aufgabe lautet: "Postleitzahlensuche während der Registrierung hinzufügen", aber die Interviewfragen klingen wie "Sortiere dieses Array im Pseudocode anhand funktionaler Programmierungskonzepte." Erinnern Sie sich an den "guten Absichten"-Aspekt von Anti-Patterns? Es scheint, als würden wir den Kandidaten an einem wertvollen Prinzip testen: Kenntnis der Grundlagen. Programmieren ist jedoch oft eine kompromisslos pragmatische Praxis, und dieser Fokus auf theoretisches Wissen statt auf praktische Fähigkeiten und Erfahrung könnte dazu führen, dass wir einen Kandidaten wählen, der unseren kulturellen Idealen entspricht, aber nicht über die tatsächlichen Fähigkeiten verfügt, um in der Position erfolgreich zu sein. Anders ausgedrückt: Wenn StackOverflow eine regelmäßige Ressource für den Entwickler in der Position sein wird, sollte es während des Vorstellungsgesprächs verfügbar (und genutzt) sein. Hausaufgaben und Herausforderungen beim Pair-Programming können ebenfalls erforscht werden. Alle Muster und Anti-Muster haben gültige Ausnahmen. Ein Entwickler, dessen Aufgabe es ist, Bibliotheken von Algorithmen für andere zu erstellen, muss möglicherweise die Analysis hinter einem Mechanismus kennen. Der Fehler ist, diese Erwartung universell anzuwenden, selbst auf Entwickler, die dies nicht tun.
Moralisches Risiko
In philosophischen Kontexten ist Moral Hazard die Trennung von Individuen von den Folgen ihrer Entscheidungen. Das klingt nach einem offensichtlichen Verhalten, das man vermeiden sollte, aber dieses Anti-Muster ist die Hauptursache vieler SDLC-Ineffizienzen . Betrachten Sie den traditionellen QA-Prozess, bei dem "Tickets" von Entwicklern bearbeitet und dann zur Überprüfung an die QA weitergeleitet werden, bevor sie eingesetzt werden. Hier gibt es zwei Probleme. Erstens sind die Personalverhältnisse fast nie "1 Entwickler zu 1 QA-Analyst", und selbst eine Handvoll Entwickler kann die Kapazität des QA-Teams leicht überschreiten. Zweitens schützt dies Entwickler vor den Konsequenzen ihrer Fehler, indem es in die Verantwortung eines anderen Einzelnen fällt, sie vor ihrer Entlassung zu finden – ein moralisches Risiko. Die Auswirkungen dieses Anti-Musters können subtil sein: Wenn das QA-Team effektiv ist, führt es möglicherweise nicht direkt zu schlechteren Qualitätsergebnissen. Es zeigt sich eher in anderen Bereichen wie Beschwerden über Schätzungsgenauigkeit und verfehlte Ziele. Qualität und Schätzungsgenauigkeit leiden, weil Entwickler instinktiv darauf fokussieren, "Dinge durch QA zu bringen", anstatt hochwertige Software auszuliefern. Selbst bei einer moderaten Fehlerquote von 20–30 % (eine Zahl, die in vielen Organisationen sogar optimistisch sein mag) kann die daraus resultierende Fluktuation die Produktivität des Teams erheblich beeinflussen. Zusätzliche Anti-Patterns entstehen oft beim Versuch, das Problem zu lösen. In Scrum kann es verlockend sein, Sprints länger zu machen oder offen zu halten. Ein Sprint soll jedoch ein Zeitmaß sein, kein Maß für den Output. Dieser Akt kehrt diese Natur um, was den Wert anderer Tools wie "Velocity"-Metriken, die darauf basieren, zerstört. Es ist auch üblich, längere Sprint-Planungen oder Vorplanungsmeetings zu sehen, um Tickets gründlicher zu überprüfen. Dies versucht jedoch, einen instinktiven Prozess in einen wissenschaftlichen zu verwandeln, wobei man vergisst, dass der Zweck der Implementierung einer Methodik wie Scrum darin bestand, diese Unmöglichkeit überhaupt anzuerkennen. Zwei Muster, die oft wirksam sind, um dieses Problem zu lösen, sind:
- Eine Kultur der kontinuierlichen Verbesserung annehmen: "Shippe es, wenn es besser ist, nicht wenn es richtig ist." (Siehe auch "Polishing the Cannonball" unten). Entwickler, die dazu ermutigt und befähigt werden, können bessere Entscheidungen darüber treffen, wie sie ihre Aufgaben angehen, und zudem ein greifbareres Gefühl persönlicher Erfüllung erleben.
- Mach Entwickler für ihr Arbeitsprodukt bis hin zur Produktionsbereitstellung verantwortlich. Facebook, Google und andere Branchengrößen haben mit diesem Ansatz alle Erfolge gemeldet.
Polierung der Kanonenkugel
Manchmal auch als "Gold plating" oder "Boiling the Ocean" bekannt, erhöht der Versuch, perfekte Produkte zu liefern, oft die Projektzeitpläne und Kosten erheblich, ohne den gelieferten Wert tatsächlich zu steigern. Ein eng verwandtes Antimuster ist das "Zombie-Ticket", die Plakette an den Hauptwänden des Backlogs. Zombie-Tickets haben nie hohe Priorität, um ausgeräumt zu werden, aber werden nie geschlossen, aus Angst, die dokumentarische Aufzeichnung der Aufgabe zu verlieren. Das Problem bei beiden Gewohnheiten ist, dass die Kennzahlen, die sie unterstützen, Phantome sind. Nicht ausgelieferte Features haben für Kunden keinen Wert, und Aufgaben, die nicht genug Schmerz verursachen, um Prioritäten zu werden, sind möglicherweise nie lohnenswert. Es ist fast immer besser, verfügbare Ressourcen regelmäßig auf die Bereitstellung neuer, wertvoller Features zu konzentrieren, anstatt ständig auf kleine Probleme zurückzublicken, die nur wenige Nutzer betreffen. Das "Muster"-Gegenstück ist hier das minimal-viable Product (MVP), das oft selbst ein Phantom ist. (MVPs sind fast nie so klein wie geplant oder erhofft.) Allerdings ist der Versuch, ein MVP auszuliefern, oft selbst ein Gegenmittel zu den oben genannten Problemen, sodass es sich selbst bei einer Verzögerung lohnt. Iterative Entwicklungsprozesse gehen dies ebenfalls an, indem sie die regelmäßige, vorhersehbare Bereitstellung von inkrementeller Wertschöpfung betonen, verstärkt durch Feedback der tatsächlichen Endnutzer.
Wie man Anti-Patterns vermeidet
Selbst mit den besten Absichten treten Anti-Muster auf, wenn Menschen keine Erfahrung oder keinen Zugang zu Best Practices haben. Sie könnten versucht sein, Abkürzungen zu nehmen, weil der Druck, pünktlich und im Budget zu liefern, abkürzt. Teams arbeiten intern oder mit externen Partnern möglicherweise nicht gut zusammen, was Kommunikationsausfälle verschärft. In Eile, mit der Arbeit zu beginnen, kann die anfängliche Planung und das Design vernachlässigt werden. Hier sind einige Tipps, um die von uns besprochenen Anti-Pattern-Beispiele zu vermeiden:
- Whiteboard-Programmierherausforderungen: Anstatt ein abstraktes Problem auf einem Whiteboard zu lösen, erstellen Sie ein praktisches und fokussiertes Szenario mit einem Codebeispiel und bitten Sie um Verbesserungen.
- Erwägen Sie, dass Kandidat und Interviewer gemeinsam an einer realistischen Herausforderung arbeiten.
- Bieten Sie eine Hausarbeit an, die es ermöglicht, zu bewerten, wie ein Kandidat Probleme durchdenkt.
- Moralisches Risiko: Verknüpfe Anreize mit Leistung und langfristigen Ergebnissen. Mache Rollen und Verantwortlichkeit klar, mit einem Gefühl individueller Eigenverantwortung.
- Nutzen Sie Werkzeuge, die Risiken klar machen und sowohl offene Kommunikation als auch eine Kultur fördern, die das Teilen von Anliegen fördert.
- Definieren Sie Grenzen, Prozesse und Leitplanken rund um akzeptable Risiken.
- Überwachen und messen Sie, wobei Sie Rückmeldungen aller Beteiligten aufnehmen.
- Die Kanonenkugel polieren: Um ständige Verfeinerungen kleiner Details zu vermeiden, definieren Sie "gut genug" mit einem minimal lebensfähigen Produkt früh im Prozess und mit spezifischen Kennzahlen.
- Definieren Sie Prioritäten und vergleichen Sie den Wert mit dem benötigten Zeit- und Arbeitsaufwand.
- Beziehen Sie die Beteiligten ein, um zu bestimmen, ob sich eine weitere Verfeinerung lohnt.
- Testen und messen, damit Sie Daten haben, um Entscheidungen über weitere Verfeinerungen zu treffen.
Weiterführende Literatur
Es gibt genug Muster und Anti-Muster in der Branche, um Bücher zu füllen, und tatsächlich wurden viele darüber geschrieben. Letztlich ist es meist nicht notwendig, Listen davon auswendig zu lernen, obwohl Entwickler, die sich auf bestimmte Sprachen oder Frameworks spezialisiert haben, ermutigt werden sollten, gezielt auf diese Bereiche zu recherchieren. Wenn Sie mehr über Muster und Anti-Muster lernen möchten, habe ich diese Ressourcen in meiner eigenen Lektüre als wertvoll empfunden:
- Die Seiten "Software Design Patterns" und "Anti-Patterns" von Wikipedia bieten gute Beispiele für übergeordnete Themen.
- Die "Design Patterns" und "AntiPatterns" von SourceMaking enthalten nützliche Details zu allgemeinen Software-Engineering-Aufgaben.
- Enough Rope to Shoot Yourself In the Foot: Regeln für C- und C++-Programmierung, Alan Holub, McGraw Hill, 1995. Obwohl sich dieses Buch auf C und C++ konzentriert, gelten die meisten Regeln, die es behandelt, immer noch für nahezu jede moderne Sprache.
- Für diejenigen, die gerade erst ihre Programmierreise beginnen und nach den besten Programmiersprachen suchen, die sie lernen können.
Außerdem bietet fast jede Sprache oder jedes Framework Dutzende von Ressourcen zur Verfügung, wenn man einfach in jeder Suchmaschine nach "NodeJS Patterns" oder Ähnlichem sucht. Ich würde jedem Entwickler empfehlen, dies für seine jeweiligen Fachgebiete zu tun: Selbst wenn wir glauben, gute von schlechten Praktiken zu kennen, kann es manchmal überraschen, wenn wir einem Anti-Pattern folgen – natürlich immer mit den besten Absichten!
Diese Beiträge sind meine eigenen und spiegeln nicht unbedingt die Position, Strategien oder Meinung von BMC wider.