Don't repeat yourself

Aus MimiPedia
Version vom 28. November 2024, 11:04 Uhr von Ullrich (Diskussion | Beiträge) (Die Seite wurde neu angelegt: „Category:Quality = Motivation = Software-Entwicklung wird gerne mit der Herstellung von Gegenständen verglichen, also mit der produzierenden Industrie. Das schlägt sich auch in Begriffen wieder wie Software-Architektur oder dem Factory-Pattern. Der Vergleich geschieht nicht ohne Grund und solange man sich nicht scheut sich von der Analogie zu lösen wenn sie nicht mehr passt kann man viele nützliche Erkenntnisse daraus ziehen. Ein Punkt an dem die…“)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)


Motivation

Software-Entwicklung wird gerne mit der Herstellung von Gegenständen verglichen, also mit der produzierenden Industrie. Das schlägt sich auch in Begriffen wieder wie Software-Architektur oder dem Factory-Pattern. Der Vergleich geschieht nicht ohne Grund und solange man sich nicht scheut sich von der Analogie zu lösen wenn sie nicht mehr passt kann man viele nützliche Erkenntnisse daraus ziehen. Ein Punkt an dem die Analogie versagt scheint mir besonders wichtig zu sein:

Ein kurzer Blick in die Historie:

Die Industrialisierung hat einen großen Schritt nach vorne getan, als man begann Bauteile zu standardisieren. Die Errichtung eines Doms erstreckte sich früher über Jahrzehnte. Ein Grund – wenn auch nicht der einzige – war, daß das Gesamtgebilde aus vielen unterschiedlichen Steinen bestand von denen jeder für sich entworfen und angefertigt werden mußte. Für dem Chrystal Palace, der anläßlich der Londoner Weltausstellung 1854 errichtet wurde, ging man einen anderen Weg: Das – gleichfalls imposante – Bauwerk wurde aus verhältnismäßig wenigen unterschiedlichen Baukasten-Teilen zusammengesetzt. Von jedem Teil wurden zwar große Mengen hergestellt, aber es gab eben nur eine kleine Anzahl unterschiedlicher Teile.

In der herstellenden Industrie versucht man nach Möglichkeit, immer wieder die gleichen Teile zu verwenden, die man in großen Mengen produzieren kann. In der Software-Entwicklung marschiert man genau in die entgegengesetzte Richtung. Ein physisches Bauteile (wie ein Niet, Träger oder Zahnrad) wird hergestellt, angepaßt und eingebaut. Dann bleibt es dort – unverändert – bis es vielleicht irgendwann ersetzt werden muß. Ein Code- Fragment – als Beispiel eines nicht physischen, gedanklichen Bauteils – wird irgendwoher kopiert, abgepaßt und an beliebiger Stelle eingesetzt. Wenn es denn irgendwann nicht mehr paßt, wird es geändert, jederzeit. Das Code- Fragment gleicht also einer Niete, die morgen ein Träger und übermorgen ein Zahnrad sein kann. Ein kopiertes Code-Fragment führt vom Augenblick seiner Entstehung ein Eigenleben und ist vollkommen unabhängig von seiner Vorlage.

Mit jeder Code-Zeile erhöht sich die Komplexität der Anwendung. Denn jedes Code-Fragment hat seine eigene Entwicklung, kann unabhängig vom Rest verändert werden und muß immer einzeln angepaßt werden.Denn jedes Code-Fragment hat seine eigene Entwicklung, kann unabhängig vom Rest verändert werden und muß immer einzeln angepaßt werden. Die Regel lautet daher:

Für jedes Konzept soll es genau eine Implementierung geben.

Was heißt "wiederholen"?

Das klingt jetzt erstmal so, als wäre Kopieren die einzige Möglichkeit der Wiederholung. Das stimmt natürlich nicht, auch wenn es vermutlich die am häufigsten vorkommende Form ist (vielleicht weil 'C' und 'V' auf der Tastatur so nahe bei einander liegen). Die andere Variante der Wiederholung ist das "nochmal schreiben". Ein Beispiel: Irgendwo in der Anwendung wird ein Algorithmus ausgeführt, eine Suche in einem Graphen, eine Sortierung, das Vergleichen zweier Listen, oder auch nur der direkte Zugriff auf eine Datenbank durch öffnen einer Verbindung, Zusammenbau eines SQL-Statements und auswerten der Antwort. Vergessen wir für einen Augenblick, daß es für die meisten dieser Tätigkeiten coole Bibliotheken oder Frameworks gibt und nehmen an daß keine zur Verfügung stünden, daß wir darauf angewiesen sind, unseren Algorithmus komplett selbst zu schreiben. Erstaunlich viele Entwickler haben solche Algorithmen im Kopf und schreiben sie jedesmal neu, wann sie sie brauchen – so wie man auch eine for-Schleife oder eine if-Anweisung heruntertippt ohne groß darüber nachzudenken. Das Resultat ist eine wachsende Zahl von Boilder-Plate-Fragmenten, die – mit leichter Variation – immer wieder das Gleiche tun.

Wie hält man die Regel ein?

