Agile Modellierung mit
UML
Loading

2.3 Ausgesuchte Entwicklungspraktiken

Pair Programming, der dem XP zugeordnete Test-First-Ansatz und die Weiterentwicklung von Code werden als drei der technik-orientierten Elemente von XP weiter untersucht, da sie auch bei agiler Modellierung mit UML eine bedeutende Rolle spielen.

2.3.1 Pair Programming

Pair Programming war bereits vor XP bekannt und wurde zum Beispiel in [Con95] beschrieben. War es zunächst eine eigenständige Technik, so ist es heute unter anderem in XP integriert, weil es eine gute Basis für gemeinsamen Codebesitz darstellt und es deshalb grundsätzlich für alle Teile des System mindestens zwei Personen gibt, die damit vertraut sind.

Die wesentliche Idee des Pair Programming ist auch als „Vier-Augen-Prinzip“ bekannt: Zwei Entwickler sitzen gemeinsam an einer Aufgabe, die sie kooperativ lösen. Sie benötigen dafür nur einen Rechner und während einer das gemeinsam Erarbeitete eintippt, findet durch den Partner gleichzeitig ein konstruktiver Review statt. Die Tastatur und damit die Kontrolle über das konstruktiv Eingearbeitete wird allerdings sehr schnell zwischen den Beteiligten gewechselt. Dieses Prinzip war zunächst zur Anwendung unter Gleichen gedacht, eignet sich aber auch zur Kombination eines Kenners des Systems mit einem Projektneuling. Auf diese Weise kann die Einarbeitung des Neulings in vorhandene Softwarestrukturen sowie neue Techniken effizient erfolgen. Rein rechnerisch bedeutet Pair Programming aber zunächst doppelten Personalaufwand.

Pair Programming wurde in einigen an Universitäten angesiedelten Tests bereits eingehender untersucht beziehungsweise Untersuchungsschemata erstellt [SSSH01]. Die Untersuchungen [WKCJ00CW01] zeigen, dass nach einer relativ kurzen Einarbeitungszeit in den neuen Programmierstil zwar der Gesamtaufwand gegenüber der Einzelprogrammierung erhöht war, sich aber eine deutliche Reduktion der Projektdauer und insbesondere eine signifikante Erhöhung der Softwarequalität ergeben hat.2 Allerdings zeigt sich auch, dass die Technik des paarweisen Programmierens erst erlernt werden muss und dass paarweises Programmieren nicht jedem liegt.

In der Praxis dürfte deshalb ein rigoros erzwungenes Pair Programming nicht zielführend sein, sondern vielmehr eine kooperative Förderung desselben zu optimalen Ergebnissen führen. Dies ist vor dem Hintergrund zu sehen, dass es im Projekt durchaus neben dem Programmieren Tätigkeiten gibt, die eine paarweise Ausübung nicht erfordern. Dazu gehören zum Beispiel Planungstätigkeiten, Werkzeuginstallation und -wartung sowie unter Umständen Diskussionen mit Kunden zur Erhebung von Anforderungen. Leider stehen auch flexible Arbeitszeiten der Entwickler und ungünstige Räumlichkeiten einer konsequenten Anwendung des Pair Programming im Weg.

2.3.2 Test-First-Ansatz

In verschiedenen Methodiken werden Tests an unterschiedlicher Stelle und unterschiedlich intensiv diskutiert und eingesetzt. Während zum Beispiel das V-Modell XT eine explizite Trennung zwischen den Tests von Methoden, Klassen, Subsystemen und des Gesamtsystems trifft, ist im Rational Unified Process nach [Kru03] weder eine Unterscheidung der Testebenen, noch eine Diskussion von Metriken zur Testüberdeckung zu finden. In XP spielt Testen als eine der vier Kernaktivitäten eine wesentliche Rolle und wird deshalb genauer diskutiert.

