Refactoring – wenn sich Anforderungen konstant ändern

Refactoring gehört für die meisten softwareentwickelnden Menschen zum täglichen Handwerk – um so erstaunlicher ist es, dass dieser so wichtige Bereich in der Lehre gar nicht vorkommt.

Refactoring & die Frage der Perspektive

Viele Entwickler versteifen sich bei der Entwicklung eines Features darauf, bereits von Beginn an eine möglichst perfekte Lösung abzuliefern. Ob das jedoch der richtige Weg ist, darf bezweifelt werden.

Das zumindest ist die Ansicht von Tobias Schlitt und Kore Nordmann von Qafoo, mit denen wir vor Kurzem über dieses Thema reden konnten. Herausgekommen ist ein Interview mit ein paar spannenden Einsichten – unter anderem der Aufforderung, im ersten Schritt der Implementierung eines neues Features nicht gleich nach der perfekten Lösung zu suchen.


Refactoring gehört zum Standard-Handwerk von Entwicklern; dennoch wird es nicht gelehrt … warum ist das Thema so spannend?

Tobias Schlitt & Kore Nordmann

Refactoring: Interview mit Kore Nordmann und Tobias Schiltt.
Tobias und Kore sind Mitgründer der Qafoo GmbH. Sie verbinden tiefes Wissen mit Leidenschaft für Softwarequalität und helfen ihren Kunden, Software mit nachhaltiger Qualität zu schaffen.

Kore Nordmann: Refactoring ist unter anderem deswegen so spannend, weil sich die Anforderungen in allen Projekten konstant ändern – ändern müssen – weil die Projektmanager, die Product Owner oder das Business, neue Geschäftsfelder finden müssen. Das Projekt muss sich von der Konkurrenz absetzen, es muss neue Möglichkeiten finden, den Kunden zu motivieren; was auch immer gerade das Geschäftsfeld ist. Das bedeutet, dass im Geschäftsmodell Änderungen stattfinden. Das bedeutet in der Konsequenz, dass in der Software Änderungen stattfinden – und entsprechend muss die Software angepasst werden.

Tobias Schlitt: Der zweite Punkt ist, dass man sich als Entwickler bewusst machen muss, dass man eben nicht von Anfang an ein Business-Requirement sofort verstehen kann, weil man sonst kein Programmierer wäre, sondern der Business-Typ, der sich das Business-Requirement ausdenkt.

Dementsprechend ergibt es überhaupt keinen Sinn zu versuchen, die zu 100 Prozent perfekte Lösung dafür zu finden. Sinnvoller ist es, erst einmal etwas zu machen, zu evaluieren, ob es das ist, was das Business haben will, und es anschließend weiter anzupassen und in die Software einzufügen.

Kore: … nicht nur zu erfassen, was das Business haben will. Wir müssen auch dem Business helfen, die Ideen, die es hat, am Markt zu evaluieren. Sprich, das Feature muss im Zweifelsfall erst einmal live gehen, es muss sich zeigen, ob es tatsächlich einen Business Value hat, nicht nur einen angenommenen. Nur wenn das der Fall ist, erfassen wir die Hintergründe, die Konzepte hinter dem Problem, und können die dann implementieren. Dafür braucht man Refactoring.

Was ist jetzt das Problem? Entwickler machen offensichtlich sehr viel Refactoring; doch warum gibt es dann so viele Unklarheiten damit?

Kore: Ein Problem liegt schlicht darin begründet, dass Entwickler Menschen sind. Nicht im negativen Sinne! Der Mensch zeichnet sich viel mehr durch den Aspekt aus, dass er exzellent darin ist, Muster zu erkennen. Etwas, was KI noch nicht ansatzweise schafft zu modellieren. Ein Beispiel dafür ist ein Kleinkind, dem man einen Tisch zeigt und das sofort die Kategorie oder das Muster „Tisch“ wiedererkennt. Im Englischen nennt man das category learning. Das kann es auch auf andere Objekte – Tische – projizieren, die aber komplett anders aussehen.

