Über unsMediaKontaktImpressum
Philipp Rembold 30. Januar 2024

Flaccid Scrum – woran Agile scheitert und wie Sie es verhindern

Was Martin Fowler als Flaccid Scrum (schlaffes Scrum) bezeichnet, beschreibt die Situation, in der ein Team nicht weiß, wie es wirklich entwickeln sollte. Mit den vier technischen Praktiken TDD, Refactoring, Pair und Ensemble Programming sowie Simple Design helfen wir Ihnen, die Code-Basis übersichtlich zu gestalten, agil zu bleiben und Flaccid Scrum zu verhindern.

Aller Anfang ist leicht...

Kennen Sie das? Sie arbeiten in einem neuen Projekt, keine Zeile Code ist bislang geschrieben. Sie und Ihre Kollegen haben die Domäne verstanden, das Problem begriffen, das Team ist heiß darauf, den Kunden mit dem neuen Produkt das Leben leichter zu machen. Sie haben den neuesten und geeignetsten Tech-Stack identifiziert und der erste Sprint startet. Der Scrum-Master hat den initialen Scope auf der Burndown Chart eingetragen und es geht los.

Im ersten Sprint werden sechs Stories fertiggestellt und das entspricht 16 Story Points. Im zweiten Sprint steigt die Velocity sogar noch auf 18 Story Points pro Sprint. Alles ist gut in der Welt.

Drei Monate später

Die Welt ist schlecht und das Projekt wird gegen die Wand fahren. Die Velocity hat sich auf neun Story Points pro Sprint verringert und die Kunden finden Bugs im Vorab-Release, die eigentlich schon mal gefixt waren. Manche eigentlich kleinen Backlog Items stehen seit Wochen auf In Progress, die Product-Ownerin ist unzufrieden und die Teamleiterin Stefanie hat ein Krisenmeeting einberufen und will wissen, wie es dazu kommen konnte.

Das Meeting

Alles kommt auf den Tisch: Das Design passt doch nicht zum Problem. Heute würde man eine andere Aufteilung der Klassen wählen. Außerdem unterstützt es die Tests nicht und neue Unit-Tests zu schreiben ist schwierig. David ist krank geworden und niemand konnte für ihn übernehmen, denn er war der einzige, der die Datenbankverbindung kannte. Und eigentlich bräuchte man jetzt schon eine komplette Neuimplementierung.

"Wie lange würde das dauern?", fragt John. "Etwa zweieinhalb Monate", sagt Thomas, der Architekt. Rebecca, die Product-Ownerin, wirft ein: "Wir müssen aber am existierenden Code weiter arbeiten, wir müssen die Bugs beseitigen und die Features liefern, damit unsere Kunden uns bezahlen!" – "Dann wird es länger dauern!", erwidert Thomas. Stefanie seufzt: "Das haben wir doch alles schon so oft erlebt. Warum passiert uns das immer wieder?"

Flaccid Scrum

Was ist in diesem Beispiel passiert, bei dem Ähnlichkeiten mit lebenden oder verstorbenen Projekten nicht beabsichtigt, aber unvermeidbar sind? Das Team richtet sich doch offensichtlich nach einer Entwicklungsmethode, die durchaus durchschlagende Erfolge erzielen kann, schließlich gibt es ein Team, eine Product-Ownerin, einen Scrum-Master, Schätzungen in Story-Points... Alles Merkmale von Scrum und Scrum ist doch wohl Agile?

Was wir hier haben, ist das, was Martin Fowler einmal "Flaccid Scrum" genannt hat, einen "schlaffen Scrum", wobei Scrum ursprünglich eine Aufstellung beim Rugby ist. Die Scrum-Methodik beschreibt, wie ein Software-Team mit dem "Business" umgehen soll, also mit den Kunden, ihren Repräsentanten, den Auftraggebern und anderen "Stakeholdern". Sie beschreibt aber nicht, wie ein Team wirklich entwickeln sollte. Ich möchte Ihnen im Folgenden vier technische Praktiken vorstellen, die verhindern können, dass Ihr Team wieder in die beschriebene Situation kommt – und sogar helfen können, aus einer solchen Situation wieder herauszukommen.

