Technical Debt und das geistige Taskboard

Entwickler wollen ihren eigenen Ansprüchen genauso gerecht werden wie den Ansprüchen anderer. Daher tun sich viele Scrum-Teams am Anfang schwer, sich einzugestehen, dass sie sich für den Sprint zuviel vorgenommen haben (Over Commitment). Für Product Owner ist es anfänglich ebenso schwer. Schließlich hat man auch als Product Owner seine Ziele und möglicherweise sogar bestimmte Features zu einem festen Zeitpunkt versprochen.
Beides zusammen kann ausreichend Druck erzeugen, dass die Entwickler in den Zug einsteigen, der direkt in die Hölle fährt: Sie opfern Qualität, um den geplanten Sprintinhalt zum Sprintende zu schaffen. Sie gehen eine technische Schuld ein (Technical Debt), die ihnen später sehr schmerzhaft auf die eigenen Füße fällt – in Form einer steilen Aufwandskurve.
Dabei ist das erste Problem aus meiner Sicht gar nicht, dass man die technische Schuld eingegangen ist. Das erste Problem ist, dass das implizit passiert. Die Entwickler machen sich beim Programmieren geistige Tasks in der Art “Hier müsste man noch mal das Refactoring XYZ durchführen.”, “Hier müssten noch die Tests nachgeschrieben werden.” etc.
Diese Tasks gehören nicht in den “Hinterkopf”. Diese Aufräum-Tasks gehören explizit und für alle sichtbar ins Sprint-Backlog (und damit auf das Taskboard). Damit hat man nicht nur die Merker, was man noch erledigen muss. Es wird auch sofort klar, dass man das Sprint-Commitment trotzdem NICHT erreicht hat. Man hat nur die übrig gebliebenen Tasks getauscht. Es sind keine fachlichen Features mehr offen, sondern technische Aufräumarbeiten.
Dieser Fakt ist erstmal zu akzeptieren und in der Sprint-Retrospektive zu beleuchten. Warum konnten wir unser Commitment nicht halten und wie machen wir es nächstes Mal besser? Und dann sollte man sich auch gleich noch die Frage stellen: Warum haben wir Qualität geopfert anstatt Funktionalität zu reduzieren und ist das Opfern der Qualität wirklich der bessere Weg?

Können Anwender/Fachexperten modellieren?

Auf diese Frage hätte ich früher mit einem klaren “Nein” geantwortet. Inzwischen mehren sich aber deutlich die Anzeichen, dass sie mit entsprechender Unterstützung vielleicht doch viel mehr können, als zumindest ich früher dachte.

  • In Feature-Driven-Development sind die Fachexperten beim Color Modeling mind. sehr stark in die Erstellung fachlicher Klassenmodelle involviert.
  • Bei InfoQ ist ein Artikel erschienen, der beschreibt, wie die Fachexperten selbst mit Domain Driven Design fachliche Klassenmodelle erstellt haben, die die Entwickler dann auch genau so umgesetzt haben.

Der InfoQ-Artikel hat mir auch nochmal eine Situation in Erinnerung gerufen, die wir vor 2 oder 3 Jahren in einem Projekt hatten. Wir hatten zeitliche Ereignisse an Wasserzählern modelliert (Einbau, Wechselung, Eichung etc.). Es wurde irgendwann im Projekt klar, dass wir das Modell ändern müssen. Der Code war schwer verständlich und sträubte sich gegen Änderungen.

In der Diskussion über das Zielmodell haben sich zwei Fronten im Team gebildet, die sich auch nicht von selbst auflösten. (Dass die Fronten entstanden sind, halte ich heute für ein Sympton eines tieferliegenden gruppendynamischen Problems, aber das ist hier nicht das Thema.)
Wir haben ziemlich viel Zeit in die Diskussion gesteckt, ohne zu einem Ergebnis zu kommen. Irgendwann habe ich einen Fachexperten einfach mal die beiden Modelle skizziert und gefragt, ob er etwas beitragen kann. Konnte er. Er hat gesagt, dass beide Modelle nicht so gut passen und ein drittes skizziert. Dieses Modell leuchtete dem ganzen Team schnell ein und wir haben die Änderungen durchgeführt. Tatsächlich beseitigte das Modell des Fachexperten unsere Probleme im System.

