menü

Softwarequalität im Einsatz bei der METRO AG

Nachhaltige technische Veränderungen in ein großes Softwareprojekt einzuführen kann eine kluge aber auch durchaus anspruchsvolle Business-Entscheidung sein. Es ist ein ständiges Ringen mit der Trägheit der bereits existierenden, und immer weiter wachsenden Codebase, dem Abwägen von Prioritäten und der Entscheidung Neues zu bauen oder Bestehendes verbessern. Bei der METRO AG konnte eines unserer Teams eine User Interface (UI) Migration von Reflux zu Redux mit folgender Vorgehensweise erfolgreich durchführen:
   
  • Technische Probleme identifizieren, die Business-Probleme verursachen können.
  • ​Die Unterstützung von Analysten und des Project Owners sichern, damit alle mit den Kosten verursacht durch die technischen Probleme sind.
  • Kosten/Nutzen einer technischen Mitigation einschätzen.
  • Bei zu hohen Kosten einer vollständigen Lösung, nur das Problem lösen, das jene Business Flows unterstützt, die zukünftig eine größere Entwicklung durchmachen werden. Weniger kritische und bereits entwickelte Flows werden in eine “Black Box” umgewandelt.
  • Das Team mit der neuen Technik vertraut machen. Dies ist ein kritischer Teil der Migration. Pair Programming ist ein effektives Werkzeug, um Wissen unter Entwicklern zu verbreiten und gleichzeitig neue Funktionalität zu erarbeiten.
  • ​Die Vorteile des gleichzeitigen Einsatzes unterschiedlicher Technik, in diesem Fall der parallele Einsatz von Reflux und Redux, überwiegen die Kosten.​

Motivation

Dieser Artikel soll Teams bei der Förderung und Durchsetzung technisch hochwertiger Produkte unterstützen. Ich werde einerseits die geschäftsorientierten Argumente vorstellen, die wir der METRO AG präsentiert haben, um unsere UI-Codebase mit Redux umzuschreiben, sowie die spezifischen Schritte erklären, die wir unternommen haben, um die Geschäftsrisiken der Migration zu vermindern. Ich hoffe, dass dies anderen Teams hilft, ihre UI nach Redux zu verlagern und eine Diskussion über die Beziehung zwischen technischer Qualität und Geschäftswert im Allgemeinen zu beginnen.

Redux und Reflux sind zwei JavaScript-basierte UI-Libraries, die den State (Zustand) einer Applikation beschreiben und verwalten. Beide folgen der Flux-Architektur und ermöglichen einen unidirektionalen Datenfluss. Ich persönlich bevorzuge die Einfachheit von Redux.

Fairerweise möchte ich voranschicken, dass den meisten Problemen, mit denen wir bei Reflux zu kämpfen hatten, die Komplexität und der Mangel an Tests der von uns übernommenen Codebase, zugrunde lag. Wir hatten also keine faire Basis für einen direkten Vergleich zwischen Reflux und Redux und darum soll es in diesem Artikel auch gar nicht gehen. Tatsächlich sind die spezifischen, von uns angewendeten Technologien ziemlich irrelevant.

Das Geschäftsproblem

Ende 2016 trat ich einem unserer Düsseldorfer Teams bei, welches der METRO AG bei ihrer digitalen und agilen Transformation half. Hierbei sollte die herkömmliche Offline-Geschäftsabwicklung durch eine hochmoderne microservice-basierte Online-Plattform ersetzt werden. ThoughtWorks wurde engagiert, um die Entwicklung mehrerer der Microservices fortzusetzen.

Im Allgemeinen stellte sich das Entwickeln und Testen unserer geerbten Codebase als langsam und frustrierend heraus, besonders für die UI. Von Anfang an wollten einige unserer Teams Redux einführen, um die UI-Entwicklung anzukurbeln. Wegen des Mangels an Redux-Erfahrung im Projekt herrschte zunächst Besorgnis hinsichtlich der Opportunitätskosten eines großen Refactoring. Es war also eine konkrete Strategie zur Einschätzung der Geschäftsrisiken und -vorteile sowie der technischen Machbarkeit nötig.

Qualität als Argument