1. Testgetriebene Entwicklung

Was ist das?

Testgetriebene Entwicklung, kurz TDD für Test Driven Development, ist eine Methodik, bei der Sie das erwartete Verhalten der Software in einem automatisierten Test hinterlegen, bevor Sie die Software implementieren.

Die Regeln von TDD lauten wie folgt:

  1. Schreibe keinen Produktcode, solange kein Test fehlschlägt.
  2. Schreibe nicht mehr Testcode als nötig ist, um einen Fehler zu provozieren. Compiler-Fehler zählen als Fehler.
  3. Schreibe nicht mehr Produktcode als nötig ist, um den Fehler zu beheben.

Ein möglicher Entwicklungsprozess, der diese Regeln einhält, ist der folgende:

  1. Schreibe Testcode, bis der Test fehlschlägt. Kompiliert der Produktcode nicht, zählt dies als fehlschlagender Test.
  2. Schreibe gerade genug Produktcode, bis der Test erfolgreich durchläuft.
  3. Refactore den Produkt- und Testcode, bis die Regeln von "Simple Design" erfüllt sind.

In jeder der drei Phasen haben wir einen etwas anderen Fokus: In der ersten Phase fokussieren wir uns auf das externe Design des Codes, auf den Aufrufer. In der zweiten ermitteln wir den kürzesten Weg, um den erkannten Fehler (der durch den fehlschlagenden Test demonstriert wird) zu beheben. In der dritten Phase suchen wir nach der besten Lösung.

Wie macht man das?

Beim ersten Kontakt mit dieser Methodik erscheint sie ausgesprochen seltsam, soll man doch Code testen, den es noch nicht gibt. Entstanden ist TDD aber aus der Praxis, automatische Acceptance-Tests zu jeder Story zu schreiben, die man implementiert und die den Kontrakt zwischen Team und Auftraggebern darstellen: Wenn alle diese Tests erfolgreich durchlaufen werden, gilt die Story als implementiert. Auch diese Tests sollten vor oder doch zumindest parallel zur Entwicklung geschrieben werden. Gleichzeitig wird ja auch jede Story in kleine, implementierbare Häppchen zerlegt. Es ist nur natürlich, auch für diese Häppchen dann die Tests zuerst zu schreiben.

Gleichwohl ist es nicht leicht, mit TDD zu beginnen. TDD trainiert man am besten mit Übungsaufgaben, die man immer wieder durchführt, sogenannten "Katas". Es gibt zahlreiche gute Tutorials und auch Kurse im Internet zu finden. Für den Einstieg in den Prozess in existierendem Produktcode eignen sich Bugs, da man sie sowieso nachstellen muss – und wenn man dafür automatisierte Tests zuerst schreibt, kann man diese debuggen, um den Fehler zu finden.

Warum macht man das?

Meiner Erfahrung nach führt diese Art, Code zu schreiben, zu drei Dingen:

  1. Vertrauen, dass aller Code, den man geschrieben hat, so funktioniert, wie er soll.
  2. Besseres Design, denn der Testcode liefert direkt Feedback, ob der Code nutzbar ist.
  3. Sprechender Code, denn gute Tests sagen mir, wie der Code benutzt werden soll. TDD produziert in der Regel bessere Tests, da man in sie viel "Gehirnschmalz" steckt.

Es gibt noch zahlreiche andere Vorteile. Insbesondere ist derartig produzierter Code so gut, dass man ihn in einem CI/CD-Prozess täglich oder mehrmals am Tag ausliefern kann.

2. Refactoring

Was ist das?

Das Refactoring ist der Prozess, mit dem man das Design existierenden Codes verbessert. Auch ein einzelner, wohldefinierter Schritt (z. B. Variable umbennen) wird als Refactoring bezeichnet.