Software-Architektur mit Dependency-Injection

Überblick

Dieser Artikel beschreibt meine Erfahrungen mit Dependency-Injection (DI) in mehrjährigen Großprojekten. Dabei geht es vor allem um die Best-Practices für Entwurf und Architektur.

Wir haben vor allem handprogrammierte DI, Pico-Container und Java-Server-Faces als DI-Container eingesetzt. Der Artikel bezieht sich primär auf Pico-Container. Die „Erkenntnisse“ lassen sich aber auf andere DI-Container übertragen.

In einem Großprojekt haben wir ein System mit vielen Singletons schrittweise auf Pico-Container umgestellt. Wir haben einige größere Refactorings in dem Projekt durchgeführt und bei vielen war der Nutzen letztlich zweifelhaft. Der Umbau auf Pico-Container gehört aber definitiv zu den Umbauten, die sich deutlich gelohnt haben.

Zielsetzungen

Mit DI kann man eine Reihe von Zielen erreichen:

  • Entwurf entkoppeln
  • Abhängigkeiten explizieren
  • Testbarkeit verbessern
  • Singletons und static-Attribute eliminieren

Entwurf entkoppeln

DI führt nicht automatisch zu einer Entkopplung der Systemkomponenten. Man kann sich auch mit DI eigenartige und unwartbare Abhängigkeitsgeflechte zusammenbauen. Ohne DI hingegen ist es in der Praxis fast unmöglich, entkoppelte Systeme zu bauen. In diesem Sinne ist DI eine notwendige, aber keine hinreichende Bedingung für Entkopplung. Wenn man zusätzlich zu DI noch Interfaces und testgetriebene Entwicklung einsetzt, bekommt man aber ohne größere Anstrengungen ein gut entkoppeltes System.

Abhängigkeiten explizieren

DI macht die Abhängigkeiten von Objekten an ihrer Schnittstelle deutlich. Dadurch ist erstmal natürlich nur ein wenig Dokumentation gewonnen. In unseren Projekten hat dieses kleine bisschen Mehr an Dokumentation aber erheblichen Einfluss darauf gehabt, wie gut man Entwürfe verstehen konnte.

Testbarkeit verbessern

Durch die Entkopplung ließen sich die einzelnen Komponenten besser testen. Hier fällt insbesondere die Harmonie von DI mit Mock-Testen[1] positiv auf. Zusammen mit testgetriebener Entwicklung entsteht ein schlagkräftiges Trio.

Nicht zuletzt laufen so entkoppelte Tests viel schneller ab als klassisch erstellte Unit-Tests (in einigen Fällen konnten wir Testlaufzeiten von mehreren Minuten auf unter 10 Sekunden reduzieren).

Singletons und static-Attribute eliminieren

Setzt man DI konsequent ein, braucht man weder Singletons noch irgendeine andere Form statischer Attribute. Dadurch werden Zustandsabhängigkeiten im System reduziert, so dass sich Systemteile besser unabhängig voneinander wieder verwenden und testen lassen.

In unseren Projekten sind durch die Umstellung von Singletons auf DI eine ganze Reihe eigenartiger Phänomene beim Ausführen und beim Testen verschwunden.

Pico-Container im Code