Der Mensch ist exzellent darin, Muster zu erkennen. Doch das kann in der Softwareentwicklung zu einem Problem werden.

Darin sind Menschen exzellent, schon immer. Deswegen können wir, was wir können.

Das Problem im nächsten Schritt ist dann, dass das auch bei Softwareentwicklern so ist. Man kennt gewisse Muster, man kennt Kategorien von Problemen. Man bekommt ein neues Requirement vorgelegt, findet in seinem Kopf sofort diese Muster dort wieder und versucht, sie auf das Problemen zu mappen. Das ist häufig falsch, weil wir nicht in die Zukunft schauen können. Niemand kann das, leider. Deswegen wissen wir auch nicht, ob dieses Muster, das wir sofort meinen zu erkennen, tatsächlich der Realität, also der Domäne, entspricht.

Toby: Um jetzt den Bogen zum Refactoring zu spannen: Das Problem an der Stelle ist, dass Entwickler versuchen, eine kategorisierte Lösung zu entwickeln. Sie versuchen, 100 Prozent perfekt zu arbeiten.

Sie sollten sich stattdessen jedoch bewusst machen, dass sie die Lösung in Zukunft sowieso wieder ändern müssen, dass sie refaktorisieren müssen. Sie sollten lieber dahingehend optimieren, änderbaren Code zu haben. Das wäre der richtige Weg.

Kore: Änderbarer Code ist im Zweifelsfall das, was man gemeinhin als schlechten Code bezeichnet. Ich würde behaupten, dass eine der Haupterfolgsfaktoren für Software wie WordPress, die allgemein als schlecht gilt, ist, dass jeder an beliebige Codestellen springen kann, versteht, was passiert und strukturelle Änderungen sofort vornehmen kann. Diese Änderungen sind bei WordPress für dessen Verbreitung und die Nutzung nicht ausreichend abstrahiert, ist trivial lokalisiert und deswegen nicht wartbar, weil diese Änderungen nicht mit den Änderungen anderer Menschen zusammenspielen.

Das ist ein Problem von WordPress; so etwas kommt im Projektgeschäft nicht vor. WordPress ist Commodity-Software, wird von vielen Menschen auf beliebige Art und Weise modifiziert. Es wird als Blog benutzt, als Shop, als Content-Management-System und als was nicht alles.

Wenn ich aber nur eine Software schreibe, die ich als Unternehmen live bringe, habe ich genau einen Use Case für diese Software und kann Code einfach modifizieren, ohne dass ich für meinen Domänencode direkt einen Abstraktionslayer wie Symfony oder andere Bibliotheken brauche. Wenn sich die Requirements ändern, ändere ich den Code an einer Stelle. Das ist am einfachsten, wenn der Code in einer dummen Methoden steckt; im schlimmsten bzw. im einfachsten Fall. Wenn ich dann verstanden habe, dass das Problem in ähnlicher Art und Weise in meiner Software mehrfach auftaucht und sich der Business Case validiert hat, kann ich anfangen, auf Basis eines bereits verstandenen Geschäftsmodells sinnvoll Abstraktionen zu schaffen.

Widerspricht das nicht dem, was gemeinhin als guter Code aufgefasst wird?

Kore: Vollkommen, und zwar mit voller Absicht. Ich gehe noch mal einen Schritt zurück. Unternehmen wie booking.com setzen es beispielsweise so um, dass ein Feature implementiert, deployed und live geschaltet wird.

Wenn man mal eben ein Feature ausprobiert, wäre es dumm, viel zu viel Arbeit zu investieren.

Nur wenn das aus Geschäftsperspektive, aus Usability-Perspektive oder was immer das Interesse ist, validiert wird, geht das Feature in eine Testing und Refactoring Chain. Dann nimmt sich ein Team dieses Features an und zieht den Code sauber, sodass er wartbar ist. Code, den ich lange brauche, der lange auf meiner Plattform überleben soll und der mein Kern-Business-Modell abbildet, der muss natürlich getestet und sauber sein.