Wie macht man das?

Wann immer das Design des Codes nicht zu dem passt, was man mittlerweile über das System weiß, muss man es ändern. Dazu ändert man den Code in vielen kleinen (noch kleineren!) Schritten und nach jedem dieser Schritte müssen alle Tests weiterhin erfolgreich durchlaufen werden. Z. B. kann Variable umbennen so erfolgen:

  1. Die Variable mit neuem Namen einführen
  2. Alle Schreibzugriffe auf die alte Variable auch auf die neue Variable anwenden
  3. Alle Lesezugriffe auf die alte Variable auf die neue Variable umstellen
  4. Die alte Variable löschen

Nach jedem der Schritte müssen noch alle Tests funktionieren. Schlägt ein Test nach einem Schritt von 3. fehl, weiß man, dass man bei 2. irgendwo etwas übersehen hat. Dann nimmt man seine Änderung zurück und schaut sich den zweiten Schritt nochmals an. Wichtig ist, dass man jederzeit in einem Refactoring aufhören kann – da ja immer alle Tests erfolgreich durchlaufen werden.

Ein guter Einstiegspunkt ist, wenn Sie eine schwierige Aufgabe vor sich haben. Stellen Sie sich folgende Frage: Wie müsste das Design sein, damit die Aufgabe "leicht" ist? Ändern Sie das Design, bis es an diesen Punkt gelangt ist.

Warum macht man das?

Jeder, der länger als einen Monat am gleichen System arbeitet, wird irgendwann feststellen, dass das Design nicht mehr passt – recht unabhängig davon, wann und wie dieses Design definiert wurde (was nicht bedeutet, dass "designen" nicht wichtig wäre). Statt nun alles wegzuwerfen und von vorne anzufangen ("Diesmal machen wir alles richtig."), passt man das existierende Design an den neuen Wissensstand an. Wenn man das kontinuierlich macht, wird das Design fortlaufend besser (und Sie haben den Zeitpunkt des Designs an einen Punkt verschoben, an dem Sie sehr schnell Feedback für Ihr neues Design bekommen).

3. Pair und Ensemble Programming

Was ist das?

Pair oder Ensemble Programming beschreibt die Praxis, in der nicht ein:e Programmierer:in alleine im stillen Kämmerlein seine/ ihre Aufgabe löst, sondern zwei oder mehr Programmierer:innen mit derselben Aufgabe betraut sind. Dabei gibt es verschiedene Möglichkeiten, von "einer tippt und einer redet" bis hin zu "alle arbeiten gleichzeitig am gleichen File".

Wie macht man das?

Befinden sich alle physisch am gleichen Ort, bietet es sich an, dass Sie sich mit Ihren Kolleg:innen vor einem Bildschirm versammeln.

Sind Sie zu zweit, könnte z. B. einer Tests schreiben und einer Produktcode (tauschen Sie regelmäßig) oder Sie tauschen alle 15 Minuten: Während Sie tippen, navigiert Sie Ihr:e Partner:in durch den Code und umgekehrt. Oder Sie schließen mehrere Mäuse und Tastaturen an den Computer an und arbeiten "wild durcheinander".

Sind drei oder mehr Personen beteiligt, bietet es sich an, dass Sie durchtauschen. Es ist immer einer in der Rolle des "Fahrers", der gerade Maus und Tastatur bedient, einer in der Rolle des Navigators, während der Rest durchschnauft, Fragen stellt und über das Design mitdiskutiert (oder auch mal Google befragt, wie man ein Problem lösen könnte).

Die Lösung komplexer Probleme ist viel einfacher, wenn man nicht mit ihnen allein ist.

In einem virtuellen Setup bietet Screensharing über populäre Kollaborations-Software die gleichen Möglichkeiten. Außerdem bieten einige IDEs die Möglichkeit, direkt mit Kolleg:innen zusammenzuarbeiten.