Der erste Schritt war, das Buy-In für Redux von der Geschäftsseite unseres Teams zu bekommen, d.h. von Business-Analysten, Qualitätsanalysten und vom Product Owner. Dafür stellten wir, die Entwickler, zunächst das technische Problem auf verständliche Art und Weise dar, sodass unsere Analysten anhand von geschäftsrelevanten Vor- und Nachteilen entscheiden konnten, inwieweit in Redux investiert werden sollte.

Folgendermaßen präsentierten wir die technischen und geschäftlichen Vorteile eines Wechsels zu Redux für unser Team und die METRO:
   
  • Besser skalierbares UI
  • Lesbarer, simpler Code
  • Bessere Erweiterbarkeit und leichtere Weiterentwicklung der Codebase
  • Reduzierte Sprödigkeit
  • Weniger Bugs (Softwarefehler)
  • Verbesserte Debugging und Incident-Response
  • Bessere Testabdeckung und Code-Testbarkeit
  • Lösung des teamübergreifenden UI-Problems und somit Verbesserung der gesamten Plattform
  • Gesteigertes Vertrauen in unser Team bei der METRO AG und Vergrößerung unseres Einflusses auf die Plattform

Die Spike

Durch unseren Pitch gewannen wir an Vertrauen und konnten sofort eine zweitägige Spike spielen, um die technische Machbarkeit eines schrittweisen Wechsels zu Redux zu erfassen. Hierbei war es wichtig herauszufinden, inwiefern Reflux und Redux harmonisch koexistieren konnten, denn eine “Big-Bang” Migration schien wegen des notwendigen Zeitaufwands nicht in Frage zu kommen.

Wir fanden heraus, dass die beiden Flux-Versionen prinzipiell problemlos parallel angewendet werden konnten. Daher war ein schrittweiser Wechsel zu Redux grundsätzlich möglich – siehe Details im Technischen Anhang. Das Refactoring könnte theoretisch in kleinen Schritten ausgeführt und, wenn erforderlich, auch pausiert werden, z. B. wegen anderer Arbeiten mit höherer Priorität. Dabei bliebe die Applikation in jeder Phase voll funktionsfähig.

Wegen des Mangels an Regressionstests und deren Labilität und enger Kopplung an Reflux mussten wir allerdings feststellen, dass die schrittweise Migration in jedem Stadium mit hohen Qualitätssicherungskosten verbunden war.

Ein großer Vorteil von Test-Driven Development (TDD) ist die Dokumentation der vorhandenen Codebasis. Die Behebung von Bugs wird sehr schwierig, wenn die Tests, die ein kritisches Stück Code dokumentieren, unzureichend oder durch Komplexität verdeckt sind. Beides traf in unserem Fall zu, sodass wir 75% unserer Zeit während der Spike damit verbrachten, die bestehenden Tests für offensichtlich funktionierenden Code zum Laufen zu bringen, was unsere schrittweise Migrationsstrategie extrem kompliziert und aufwendig machte. Ich bin allerdings weiterhin zuversichtlich, dass der schrittweise Ansatz bei einer etwas günstigeren Ausgangssituation gut funktionieren würde.

Wir waren jetzt in einer schwierigen Situation. Wir hätten natürlich einfach eine bessere Teststrategie für Reflux entwickeln und die Tests umgestalten können, um dann, nach jeweils bestandenen Tests, schrittweise zu Redux zu wechseln. In diesem Fall hätten unsere Entwickler viel Zeit in ein Framework investiert, dass wir im nächsten Schritt wieder entfernt hätten.


Redux-Migrationsstrategie

Dieses Dilemma lösten wir durch die Fokussierung auf den geschäftlichen Mehrwert für die METRO AG. Schlussendlich entschieden wir uns gegen die schrittweise Migrationsstrategie und schrieben stattdessen die wichtigsten Teile der Applikation neu – mit Redux. Dabei priorisierten wir jene Business Flows, die zu diesem Zeitpunkt am wenigsten weit entwickelt waren. Zum Beispiel hatten wir in unserem mobilen UI zwei unterschiedliche Interaktionsströme – 90% des einen war bereits entwickelt, doch nur 20% des anderen. Also stellten wir zunächst nur den letzteren auf Redux um.