Aber wenn ich mal eben ein Feature ausprobiere, dann wäre es dumm, viel zu viel Arbeit zu investieren und nicht antizipieren zu können, in welche Richtung der Code sich wahrscheinlich entwickeln muss, um den Business-Use-Case tatsächlich abzubilden und dafür eine falsche Abstraktion zu schreiben. Es wäre dumm, diese Zeit zu investieren. Der Punkt ist, und das ist ein sehr schönes Zitat: Code duplication is better than wrong abstraction.

Die richtige Abstraktion, wenn man sie von vornherein findet, ist das Göttliche, das ist perfekt. Super! Machen! Das ist nur selten der Fall, weil wir die Zukunft nicht abschätzen können. Lieber Code-Duplikation, die einfacher zu refaktorisieren ist, als die falsche Abstraktion, die man nie wieder oder nur mit sehr, sehr viel Aufwand aus der Software herausbekommt.

Lieber Code-Duplikation, die einfacher zu refaktorisieren ist, als die falsche Abstraktion, die man nie wieder oder nur mit sehr, sehr viel Aufwand aus der Software herausbekommt.

Toby: Das ist sehr spezifisch für Projekte, also typischerweise Agentur, interne IT-Abteilungen, Startups, Software-as-a-Service. Wer dagegen Off-the-Shelf-Lösungen entwickelt, also ein eZ-Publish, ein Shopware, ein Oxid – das heißt, ich baue ein Produkt, das viele Leute einsetzen und an ihre Bedürfnisse anpassen wollen –, dann will ich überall Clean Code haben, saubere, stabile APIs. Viel Gehirnschmalz, den viele Leute reinstecken, um eine sinnvolle Architektur und sinnvolle Erweiterbarkeit zu haben. Nur weil ich das in meinem Produkt, auf dem ich gerade mein Projekt aufbaue, vorfinde, heißt das nicht, dass ich das im kompletten Projekt so durchziehen muss. Zitat aus unserem Talk auf der IPC 2017: „Du bist nicht Symfony in deinem Projekt, du musst nicht für alles ein Interface bauen.“

Wenn du ein Symfony-Bundle releasen würdest, dann musst du für alles ein Interface bauen. Das wollen andere Leute benutzen, das ist dann wieder Commodity-Software. Aber in dem aktuellen Projekt, in dem du arbeitest, ist es erst mal nicht notwendig.

Wenn du aber feststellst, das ist etwas, was ich wiederverwenden will, was ich in Zukunft wieder brauche, bei der ich meine eigene wiederverwendbare Bibliothek baue, dann kann man anfangen, das in die Richtung zu refaktorisieren, von der man weiß, dass das in Zukunft so aussehen muss. Aber wenn ich mir das von Anfang vorstelle, dass es so in Zukunft einmal werden sollte, ist die Chance sehr hoch, dass es schief geht und ich an einem Punkt ende, an dem ich refaktorisieren muss, aber etwas gebaut habe, was nicht einfach zu refaktorisieren ist.

— Spannendes Thema? —

Du interessierst Dich für weitere Informationen zu diesem Thema? Setz‘ Dich über unser Kontaktformular mit uns in Verbindung, wir stehen Dir gerne mit Rat und Tat zur Seite.

Ihr verlangt also von den Entwicklern, dass sie zwei komplett unterschiedliche Leben leben. Dass sie selbst entscheiden müssen, in welcher dieser genannten Situationen sie sich gerade befinden …

Toby: … schlimmer, du musst eigentlich immer den Trade-off zwischen beiden Situationen finden, die sind ja auch nicht mutual exclusive. Es kann passieren, dass du ein Projekt entwickelst und irgendwann kommt jemand an und sagt: „Können wir das auch auf unseren eigenen Server installieren? Wir wollen aber jetzt das irgendwie anpassen.“ Dann rutschst du von Projekt in Commodity-Softwarerichtung. Das ist nicht einfach.

Was fehlt dafür?

