Sonntag, 9. März 2008


Haltet euch zurück beim Programmieren

In seinem Blog schreibt Cedric Beust gegen den, wie er es nennt, "Testextremismus". Ich muß soweit zustimmen, daß Extremismus in dem Sinn, daß eine Methodik als die allein seligmachende hingestellt wird, zu nichts Gutem führt. Aber wenn jemand aus dem agilen Lager der Softwareentwicklung das so hinstellt, hat er sicher etwas gründlich mißverstanden. Es ist einer der agilen Grundsätze, nie etwas zu machen, was nicht dem Ziel dient, ein gutes Produkt herzustellen. Nie, nie soll etwas nur um der Methodik wegen getan werden.

Für diejenigen, die den Namen Beust nicht kennen, es handelt sich um einen der Entwickler von TestNG, einer Alternative zu JUnit, die auch dazu beigetragen hat, daß die Entwicklung auch auf der Seite von JUnit wieder Fahrt aufgenommen hat.

Was Cedric Beust behauptet, ist unter anderem Folgendes:

  • Es ist egal, wann Test geschrieben werden, vorher oder nachher.

  • Nur funktionale Tests zählen.

  • Schreibe mehr Code, als für die Tests notwendig ist, wenn dir danach ist.

Auf die ersten beiden Punkte will ich nur kurz eingehen. Der letzte stört mich besonders, da ich zur Zeit in einem Projekt tätig bin, wo diese Arbeitsweise ihre furchtbaren Auswirkungen besonders deutlich zeigt.

Warum Test vorher geschrieben werden sollten

Eine einfache, aber leider zutreffende, Antwort ist, weil nachher meist keine Zeit mehr ist. Wenn sie nicht ein vertraglich festgelegter Lieferbestandteil sind, werden Tests gewöhnlich eingespart, um vermeindlich Aufwand zu reduzieren. Der Code der geliefert werden soll, ist doch schon da. Wenn er beim Abnahmetest akzeptiert wird, ist doch alles gut.

Wenn Tests nicht zuerst geschrieben werden, ist man nicht gezwungen, von Anfang an Testbarkeit beim Design zu berücksichtigen. Ein testbares System ist in der Regel weniger gekoppelt und modulare, also auch in anderer Beziehung besser aufgebaut. Umgekehrt wird es sehr viel aufwendiger, Tests für ein System zu schrieben, daß stark gekoppelt ist.

Der wichtigste Grund aber ist, daß das Schreiben von Tests den Entwickler zwingt, sich vor der Kodierung über die präzisen Anforderungen im klaren zu werden.

Im übrigen sind Modultests natürlich nur ein Mittel zum Zweck. Aber viele Dinge kann man realistisch nur auf dieser Ebene Testen. Wenn ich z.B. ein Interface zu einer von Amazon bereitgestellten API schreiben soll, so sollte das entstehende System sicher auch robust gegen Störungen auf der Seite von Amazon sein. Während des funktionalen Tests kann ich aber nicht mal kurz bei Amazon anrufen, ob sie bitte mal vorübergehend ihr System abstürzen lassen könnten.

Warum nur der notwendige Code geschrieben werden sollte

Ein Grundsatz aus ExtremeProgramming ist YAGNI (YouArentGonnaNeedIt). Das bedeutet: mach nicht mehr, als du tun mußt, um die Anforderung zu erfüllen, denn wenn du meinst, daß du dir in Zukunft Arbeit sparen kannst durch ein paar Zeilen Code mehr heute, bedenke: Erstens kommt es anders und zweitens als du denkst.

In der Regel ist meist nicht genug Zeit vorhanden, so ist es vernünftiger, ein paar zusätzliche Tests zu schreiben und den Code stabiler zu machen.

Es kommt aber noch viel, viel schlimmer. Wenn Code auf Vorrat geschrieben wird, so enthält das System diesen Code, der nie läuft, nie getestet wird. Es ist völlig unklar, in welchem Zustand er sich befindet. Aber bei jeder Codeänderung will er mit berücksichtigt werden. Es entstehen also zusätzliche Pflegekosten.

Man macht eine Abschätzung für eine neue Anforderung und denkt nach Betrachtung des vorhandenen Codes, daß nur ein kleiner Aufruf zu der sowieso vorhandenen Routine X notwendig sei. Dummerweise treten bei der Implementierung dann eigenartige Fehler auf. Erster Gedanke: Ich habe einen Fehler gemacht. Finde aber keinen in meinem Code. Vielleicht habe ich die Routine falsch aufgerufen. Ich suche nach Code, der die Routine benutzt, finde aber kein Beispiel. Okay, war noch nicht verwendet worden. Also versuche ich die fremde Routine zu verstehen und behebe mühsam die dort vorhanden Probleme. Wenn diese Routine nie existiert hätte, hätte ich mir Stunden Sucherei sparen können und direkt eine (vielleicht einfachere) Routine selbst geschrieben.

Häufig läßt sich eine Anforderung viel einfacher Einbauen, wenn man die Anwendung zuerst ein wenig umbaut (Refactoring). Der Aufwand hierfür ist in etwa proportional zu der Menge betroffenen Codes. Auch hier stört jeder auf Verdacht eingebaute Code.

Wider Codezombies

Mit jeder Zeile Code, die man ohne die Funktionalität zu beeinträchtigen entfernt, wird die Anwendung besser (pflegbar). Toter Code muß, sobald er als solcher erkannt wird, entfernt werden. Bei Code, der auf Verdacht eingefügt wurde, aber derzeit keine Funktion hat, handelt es sich um einen Codezombie. Meine Erfahrung lehrt, daß man gegen solche unangenehmen Geschöpfe immer Weihwasser und Holzpflock bereit haben sollte. Also auskommentieren, compilieren und Tests laufen lassen. Wenn kein Fehler auftritt, sofort löschen.

Es gibt Leute, die in der Tat etwas extrem sind. Diese messen die Testabdeckung und entfernen alles was nicht von Tests erreicht wird automatisch. Netter Gedanke, aber leider wohl selten durchsetzbar.

1 Kommentar:

F Quednau hat gesagt…

Unit Tests sind ein feiner Indikator für den Design der gestesteten Klassen. Letztens erst, beim Mocken der Abhängigkeiten fiel mir auf, dass ich zu viele von diesen mocken musste, um ein erwartetes Verhalten zu testen. Ein schöner Indiz dafür, dass man an der Stelle etwas verändern sollte.
Manche Dinge in Richtung TDD lesen sich tatsächlich ein wenig dogmatisch. Ein etwas rebellischer Entwickler kann da durchaus in eine Abwehrhaltung verfallen. Vielleicht wollte Cedric dem etwas entgegen treten.