Pico-Container ist ein sehr leichtgewichtiger DI-Container, der programmatisch konfiguriert wird. Daher können programmatisch DI-Container erzeugt und an andere Objekte übergeben werden. Da ist eine Fußangel versteckt: Man sollte Pico-Container minimal-invasiv benutzen. Das bedeutet, dass möglichst wenig Code Kenntnis davon haben soll, dass Pico-Container (oder ein anderer DI-Container) eingesetzt wird. Folglich sollten die Pico-Container nur im Startup erzeugt werden. Weitere Klassen dürfen nicht vom Pico-Container abhängen. In der Schichtung des Systems liegt der Pico-Container also ganz oben.

Pico-Container in Tests

In Unittests hat Pico-Container nichts verloren. Sind die getesteten Klassen so kompliziert miteinander verflochten, dass man sie manuell nicht zusammenstecken kann, ist Refactoring angesagt – und zwar schleunigst.

Welche Klassen registriert man beim DI-Container?

In unseren Projekten haben sich drei Typen von Klassen etabliert, die wir typischerweise beim DI-Container registrieren.

  • Fabriken
  • Services
  • Objekte, die nur einmal existieren dürfen (ehemalige Singletons)

Wofür baut man Fabriken?

Wenn ein Objekt andere Objekte erzeugen will, kann man diese Objekte nicht über DI hineinreichen – man weiß ja noch nicht, wie viele Objekte erzeugt werden. In solchen Fällen reicht man stattdessen Fabriken per DI in das Objekt.

Fabriken werden häufig mit static-Methoden implementiert. Wenn man die Fabriken beim DI-Container registriert, wäre das jedoch einigermaßen witzlos. Also gilt: Fabriken sollten keine static-Methoden enthalten – static-Attribute sowieso nicht.

In vielen Projekten habe ich beobachtet, dass viel zu selten Fabriken benutzt werden! Fabriken (ohne static-Methoden) helfen bei DI und erleichtern das Testen mit Mocks ungemein[2]. Im Zweifel würde ich lieber eine Fabrik zuviel spendieren.

Abschluss

Unsere Erfahrungen mit Pico-Container haben wir in erster Linie in Rich-Clients gesammelt. Wir haben Pico-Container serverseitig nicht verwendet. Es sollte jedoch möglich sein, weil der Pico-Container sehr leichtgewichtig ist. Der Overhead für die Erzeugung bei jedem Request oder EJB-Aufruf sollte in den meisten Anwendungen zu verkraften sein. Wenn der Overhead zu groß wird, kann man den Pico-Container mind. bei Webanwendungen auch in der Session speichern.

Inzwischen kommen viele Server-Infrastrukturen aber bereits mit eigenen DI-Containern (Spring, JSF, EJB 3), so dass man serverseitig i.d.R. wahrscheinlich nicht in die Verlegenheit kommt, Pico-Container einzusetzen.

Referenzen


[1] z.B. mit Easy Mock

[2] Beim Testen gilt: Jedes Testproblem kann durch eine weitere Indirektion gelöst werden.

Screencast zum Vortrag "Einfachheit in Softwareprojekten"

Auf den XP-Days habe ich einen Vortrag mit dem Titel “Einfachheit in Softwareprojekten” gehalten. Den habe ich als Screencast aufgezeichnet und dabei leider das Mikro so übersteuert, dass der Ton unbrauchbar ist.
Glücklicherweise hat Jens Himmelreich den Ton separat aufgezeichnet und mir zur Verfügung gestellt. Nach ziemlich viel Arbeit habe ich es dann auch geschafft, Bild und Ton so zu mischen, dass sie synchron laufen.
Das Gesamtkunstwerk kann man sich hier ansehen (oder einfach nur den Ton oder die Folien als PDF runterladen).

Nochmal vielen vielen Dank an Jens Himmelreich. Ohne seine Ton-Aufnahme wäre das alles nichts geworden.

Lohnt sich Dependency-Injection

Auf InfoQ ist gerade eine Zusammenfassung einer Diskussion erschienen, in der es um die Frage geht, ob sich Dependency-Injection auszahlt. Die einen sagen, dass DI nur dazu gut ist, damit man entkoppelt mit Mocks testen kann. Stattdessen könne man aber auch bessere Mock-Frameworks benutzen, die DI nicht erzwingen. Die anderen sagen, dass Testen DI erzwingt und dass daher DI automatisch gut ist.