Kore: Ein Punkt, der sicherlich fehlt, um den Bogen zurück zum Anfang zu schlagen, ist das Wissen um Refactoring. Wenn ich aus einem Projekt Commodity-Software machen will, muss ich refaktorisieren. Das ist erstens in PHP leider mangels statischer Typisierung schwer automatisiert umzusetzen, auch wenn der Tool-Support in PhpStorm nicht schlecht ist. In der Ausbildung fehlt der Punkt mit Sicherheit, sodass sich Entwickler damit schwertun, schlicht, weil sie es nie beigebracht bekommen haben.

Wenn der Schritt in Richtung Commodity-Software gegangen wird, muss und sollte dem Business bekannt sein, dass ein relevanter Refactoring-Schritt gegangen werden muss.

Es fehlt vor allem aber auch das Bewusstsein, wenn man ein Projekt hat und daraus ein Business Pattern extrahiert, sprich Commodity-Software – ich habe hier ein CMS, ich release das jetzt als Public CMS –, dann muss und sollte auch dem Business bekannt sein, dass dann ein relevanter Refactoring-Schritt gegangen werden muss.

Große Hersteller von Commodity-Software haben verstanden, was sie machen müssen. Aus unserer Sicht ist eher das Problem, dass Entwickler von Projekten nicht verstanden haben, dass sie diese Abstraktion zu Beginn nicht machen müssen. Auf der anderen Seite, wenn Aspekte der Software über lange Zeit stabil und wartbar sein müssen, dann muss ich sie irgendwann sauber machen, sodass sie wartbar sind. Ein anderer Aspekt von Clean Code ist, dass er in der Tendenz wartbarer, allerdings auch schwieriger modifizierbar ist.

Der perfekte Entwickler kennt das? Was muss man ihm dafür an die Hand geben?

Kore: Unter anderem einen Begriff von Business Value und von Stabilität.

Ein Problem, das wir immer wieder in Unternehmen feststellen, ist, dass Entwickler/Teams häufig nicht wissen, wo der eigentliche Core Value ihrer Applikation liegt. Wir sagen den Entwicklern immer wieder – oder versuchen zu vermitteln –, dass die Kern-Domäne der Applikation gut getestet und stabil und sauber sein muss. Wenn ihr irgendwann ein Experiment macht, irgendwo, dann muss das nicht sauber getestet sein, sondern sich erstmal validieren; davon haben Entwickler häufig keinen Begriff. Es ist die Verantwortung des Produktmanagements, das zu vermitteln. Das ist einer der Punkte. Fällt dir noch ein weiterer ein, Toby?

Toby: Was ich auch noch wichtig finde, ist, dass die Leute selbstreflektiert werden, daraus zu lernen: „An dem Punkt habe ich jetzt überabstrahiert, an dem Punkt ist es unsauber geworden, da hätte man eher refaktorisieren müssen“. Dass sie dadurch in einen Flow kommen, bei dem Refactoring zum Daily Business gehört.

Ich höre so oft: „Wir haben keine Zeit für Refactoring. Wo müssen wir denn Refactoring-Tickets einführen? Wir müssen mal einen Refactoring-Sprint machen.“ Das ist Quatsch. In jedem Ticket muss ein Teil Refactoring-Zeit mit drinstecken, genau wie ein Teil Testing-Zeit drinstecken muss und ein Teil Analysephase. Genauso muss Refactoring essentiell überall drin sein. Wenn man das mal gerafft hat und anwendet, wird die Welt schon ein ganzes Stück besser.

Wo sollte man in der Ausbildung ansetzen? Oder ist es etwas, dass man erst im Projekt lernen kann?

Kore: Das muss man zum Teil im Projekt lernen. Das ist auch ein Problem mit Workshops, die wir zu diesem Thema haben: Das ist nahezu nicht generisch zu lösen. Stattdessen muss man in ein Projekt reingehen, Projektverständnis aufbauen und das am echten Projekt zeigen, denn virtuelle Projekte haben keinen Business Value bzw. es ist schwer, einen komplexen realen Business Value zu finden.