Dies hatte den Vorteil, dass wir sofort eine erhöhte Entwicklungsgeschwindigkeit und Testbarkeit in den wichtigsten Business Flows erhielten und dabei mit verhältnismäßig wenig Reflux-Code in Kontakt kommen mussten. Wir verließen uns bei diesen Rewrites auf striktes TDD und unsere funktionalen Tests, um die existierenden Funktionalität zu sichern, da wir uns gegen das Umgestalten der existierenden Reflux-Tests entschieden hatten.

Neue Flows wurden von Anfang an in Redux geschrieben werden; ältere und weniger kritische Flows konnten von Fall zu Fall umgeschrieben werden. Die Grundlage unserer Arbeit bildete der kontinuierliche Austausch mit unserem Product Owner und unseren Analysten. So konnten wir, als wir die notwendigen Kapazitäten zur Verfügung hatten, drei Monate nach der erfolgreichen Redux-Migration unseres ersten mobilen Flows auch den zweiten umstellen. Das langfristige Ziel war es, immer mehr Business Flows zu migrieren, sobald sie für den aktuell zu liefernden Scope relevant wurden. So fanden wir letztlich zu einer anderen Art von schrittweiser Migrationsstrategie, nämlich einer aus Sicht des Produkts, statt einer aus Sicht der Technik.

Natürlich entstanden durch diese Herangehensweise auch Kosten, beispielsweise durch die langfristige parallele Anwendung zweier State Management Frameworks in der Codebase. Das setzte nämlich voraus, dass alle Entwickler sowohl mit Redux als auch Reflux vertraut waren. Mittlerweile hat sich dies als wenig problematisch herausgestellt, doch selbst als wir es noch als wesentliches Risiko sahen, waren folgende Vorteile für uns ausschlaggebend:
        
  • Die Migration auf Business Flows zu fokussieren ermöglicht es, die vorhandene Funktionalität im jeweiligen Flow zu überarbeiten und alle anderen Flows als “Black Box” zu behandeln. Es sollte nie einen Fall geben, bei dem ein einzelner Flow sowohl Reflux als auch Redux verwendete.
  • Redux ist Reflux so ähnlich, dass unsere Entwickler den größten Teil ihres Wissens einsetzen konnten.
  • In der Praxis arbeiteten wir sowieso hauptsächlich mit Redux, da die Flows, die am meisten Weiterentwicklung benötigten, zuerst umgestellt wurden.
  • Alle neuen Flows profitierten von Redux, was uns half, mit dem MVP Go-Live auf Kurs zu bleiben.
  • Durch den verringerten Umfang der Migration bzw. das Herunterbrechen auf einzelne Flows, brauchten wir weniger Zeit sie durchzuführen.
  • Einzelne Business Flows konnten mit verhältnismäßig geringen Konsequenzen und ohne die größeren Kosten eines “Big Bang Einsatzes” neu geschrieben werden, da die zuerst umzuschreibenden Flows noch klein waren.
  • Die neu zu schreibenden Flows konnten wir von Anfang an mittels TDD architektonisch sauber anfertigen, wobei unsere funktionalen Tests zeigten, dass alles noch so funktionierte wie es sollte.

Strangulieren oder Eindämmen?


Aufgrund der Größe der Legacy-Codebase und dem Zeitdruck, unter dem wir uns befanden, stand ich einer vollständigen Redux-Umschreibung der gesamten Codebase, einer sogenannten Strangulation, von Anfang an sehr skeptisch gegenüber. Ganz ohne Migration hingegen verloren unsere ThoughtWorks-Teams bei der METRO viel Entwicklerzeit mit Reflux, was unser Liefertempo verlangsamte.

Vor einigen Monaten wurde auf dem ThoughtWorks-internen “Software Development” Emailverteiler der Umgang mit großen Legacy-Systemen diskutiert. Die Beiträge halfen mir zu erkennen, dass der praktischste Weg in der Mitte lag. In unserem Fall hieß das, nur in Schlüsselbereichen ein Refactoring vorzunehmen und bereits vorhandene Features als “Black Box” zu behandeln – also den Legacy-Code einzudämmen anstatt ihn komplett zu strangulieren.

Es war wichtig, jeden Flow, der für Redux ausgewählt wurde, vollständig umzugestalten, da wir entdeckt hatten, dass die Qualitätssicherungskosten für die Aufrechterhaltung von zwei ineinander verzahnten Flux-Implementierungen im selben Business Flow bei unserem Zeitplan zu hoch waren.