Ein Verbot läßt sich nur auf eine Art einhalten: Indem man unterläßt. Die Alternative zum Kopieren lautet daher: Statt das Fragment zu kopieren, lagert man es in eine Methode aus. Statt das Fragment einzufügen ruft man die Methode auf. Das funktioniert allerdings nur solange, bis das Fragment eine Anpassung erfordert. Im einfachsten Falle kann man das mit Methoden-Parametern durchführen, stößt damit aber – vor allem wenn das Fragment an weiteren Stellen verwendet werden soll – schnell an Grenzen (Für Clean Code etwa sind drei Parameter bereits eine Zumutung). Dann kommen Klassen, Factories und das ganze übrige Arsenal zum Einsatz. Welche Mittel man wählt und wie weit man gehen sollte um Duplikate zu vermeiden läßt sich nicht pauschal beantworten. Erfahrung und ein gesundes Urteilsvermögen leisten beim Design wie immer gute Dienste. Die Fragen die sich der Entwickler zwischen Ctrl-C und Ctrl-V daher immer stellen sollte ist:

Wo kann ich das noch verwenden?

Und wie kann ich das verallgemeinern?

Da Wiederholung auch dann entsteht, wenn man Fragmente wiederholt implementiert, sollte man sich die Fragen immer dann stellen, wenn man ein Teilproblem implementieren möchte das so klein ist, daß es nicht von vornherein als eigenes Module konzipiert wurde.

Bliebe noch die Frage, wieviele Code-Zeilen man denn überhaupt noch kopieren darf. Man orientiert sich bei der Verallgemeinerung am Zweck und nicht an der Menge. Eine Serie von Zuweisungen für eine Objekt-Initialisierung läßt sich oft gar nicht verallgemeinern, hingegen kann eine Berechnung die nur eine einzelne Zeile durchaus Wiederverwendungswert haben (man denke da zum Beispiel an die Berechnung der Mehrwertsteuer). Man orientiert sich da auch an den anderen Regeln: „Separation of Concerns“, „One function, one purpose“, „separation of abstractations“ etc. Dann sollen die Klassen und Methoden ganz von alleine und im richtigen Format entstehen.

Spezialfall „Distributed Repetition“

In großen Projekten mit vielen Entwicklern verteilt man die Aufgaben gerne so, daß sich die Entwickler möglichst nicht in die Quere kommen. Da es für viele Algorithmen eine bevorzugte Standard-Implementierung gibt, geschieht es leicht, daß mehrere Entwickler – mehr oder minder – identischen Code produzieren. Man mag einwenden, daß hier keine Wiederholung im Wortessinne vorliegt, doch der Effekt ist der gleiche: ein Code-Fragmnet existiert mehrfach und jede Emanation startet sofort ihre individuelle Entwicklung.

Was kann man dagegen tun?

Grundsätzlich gilt auch hier die oben genannten Fragen, allerdings kommt noch das Problem dazu, daß Entwickler A in der Regel nicht weiß, was Entwickler B so alles implementiert. Daher kann er auch nicht wissen ob für sein Teilproblem vielleicht schon irgendwo eine Lösung existiert. Dieses Problem kann man nur organisatorisch lösen oder zumindest abmildern. Die Kernfrage lautet dabei „Wo finde ich...?“ bzw. "Wohin packe ich?"

Struktur definieren

Das Team legt fest, wo in der Code-Basis Lösungen abgelegt werden für Teilprobleme die von allgemeinem Interesse sind. Also zum Beispiel für Konvertierungs-Funktionen, Graphen-Suche, Sortierung etc. Dazu gehören auch anwendungsspezifische Probleme die allgemein gelöst werden sollten (z.B. Ausgabe von Meldungen an den Benutzer, Formatierung von Log-Ausgaben, fachspezifische Berechnungen), für die es aber keine Vorgabe oder Lösung aus höherer Ebene gibt. Lösungen aus verwendeten Frameworks (z.B. EAP) werden natürlich bevorzugt. Damit haben alle Entwickler ein gemeinsames Source-Forum für allgemeine Lösungen.

Dokumentation

Umfangreiche Code-Basen sind unübersichtlich und komplexe Problemlösungen erklären sich nicht von selbst. Es ist empfehlenswert – z. B. mit einem Wiki – neben der Kernfunktionalität der Anwendung auch die Utility-Klassen und die verwendeten, allgemeinen Algorithmen zu beschreiben und das Customizing zu beschreiben und über die Dokumentation die Ablagestruktur zu beschreiben. Das ist allerdings zeitintensiv und lohnt sich eigentlich nur bei Anwendungen die über einen langen Zeitraum gewartet und geändert werden.

Kommunikation

Entwickler sollten über ihre Arbeit sprechen und von gefundenen Lösungen erzählen. Im Daily geht es meist nur darum ob die Aufgaben umgesetzt wurden und ob dabei Probleme auftraten, nicht aber darum wie Aufgaben gelöst wurden und was man mit der Lösung noch anderes anfangen kann. Pair Programming hilft schon etwas mehr, weil sich die Entwickler gemeinsam mit dem Lösungsweg auseinandersetzen. Allerdings ist die Verbreitung dabei gering, weil in der Regel nur zwei Entwickler mit einander sprechen. Optimal sind Entwickler-Runden in denen Entwickler von ihren Lösungen erzählen. Dabei geht es nicht in erster Line darum den Code zu reviewen, sondern darum daß das Wissen um das Vorhandensein von Lösungen schnell verbreitet wird. Bei dieser Gelegenheit kann auch über Ablage-Orte gesprochen werden und selbstverständlich kann auch darüber diskutiert werden, welche Lösungen allgemein verwendet werden sollen und welche nicht. Der Fokus sollte aber immer auf dem Austausch von Information liegen, nicht auf der Bewertung.