Warum macht man das?

Diese Art zu programmieren macht es sehr leicht, neue Teammitglieder an Bord zu holen und kann jeden formellen Code-Review-Prozess überflüssig machen. Sie verhindert das Entstehen von Wissensinseln und reduziert so das Risiko, kritisches Wissen zu verlieren, wenn einzelne Teammitglieder das Team verlassen. Außerdem ist die Lösung komplexer Probleme viel einfacher, wenn man nicht mit ihnen allein ist. Paaren fällt es häufig leichter, sich an die strikte Disziplin von TDD zu halten und sie produzieren bessere Designs.

4. Simple Design

Was ist das?

Ein System erfüllt Simple Design, wenn es die folgenden vier Eigenschaften hat. Dabei beschreibt diese Liste sowohl die inhaltliche Priorität als auch die Reihenfolge, in der diese Eigenschaften erfüllt werden müssen.

  1. Funktional korrekt – alle Tests werden erfolgreich durchlaufen
  2. Expressiv – der Code legt dar, wofür er geschrieben ist
  3. Keine Code-Duplikation
  4. Reduzierte Anzahl an Code-Artefakten

Wie macht man das?

Erinnern Sie sich an den TD-Prozess: Dort steht als letzter Schritt jedes Zyklusses: "Refactore den Produkt- und Testcode, bis die Regeln von Simple Design erfüllt sind."

Der erste Punkt der Liste ist an dieser Stelle immer erfüllt, denn wir haben gerade genug Produktcode geschrieben, um unsere Tests zu erfüllen.

Nun schauen wir: Spricht der Code die Wahrheit? Oder brauchen wir neue Methoden, gehört eine bestimmte Methode in eine andere Klasse, die Klasse in ein anderes Paket? Muss die Variable anders heißen? Ist die Methode oder Variable vielleicht überflüssig?

Wenn wir damit einverstanden sind, wie expressiv der Code ist, nehmen wir jede Code-Duplikation weg, die nichts zu dieser Expressivität beiträgt. Und wenn wir jetzt noch – ohne 2. oder 3. zu verschlechtern – Artefakte (Methoden, Klassen, Interfaces, gar Pakete) entfernen können (indem wir sie an ihre Aufrufstelle verschieben, ein sogenannter "Inline"), machen wir auch das.

Warum macht man das?

Ein gutes Design erleichtert das Arbeiten. In agiler Entwicklung, wenn wir iterativ arbeiten und Features schnell ändern wollen, wenn wir Neues lernen, muss das Design genau dies ermöglichen. Ein Design, das im obigen Sinne einfach ist, tut genau das: Es ermöglicht Änderungen und zwar sowohl von Features als auch des Designs selbst.

Das letztendliche Ziel ist, ein funktionierendes System zu haben ("erfüllt 1."), das leicht zu lesen und zu verstehen ist ("erfüllt 2."), in dem kein Code öfter als nötig geschrieben wird ("erfüllt 3.") und in dem die Anzahl der Klassen, Methoden, Variablen usw. so gering wie möglich ist, ohne einen der drei erstgenannten Punkte zu verletzen. Die Komplexität eines Designs ist einer der Aspekte, die bestimmen, wie leicht eine Anwendung verstanden und geändert werden kann. Sie hat einen Einfluss darauf, wie schnell neue Teammitglieder eingebunden werden können und wie viel Zeit für die Implementierung einer neuen Funktion benötigt wird. Kurz gesagt: Ein zu komplexes Design wirkt sich negativ auf die Produktivität aus. Der andere wichtige Aspekt, wie leicht eine Anwendung verstanden und geändert werden kann, ist die Komplexität der Anforderungen. Handelt es sich um ein hochgradig konfigurierbares Buchhaltungssystem, das obskure Gesetze einhalten muss, oder ist es "Minesweeper"?