In [Bec01] ist auf anschauliche Weise beschrieben, welche Vorteile der in XP propagierte Test-First-Ansatz bei der Softwareentwicklung haben kann. In [LF02] wird dieser Ansatz zur Beschreibung von Unit-Tests elaboriert und Vor- und Nachteile sowie der methodische Einsatz in pragmatischer Form diskutiert. [PP02] vergleicht den Test-First-Ansatz mit traditioneller Entstehung des Tests nach der Implementierung in einem allgemeinen Kontext. [Wak02, S. 8] beinhaltet eine Beschreibung für einen Mikro-Entwicklungszyklus auf Basis von Tests und Codierung.

Die wesentliche Idee des Test-First-Ansatzes besteht darin, sich vor der Entwicklung der eigentlichen Funktionalität (insbesondere einzelner Methoden) Testfälle zu überlegen, die zur Überprüfung der Korrektheit der zu realisierenden Funktionalität geeignet sind, um damit diese Funktionalität exemplarisch zu beschreiben. Diese Testfälle werden in automatisiert ablaufenden Tests festgehalten. Laut [Bec01LF02] hat dies eine Reihe von Vorteilen:
  • Die Definition der Testfälle vor der eigentlichen Implementierung erlaubt es, eine explizite Abgrenzung der zu realisierenden Funktionalität vorzunehmen, so dass der Testentwurf de facto einer Spezifikation gleichkommt.
  • Der Test begründet die Notwendigkeit des Codes und beschreibt unter anderem, welche Parameter für die zu realisierende Funktion notwendig sind. Der Code wird dadurch so entworfen, dass er testbar ist.
  • Nach der Realisierung der Funktionalität können die bereits vorhandenen Testfälle zur sofortigen Überprüfung verwendet werden, wodurch das Zutrauen in den entwickelten Code enorm steigt. Obwohl natürlich eine Fehlerfreiheit nicht gesichert ist, zeigt doch die praktische Anwendung, auch im Auktionsprojekt, dass dieses Zutrauen gerechtfertigt ist.
  • Der logische Entwurf wird von der Implementierung besser getrennt. Während der Definition der Testfälle, also noch vor der Implementierung, wird zunächst die Signatur der neuen Funktionalität bestimmt. Dies beinhaltet Namen, Parameter und die Klassen, in der Methoden angesiedelt sind. Erst im nächsten Schritt, nach der Definition der Tests, wird die Implementierung der Methoden vorgenommen.
  • Der Aufwand zur Formulierung von Testfällen ist im Allgemeinen nicht zu vernachlässigen, insbesondere wenn komplexe Testdaten zu erstellen sind. Das führt laut [Bec01] dazu, dass Funktionen so definiert werden, dass ihnen nur die jeweils benötigten Daten zur Verfügung gestellt werden. Dies resultiert in einer Entkopplung der Klassen und damit in besseren und einfacheren Entwürfen.
    Dieses Argument mag in Einzelfällen zutreffen, ist möglicherweise aber nicht immer richtig. Vielmehr erfolgt Entkopplung von Klassen eher durch frühzeitiges Erkennen der Möglichkeit zur Entkopplung oder durch nachträgliches Refactoring. Dies demonstrieren auch typische Beispiele des Test-First-Ansatzes [LF02Bec01].
  • Die frühzeitige Definition von Testdatensätzen und Signaturen ist auch beim Pair Programming hilfreich. Entwickler können dann expliziter über die gewünschte Funktionalität diskutieren.

Ein Vorteil von vorhandenen Testfällen ist, dass andere Entwickler anhand der Testfallbeschreibungen die gewünschte Funktionalität erkennen können. Dies ist oft notwendig, wenn der Code unübersichtlich und nicht ausreichend kommentiert ist und eine explizite Spezifikation der Funktionalität nicht besteht. Die Testfälle stellen daher selbst ein Modell für das System dar.