Ich finde, die Diskussion geht etwas am eigentlichen Kern vorbei. DI ist erstmal nur eine Technik, genauso wie Mock-Testen. Keines von beiden ist generell gut oder schlecht. Genauso wie Entkopplung nicht automatisch gut ist. Man muss auch die richtigen Teile entkoppeln.

Meiner Meinung nach ist das Dependency-Inversion-Principle (DIP) das übergeordnete Konzept. DIP sagt, dass ein High-Level-Konzept nicht von Low-Level-Implementationen abhängen sollen. Demnach darf z.B. die Fachlogikschicht nicht von der Datenbankschicht abhängen. Begründung: Low-Level-Implementationen ändern sich häufiger als High-Level-Konzepte und die Änderungen finden nicht nur hinter dem API der Low-Level-Implementation statt, sondern schlagen häufig bis in die Klienten durch. Hängen die High-Level-Konzepte von den Low-Level-Implementationen ab, müssen sie unnötig häufig geändert werden.

Das bedeutet: DIP ist generell gut.

DI und Mocks erlauben mir, DIP umzusetzen. Daher sind DIP und Mocks mind. dann gut, wenn sie für DIP eingesetzt werden.

Interessanterweise korreliert DIP mit vielen Aspekten aus Quasar.

Warum einfach, wenn es auch kompliziert geht?

Ich habe auf den XP-Days in Karlsruhe einen Vortrag über Einfachheit in Softwareprojekten gehalten, den ich hoffentlich bald als Screencast online stellen kann.
In dem Vortrag ich von einem Experiment berichtet, dass von Bavelas an der Stanford-Universität durchgeführt wurde. Man hat Propanden mit Bildern von Gewebezellen konfrontiert. Sie sollten ohne medizinische Vorkenntnisse diagnostizieren, ob die Zellen gesund oder krank sind. Sie bekamen als Feedback dann jeweils, ob sie richtig oder falsch gelegen haben. Aus dem Feedback entwickelten sie ein Modell darüber, woran man krankhafte Gewebezellen erkennt.
Allerdings hat man nur der einen Gruppe korrektes Feedback gegeben. Die andere Gruppe hat zufälliges Feedback erhalten.
Schließlich hat man Paare mit jeweils einem Vertreter jeder Gruppe gebildet, die dann gemeinsam Gewebezellen diagnostizieren sollten. Und dabei setzte sich meistens die Person durch, der man das zufällige Feedback gegeben hatte.
Begründung: Diese Person hat sich auf Basis des zufälligen Feedbacks ein sehr kompliziertes (aber falsches) Modell darüber gemacht, woran man krankhafte Zellen erkennt. Die Person mit dem korrekten und einfachen Modell war begeistert von der Detailtiefe des komplizierten Modells (“da habe ich selbst wohl etwas übersehen”) und ist daher der meist falschen Einschätzung seines Partners gefolgt.
Und dieses Phänomen findet sich auch ständig in der Softwareprojekten. Es ist so verführerisch den komplizierten Architekturen, Technologien und Vorgehensmodellen zu folgen. Ich glaube jedoch, dass das meistens der falsche Weg ist: Komplizierte Lösungen sind auch dann falsch, wenn sie richtig sind.

Eine genauere Beschreibung des Experimentes findet sich in “Wie wirklich ist die Wirklichkeit?” von Paul Watzlawick im Kapitel (“Warum einfach, wenn es auch kompliziert geht?”). Der Text findet sich unter der gleichen Überschrift auch Online auf Seite 20ff.

W-JAX: REST-Vortrag