Diese beiden Aspekte sind jedoch nicht additiv. Eine Anwendung, die sehr komplexe Anforderungen erfüllt, kann ein sehr ausgeklügeltes Design verwenden, um die Anwendung leichter verständlich zu machen, als es der Fall wäre, wenn das Design naiv und nicht einfach wäre. Wir bemühen uns, unser Design so einfach wie möglich zu gestalten und gleichzeitig die Komplexität der Anforderungen zu reduzieren, was meiner Meinung nach der wichtigere Aspekt ist, wenn es darum geht, wie leicht eine Anwendung verstanden wird.

Zusammenspiel

TDD ermöglicht Refactoring

Der offensichtlichste Punkt ist wohl, dass testgetriebene Entwicklung Refactoring ermöglicht. Wenn wir mit einem hohen Maß an Sicherheit sagen können, dass eine Änderung an der Struktur eines Codes das Verhalten desselben Codes nicht verändert, können wir ein Refactoring durchführen, wie "klein" oder "groß" es auch sein mag, und sicher sein, dass wir nichts kaputt gemacht haben. Das ist der Schlüssel, um nie wieder in die Situation zu kommen, dass wir eine einfache Funktion nicht in unser Programm einbauen können, weil es ein Redesign braucht und das einfach zu riskant ist. Diese Neugestaltung ist jedoch mit Kosten verbunden und diese Kosten lassen sich durch folgende Praktik in den Griff bekommen.

Refactoring ermöglicht Simple Design

Erinnern Sie sich daran, dass die Absicht des Simple Designs darin besteht, ein System zu schaffen, dessen Design gerade komplex genug ist, um die Komplexität der Anforderungen zu bewältigen, die das System erfüllen soll. Wenn wir diesen günstigen Zustand erreicht haben, nachdem jede Anforderung implementiert wurde, wird die Implementierung der nächsten kleinen Funktion keine massive Umgestaltung erfordern. Und diesen günstigen Zustand erreichen wir durch kontinuierliches Refactoring.

Simple Design – erstellt von vielen

Je komplexer das Problem ist, desto mehr brauchen wir jemanden, mit dem wir das Thema diskutieren können. Und da das Programmieren immer noch ein komplexes Problem ist, selbst wenn wir alle Regeln befolgen, ist das Pairing allein schon aus diesem Grund eine sehr nützliche Praxis. Von den vier Praktiken hebt sich das Pairing jedoch dadurch ab, dass es nicht so technisch ist wie die anderen. Es ist schwer zu sagen, wo die unmittelbare Verbindung zwischen dem Pairing und den anderen Praktiken liegt. Jede dieser weiteren Praktiken ist jedoch leichter zu befolgen, wenn man eng mit jemandem zusammenarbeitet, und für mich ist die Praxis des Simple Designs diejenige, auf die das Pairing den größten positiven Einfluss hat.

Fazit

Diese vier technischen Praktiken sind für agile Softwareentwicklungsprojekte unerlässlich. Werden sie nicht befolgt, wird die Codebasis unweigerlich so unübersichtlich, dass die Entwicklungsgeschwindigkeit sinkt, bis jede weitere Entwicklung massive Kosten und Risiken verursacht. Das liegt vor allem an dem hohen Stellenwert, den wir der Agilität beimessen, d. h. unserer Fähigkeit, schnell auf veränderte Anforderungen zu reagieren. Sie sind, kurz gesagt, die Software-Äquivalenz des Sprichworts "Was Du heute kannst besorgen, das verschiebe nicht auf morgen!"

Autor
Philipp Rembold

Philipp Rembold

Philipp Rembold studierte Mathematik in Freiburg, wohnt im Saarland und arbeitet bei SAP als Engineering Lead. Seine Leidenschaft ist die agile Software-Entwicklung, wobei er weniger Wert auf organisatorische Prozesse sondern auf…
>> Weiterlesen
Das könnte Sie auch interessieren
Kommentare (0)

Neuen Kommentar schreiben