Die Erfahrung zeigt aber, dass die Möglichkeit, sich anhand der Tests die Funktionalität zu erarbeiten, beschränkt ist. Dies liegt daran, dass Tests normalerweise weniger sorgfältig definiert werden als der eigentliche Code und die in einer Programmiersprache geschriebenen Tests die eigentlichen Testdaten nicht sehr kompakt und übersichtlich darstellen.

Obwohl der Test-First-Ansatz eine Reihe von Vorteilen bietet, ist dennoch in der Praxis davon auszugehen, dass die Vorab-Definition von Tests keine ausreichende Überdeckung der Implementierung beinhaltet. Die aus der Testtheorie bekannten Überdeckungsmetriken werden aber in XP nicht eingesetzt. Man gibt sich weitgehend mit einem sehr informellen Begriff zur Testüberdeckung zufrieden, der vor allem auf der Intuition der Entwickler beruht. Diese Situation ist einerseits für das Controlling eines Projekts wenig zufriedenstellend, zeigt aber andererseits dennoch praktische Erfolge. Zur Zeit werden trotzdem Werkzeuge entwickelt beziehungsweise für den XP-Ansatz adaptiert [Moo01], die durch automatische Verfahren versuchen, herauszufinden, inwieweit die Tests lokale Änderungen des Codes entdecken und damit bestimmte Überdeckungskriterien erfüllen. Dazu gehören zum Beispiel Mutationstests [Voa95KCM00Moo01], die durch einfache Mutation des Testlings prüfen, ob diese Veränderungen durch einen Test entdeckt werden.

Ist die Implementierung der gewünschten Funktionalität gegeben, so sollte nach Erfüllung der initialen Testsammlung durch die Entwicklung weiterer Tests eine bessere Überdeckung erreicht werden. Dazu gehören zum Beispiel die Behandlung von Grenzwertfällen und die Untersuchung von Fallunterscheidungen und Schleifen, die teilweise durch die Technik oder ein genutztes Framework bedingt sein können und deshalb in den vorab entwickelten Testfällen nicht antizipiert wurden.

Generell wird der Test-First-Ansatz als eine Tätigkeit angesehen, die Analyse, Entwurf und Implementierung in „Mikro-Zyklen“ vereint. In [Bec01] wird jedoch auch erwähnt, dass Testverfahren, die zur systematischen Definition von Testfällen dienen und nach verschiedenen Kriterien die Überdeckung des Codes messen, im Wesentlichen ignoriert werden. Das wird absichtlich in Kauf genommen mit dem Argument, dass dadurch sehr viel mehr Aufwand entsteht, aber die Ergebnisse nicht wesentlich (wenn überhaupt) verbessert werden. Immerhin wird in [LF02, S. 59] darauf hingewiesen, dass Tests nicht nur vor der Implementierung, sondern auch nach Fertigstellung einer Aufgabe erstellt werden, um dann eine „ausreichende“ Überdeckung zu erzielen, ohne allerdings präzise zu klären, wann eine Überdeckung ausreichend ist.

In Abhängigkeit von der Art und Größe des durchgeführten Projekts kann der strikte Test-First-Ansatz ein interessantes Element des Softwareentwicklungsprozesses sein, der typischerweise nach einer zumindest initialen Architekturmodellierung und einer Partitionierung in Subsysteme eingesetzt werden kann. Unter Verwendung der ausführbaren Teilsprache der UML/P kann dieser Ansatz mehr oder weniger unverändert auf die Modellierungsebene gehoben werden. Dazu können zunächst exemplarische Daten mittels Objektdiagrammen und exemplarische Abläufe mit Sequenzdiagrammen als Testfälle modelliert werden, auf deren Basis die zu realisierende Funktionalität mit Statecharts und Klassendiagrammen modelliert werden kann.

2.3.3 Refactoring