Das Team weiterentwickeln

Zuletzt mussten wir sicherstellen, dass jeder im Team wusste, wie man Redux benutzt und wie unsere neue Codebase strukturiert war. Da nur zwei Entwickler in unserem Team bereits mit Redux vertraut waren und während der Migration alle Informationen für unsere neue Codebase bei mir zusammengelaufen waren, mussten wir noch allen Teammitgliedern Zugang zu den relevanten Informationen verschaffen.

Jedes größere Refactoring beinhaltet genau genommen zwei Migrationen: eine technische Migration und eine Migration von Wissen und Fähigkeiten, vorausgesetzt, dass noch nicht alle im Team mit der neuen Technik vertraut sind. Ich habe das Gefühl, dass Teams diese letztgenannte Aufgabe häufig als die größere Herausforderung empfinden, was wesentlich dazu beiträgt, dass sie nicht in Qualität investieren, in Fällen wo Qualität neue/bessere Technik bedeuten würde. Wie also löst man dieses Problem?

Zunächst möchte ich sagen, dass ich das Argument “das Erlernen neuer Arbeitsweisen vermindere die Entwicklungsgeschwindigkeit” für Unsinn halte. Softwareentwickler und vor allem Consultants bei ThoughtWorks lernen ständig dazu – durch neue Projekte lernen wir ständig neue Technik- und Geschäftsmodelle kennen. Das Aneignen und Anwenden neuen Wissens ist aus Sicht unserer Kunden eines unserer wichtigsten Merkmale. Und natürlich hat es in der Regel langfristig unangenehme Konsequenzen für das Geschäft, wenn sich Entwickler nicht weiterbilden. Trotzdem bekommen wir gerade dieses Argument so oft zu hören, dass wir darauf vorbereitet sein müssen.

Es war wichtig zu erkennen, dass die Durchführung der Redux-Migration einen Informationsknotenpunkt schaffen würde, und dass alle Beteiligten deshalb ihr neues Wissen zunächst mit den anderen Teammitgliedern würden teilen müssen. Es gibt zweifellos immer eine gewisse Versuchung, den eigenen Wissenspool zu schützen, denn es ist befriedigend, "gebraucht" zu werden. Weniger erfahrene Teammitglieder können dafür besonders anfällig sein. Auf jeden Fall ist diese Problematik weit verbreitet und es müssen Mittel und Wege gefunden werden, ihr entgegenzuwirken, zum Beispiel durch konstruktives Feedback.

Durchweg war Pair Programming ein unverzichtbares Mittel Wissen auszutauschen. Während der Redux-Migration selbst arbeitete ich mit unserem UI-Entwickler, dann holte ich immer abwechselnd andere Entwickler für neue Stories ins Redux-Land. Das klappte gut, sodass ich mich nach etwa einem Monat nicht mehr primär auf die Weiterbildung meiner Teammitglieder konzentrieren musste. Zu diesem Zeit hatten bereits zwei weitere Entwickler große Einblicke in die neue Technik, sodass sie wiederum anderen helfen konnten Redux zu lernen. Für Agile/Scrum waren wir ein relativ großes Team (~12 Entwickler), sodass nicht jeder sofort die Chance bekam Redux zu lernen. Doch war nun die kritische Masse erreicht und wir konnten uns gegenseitig unterstützen, ohne dass ich ständig involviert sein musste.

Hier sind die wichtigsten Ideen, die wir zukünftig auf andere Projekte übertragen, um für die scheinbar risikoreiche Fortbildung unserer Teams zu plädieren:    
 
  • Entwickler lernen leicht und schnell. Wenn man es richtig macht, hat das Erlernen einer neuen Technik keinen großen Einfluss auf die Geschwindig des Teams. Hingegen wird die Geschwindigkeit eines Teams beeinflusst, wenn Informationen nur schleppend weitergegeben werden oder wenn an der Weiterbildung des Teams gespart wird.
  • Es ist hilfreich, einen Experten für die neue Technik im Team zu haben. Ein solcher Experte stellt jedoch eine Bedrohung für die Produktivität des Teams dar, solange er der einzige Sachverständige bleibt. Jeder im Team muss vom Experten die Weitergabe seines Wissens einfordern.
  • Durch Pair Programming kann die Störung des normalen Workflows eines Teams gering gehalten werden. Erfahrenen Entwicklern sind die "neuen" Arbeitsmuster wahrscheinlich nicht komplett fremd und sie können sie oft schnell integrieren. Bildet man zuerst die Seniors aus, dann können diese ihr neues Wissen eigenständig mit anderen teilen.
  • Striktes TDD ist beim Erklären neuer Arbeitsmuster und Ideen extrem hilfreich, denn dabei wird durch die Beschreibung der Intention des Entwicklers sofort offensichtlich, wie ein System idealerweise funktionieren sollte.