Gestern habe ich auf der W-JAX einen sehr interessanten Vortrag zu REST gehört von Stefan Tilkov. Der Vortragende hat lange Zeit mit Web-Services gearbeitet und propagiert heute REST.
Inzwischen gibt es den JSR-311 namens JAX-RS für ein standardisiertes API für REST, an dem Stefan Tilkov auch mitarbeitet. Der ist noch im Early-Draft-Stadium. Es sieht aber schon ganz nett aus.
Beim Vortrag habe ich denn auch ein Tool kennengelernt, dass wir bei unserem REST-Projekt auch benötigt hätten: curl. Mit dem Ding kann man HTTP über die Kommandozeile machen. Im Gegensatz um Browser kann man manuell den Header, die Methode (GET, POST, PUT, DELETE etc.) festlegt und auch den Body.

Klassen und Methoden im Wandel

Als ich studierte gab es genau eine Art, Klassen und Methoden zu benennen: Klassen wurden mit Substantiven benannt, die direkt dem ungesetzten Konzept bzw. ihrer Verantwortlichleit entsprechen sollten, z.B. Auftrag. Bei den Methoden wurde streng zwischen Funktionen und Prozeduren unterschieden. Funktionen liefern Werte und lassen den Objektzustand schön in Ruhe, während Prozeduren den Objektzustand ändern und keinen Rückgabewert haben. Ob etwas Funktion oder Prozedur ist, sollte nicht nur an der Signatur deutlich werden, sondern auch im Namen. Prozeduren werden immer in Befehlsform geschrieben (z.B. berechneSumme) während Funktionen eine Substantivform bekommen (ggf. mit einem ‘get’ als Präfix, z.B. ‘getNummer’). Diese ganze Konzeption wurde z.B. von Bertrand Meyer vertreten und auch gut begründet:
Wenn man eine Klasse sucht, kann man sie leicht anhand ihres Namens finden. Und wenn man dann das API der Klasse liest, ist gleich klar, was die Methoden tun. Insbesondere ist klar, ob eine Methode nur einen Wert liefert oder ob sie gefährlich den Objektzustand manipuliert.

In den letzten Jahren hat sich an verschiedenen Stellen ein deutlich anderer Programmierstil entwickelt. Der primäre Fokus hat sich gewandelt. Es geht nicht mehr primär darum, dass man das API einer Klasse leicht lesen und verstehen kann. Stattdessen soll der Klientencode möglichst gut lesbar sein.

Bei der Benennung von Unit-Tests findet sich eine zarte Andeutung in diese Richtung. Statt AuftragTest.testSummenberechnung findet man heute immer häufiger AuftragTest.berechnetSummeDerPositionen. Die zweite Variante lässt sich als Spezifikation lesen “Auftrag berechnet Summe der Positionen”.

Noch deutlicher wird es, wenn man sich Behaviour Driven Development (BDD) ansieht.

Schließlich nutzen die Fluent Interfaces dieses Konzept auch außerhalb des Testens bzw. Spezifizierens für Produktivcode.

Aus


TimeInterval meetingTime = new TimeInterval(fiveOClock, sixOClock);

wird


TimeInterval meetingTime = fiveOClock.until(sixOClock);

Für viele Entwickler, die schon länger im Java- oder C++-Geschäft sind, sind diese fließenden Interfaces sehr gewöhnungsbedürftig. Allerdings wird eine Klasse viel häufiger eingesetzt als gelesen. Also scheint es nicht gerade abwegig, mehr Gewicht auf die Benutzbarkeit als die API-lesbarkeit zu legen.

Trotzdem wird man absehbar wohl nicht alle Klassen mit einem Fluent Interfaces versehen. Dazu ist der Aufwand zu groß. Aber bei den Klassen, die sehr häufig benutzt werden, sind Fluent Interfaces sicher eine Überlegung Wert.

Nachtrag: Ein Beispiel für ein Fluent Interface findet sich z.B. in Hibernate, wenn man eine Criteria zusammenbaut.