Toby: Wobei, wir hatten an der Uni ein kleines Mini-Java-Projekt gemacht, um überhaupt Java zu lernen. In der Ausbildung als Fachinformatiker wurde zu C++ gelehrt und man musste Software-Projekte machen, das war aber alles nie viel Zeug. Das waren immer nur fünf Requirements, die musste man umsetzen, danach wird es abgehakt und ist fertig. Solche Szenarien wie, „da ändert sich mal was, wie geht man denn vor, wenn man jetzt Klassen gebaut hat und die in andere Strukturen gießen will“, das wird bei den ganzen Grundausbildungen nicht unterrichtet, zumindest nicht zu der Zeit, als ich das gelernt habe.

Erst den Test schreiben, dann die Implementierung; danach refaktorisieren, damit der Code auch die Designkriterien erfüllt.

Kore: Die Softwerkskammen machen zumindest Katas in die Richtung; wir haben auch Katas auf einem öffentlichen Repository in dieser Richtung: Dass man anfängt mit zu implementierenden Requirements und in einem zweiten, dritten, vierten Schritt ändern sich dann die Requirements jedes Mal zum Teil, oder Bestandteile der einzelnen Requirements ändern sich. Das sind natürlich auch Schritte, die kann man in der Ausbildung machen. Die kann man lernen und ausprobieren. Auf diese Weise kann man Erfahrung im Refactoring sammeln.

Das ist auch Bestandteil des Test-driven-Development-Zykluses: Erst den Test schreiben, dann die Implementierung schreiben und danach – dafür kann sich auch selbst formale Kriterien selbst vorgeben, wie die Object Calisthenics – refaktorisieren, damit der Code, nachdem er die Tests erfüllt, auch die Designkriterien erfüllt und das dann durchiterieren. So lehren wir das auch in Katas zum Beispiel und lassen das von den Entwicklern machen. Das funktioniert, da hast du recht.

Toby: Was bei einem bestehenden Team sehr stark weiterhilft, ist jemand, der das coachen kann. Etwa ein erfahrener Senior-Entwickler, der das beherrscht, der das auch erläutern kann und der das am aktuellen Projektcode und an Pull Requests etc. immer wieder begreiflich macht und den Leuten erklärt „Pass mal auf, das und das sieht man jetzt hier. Warum hast du das so und so gebaut? Meinst du nicht, dass es ein bisschen zu weit gedacht ist oder ist es sicher, dass es in Zukunft so wird? Wie könnte man das denn da rauskriegen?“ Also immer weiter reflektiert …

Kore: … im Mob Programming, im Pair Programming …

Toby: … im Code-Review-Prozess.


Wir bei Mayflower haben bereits seit über einem Jahrzehnt viel Erfahrung im Bereich der Softwaremodernisierung und des Refactorings sammeln können, die wir natürlich über Workshops weitergeben und passend dazu als Implementierungspartner zur Verfügung stehen.

Wer mehr Informationen erhalten möchte, der kann uns per E-Mail kontaktieren. Gerne stehen wir dann mit Rat und Tat zur Seite.

 

Aufmacher: Claus Grünstäudl

Für neue Blogupdates anmelden:


9 Gedanken zu “Refactoring – wenn sich Anforderungen konstant ändern

  1. Wunderbare Zusammenfassung zum Thema, vielen Dank! :)

    Besonders gut finde ich den Punkt „Code duplication is better than wrong abstraction.“! Da ist mir eingefallen, dass ich vor Jahren einen Blog-Post geschrieben habe der sich im Detail mit den Ausnahmen der DRY-Regel beschäftigt:
    https://lastzero.net/2010/12/good-code-sometimes-needs-redundancy-the-dynamic-aspects/

    In JavaScript war einer der möglichen Refactoring-Schritte früher tatsächlich Code nachträglich zu duplizieren, damit er schneller läuft (function inlining). Macht man inzwischen weniger, weil Browser und Hardware schneller sind.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.