Andere Vorteile   

 
  • Als Redux implementiert war, war es einfach, es zu erweitern. Jeder neue Flow, den wir hinzufügten, wurde in Redux durchgeführt, womit Reflux effektiv in die Legacy-Bereiche verbannt wurde.
  • Wir präsentierten der METRO die erfolgreich abgeschlossenen Redux-Migrationen, indem wir den jeweils umgeschriebenen Flow im Browser mit den Redux-Devtools vorführten. Das gab unseren Business-Kollegen ein Gefühl für das, was sich applikationsintern verändert hatte, da die Benutzeroberfläche ansonsten gleich aussah.
  • Unsere QAs und PO scheinen die Redux Devtools zu mögen. Sie sind für Entwickler und Analysten gleichermaßen von großem Interesse, weil sie den Applikationszustand visualisieren und Benutzerinteraktionen hervorheben, die sich in der Regel direkt auf Business-Konzepte beziehen.
  • Während der Migration gab es einige Fälle, wo wir Probleme mit der bestehenden technischen Umsetzung entdeckten, die wahrscheinlich aufgrund des Mangels an TDD aus Versehen eingeführt wurde. Wir haben sie notiert, aber trotzdem fast immer eins zu eins zu Redux übertragen, da wir nicht zu viele Dinge auf einmal verändern wollten, zumal wir weder über genügend Tests noch geschäftlichen Kontext verfügten, um uns die vorhandene Umsetzung zu erklären. Nach der Migration nutzten wir unsere Erfahrung, um diese Probleme bestmöglich zu lösen.
Zusammenfassung

Wir haben uns bei der METRO AG entschlossen in technische Qualität zu investieren. Wir haben eine Migration von Legacy-UI-Code zu Redux durchgeführt, wobei wir uns auf den lieferbaren Geschäftswert konzentrierten, um Bedenken hinsichtlich der Entwicklungskosten entgegenzuwirken. Wir haben gezeigt, dass große technische Verbesserungen auch unter Zeitdruck möglich sind. Wir diskutieren weiterhin offen über Qualität und streben somit kontinuierliche, geschäftlich sinnvolle Verbesserungen an. Ich glaube, dass diese Vorgehensweise zu einem wertvolleren und nachhaltigeren Produkt führt, und ich freue mich darauf, zu sehen, wohin die ThoughtWorks-METRO-Partnerschaft uns als nächstes führen wird.

Technischer Anhang

Unser ursprünglicher Plan einer schrittweisen Migration zu Redux ist unten zusammengefasst. Da Reflux und Redux beide dem grundlegenden Flux-Architekturmuster des unidirektionalen Datenflusses folgen, kann man den Reflux State-Zyklus mit Redux schrittweise übernehmen:
 
  1. Kompletter Refluxzyklus
  2. Verarbeitung aller Actions (z. B. asynchron) in Reflux, aber den kompletten Applikationszustand in Redux speichern. Reflux Store Aktion Listeners versenden nun Redux Actions, z.B. mit den Ergebnissen von API-Aufrufen und verwenden nicht mehr this.trigger. React-Komponenten erhalten den Redux-State durch Props und sind nicht mehr mit Reflux Stores verbunden. An dieser Stelle fungiert Reflux im Wesentlichen als async Middleware für Redux.
  3. Verarbeitung aller Actions (z. B. asynchron) in Redux mit entsprechender Middleware. Reflux Store Action Listeners bewirken jetzt nichts außer dem sofortiger Versand entsprechender Redux Actions.
  4. Kompletter Redux-Zyklus – React-Komponenten versenden nun Redux anstatt Reflux Actions.
     
Reflux state cycle