Der Wunsch nach Techniken zur inkrementellen Verbesserung und Modifikation von Programmcode durch Regelsysteme ist nicht wesentlich jünger als die Entstehung der ersten Programmiersprachen [BBB+85Dij76]. Ziel einer transformationellen Softwareentwicklung ist die Zerlegung des Softwareentwicklungsprozesses in kleine, systematisch durchführbare Schritte, die aufgrund lokal begrenzter Auswirkungen beherrschbar werden. Refactoring wurde bereits in [Opd92] für Klassendiagramme erstmals diskutiert. Das empfehlenswerte Buch [Fow99] beschreibt eine ausführliche Sammlung von Transformationstechniken für die Programmiersprache Java. Die Refactoring-Techniken erlauben die Migration von Code entlang einer Klassenhierarchie, die Zusammenlegung oder Teilung von Klassen, die Verschiebung von Attributen, das Expandieren oder Auslagern von Code-Teilen in eigene Methoden und ähnliches mehr. Die Stärke der Refactoring-Techniken basiert auf der Überschaubarkeit der einzelnen als „Mechanik“ bezeichneten Transformationsschritte und auf der flexiblen Kombinierbarkeit, die zu großen, zielorientierten Verbesserungen der Softwarestruktur führt.

Das Ziel des Refactorings sind semantikerhaltende Transformationen eines bereits existenten Programms. Refactoring dient nicht zur Erweiterung der Funktionalität, sondern zur Verbesserung der Qualität des Entwurfs unter Beibehaltung der Funktionalität. Es ergänzt damit die normale Programmiertätigkeit.

Refactoring und die Weiterentwicklung von Funktionalität sind sich ergänzende Tätigkeiten, die sich im Entwicklungsprozess in schneller Folge abwechseln können. Der Verlauf eines Projekts kann damit wie in Abbildung 2.5 skizziert werden. Allerdings existiert für die „Qualität des Designs“ kein objektives Messkriterium. Erste Ansätze dafür messen zum Beispiel die Konformität zu Codierungsstandards, sind aber für eine Evaluation der Qualität von Architektur und Implementierung in Bezug auf Wartbarkeit und Testbarkeit unzureichend.


Abbildung 2.5: Refactoring und Weiterentwicklung ergänzen sich

Zur Sicherung der Korrektheit semantikerhaltender Transformationen werden keine Verifikationstechniken eingesetzt, sondern die vorhandene Testsammlung genutzt. Unter der Annahme der Existenz einer qualitativ hochwertigen Testsammlung ist die Wahrscheinlichkeit hoch, dass fehlerhafte, also das Verhalten des Systems verändernde Modifikationen erkannt werden. Durch Refactoring werden oft interne Signaturen eines Teilsystems modifiziert, wenn zum Beispiel eine Methode einen zusätzlichen Parameter erhält. Deshalb müssen bestimmte Tests gemeinsam mit dem Code angepasst werden. In [Pip02] wird sogar vorgeschlagen, im Sinne des Test-First-Ansatzes zunächst die Tests und dann erst den Code einer Refaktorisierung zu unterziehen.

Refactoring-Techniken zielen auf verschiedene Ebenen des Systems. Manche Refactoring-Regeln wirken im Kleinen, andere sind für Modifikation einer Systemarchitektur geeignet. Durch die Möglichkeit, eine bereits im Code realisierte Systemarchitektur zu verbessern, verliert die Notwendigkeit a priori eine korrekte und stabile Systemarchitektur zu definieren an Brisanz. Natürlich sind Modifikationen an der Systemarchitektur kostenintensiv. In XP wird jedoch angenommen, dass die Wartung ungenutzter Systemfunktionalität auf Dauer kostenintensiver ist. Entsprechend dem Prinzip der Einfachheit wird es im XP-Ansatz bevorzugt, die Systemarchitektur einfach zu halten und nur bei Bedarf durch geeignete Refactoring-Schritte zu modifizieren oder zu erweitern.

Während die Definition von Refactoring-Techniken für Java auf Basis von [Fow99] bereits stark im Ausbau ist, existieren nur wenig ähnliche Techniken für UML-Diagramme [SPTJ01].


Bernhard Rumpe. Agile Modellierung mit UML. Springer 2012