Bitte Beachten: Dieser Blog wird hier nicht weiter geführt! Kommentare und neue Blogposts gibt es unter http://feitel.indeedgeek.de. Dort bitte auch neue Kommentare zu diesen Beiträge erstellen.

Montag, 7. April 2008

Ich kann nicht Programmieren...

... zumindest kam es mir letzte Woche so vor. Für eine Vorlesung musste ich einen (bis jetzt) vereinfachten Single Sign On - Server schreiben. Da wie gesagt dies bisher nur ein Prototyp ist, war die Implementierung relativ simpel und die Anwendung lief recht schnell. Die nächste Aufgabe bestand daraus den Prototyp mit vorgegebenen Unittests zu testen.

GRAUSAM! Ich dachte naja, zwei drei Tests werden wohl rot sein, aber das gut 60% fehlschlägt hat mich dann doch sehr betroffen gemacht. Die meisten Fehler waren einfach eine fehlende Überprüfung auf Null oder eine vergessene Negation in einer If Abfrage. Ich habe mich darauf hin gleich gefragt warum die Tests so schlecht ausgefallen sind bzw. was man besser machen könnte um solche Fehler zu vermeiden.

Natürlich Methoden wie Unittests, Design by Contract, TDD, etc. sind in aller Munde aber mal ehrlich, nutzt ihr die bei jedem Projekt so ausführlich wie man sollte? Ich bestreite nicht den Nutzen dieser Techniken, auch bin ich mir durchaus benutzt das diese bzw. vergleichbare Techniken in ernsthaften Projekten unabdingbar sind aber ich finde es sind immer noch zu viele Barrieren vorhanden so das die Techniken wirklich Sinn geben. Ich weiß nicht genau wie ich sagen soll, deshalb gibt es mal paar Beispiele:

Ein Unittest ist schnell geschrieben, aber man muss ja prinzipiell jede Mögliche Kombination von Input und Output Werten bzw. auch an Zuständen im System Abdecken. Ein enges System aus Unittests halte ich für sehr sinnvoll, aber wenn sich beispielsweise ein Nutzer mit Name und Passwort an einem Server anmelden will. Was muss ich alles Testen? Was ist wenn Nutzername und/oder Passwort Null sind? Was ist wenn Nutzer und/oder Passwort falsch sind? Was ist wenn der Server nicht erreichbar ist? Was ist wenn der Nutzer bereits angemeldet ist? Was ist wenn der Nutzer überhaupt nicht im System vorhanden ist? Was ist wenn Nutzername und/oder Passwort ein Leerer String sind? Und so weiter. Mir fällt bestimmt noch mehr ein. Und das nur für einen Vorgang! Man stelle sich das mal in einem Komplexen System vor! Außerdem ergibt sich früher oder später die otwendigkeit von Mock Objekten und GUI Tests. Diese Spirale dreht sich so lange hoch bis ich irgendwann mehr Testcode als produktiven Code habe. Und genau hier tritt für mich ein Widerspruch zwischen Theorie und Praxis auf. Ich weiß das es gut ist, ich weiß das man es braucht, aber ich habe keine Ahnung wie man sinnvoll (sowohl logistisch als auch zeitlich) alle Fälle abdecken soll.

Andere Möglichkeit sind Pre- Postconditons. Hier tritt, außer dem Umfangproblem der Unittests, auch noch das Problem der Mangelnden Verbreitung auf. Die Sprachen, die ich mehr oder weniger kenne (Java, Ruby, C(++), Python, Bash, Prolog(:D), ...) bringen Conditions von Haus aus nicht mit. Deshalb bleiben mir zwei Möglichkeiten: Entweder ich lade mir eine zusätzliche Erweiterung der Programmiersprache dazu (was leider wieder andere Probleme wie Kompatibilität und Overhead mit sich bringt) oder ich arbeite mit Asserts. Bringt meine Sprache der Wahl schon Asserts mit habe ich es etwas einfacher. Ansonsten muss man sich erst eine Assert Funktion schreiben (wofür es gerade in Ruby einige sehr schöne Ansätze gibt!). Das Problem was ich hier wieder sehe ist einfach das es keine gezielte Notation der Conditions gibt. Ich habe einfach am Anfang (oder auch am Ende) meiner Funktion 10,20 oder auch mehr Zeilen mit Assert Statements die alle Fälle die mir einfallen abdecken sollten. Blöd nur wenn die Methode selbst nur 5 Zeilen hat. Besser wäre ein Ansatz mit dem man durch die Erweiterung der Sprache eine gesonderte Syntax innerhalb des Methodenkopfes einführt. Und selbst da macht einem die Vielzahl der Möglichen Input- und Output-Werte Kopfzerbrechen.

Ich habe jetzt mal die beiden Methoden herausgegriffen und meine Bedenken dazu geäußert. Ich komme aber irgendwie aus diesem Widerspruch nicht heraus. Um robusten Code zu schreiben muss ich alle Fälle abdecken, wenn ich alle Fälle abdecke, komme ich nicht dazu Code zu schreiben. Klar der Mittelweg ist der beste, aber das dachte ich bei meinem Projekt auch und dann waren doch 60% rot. Es muss doch eine Möglichkeit geben schnell und kurz während ich die Methode schreibe alle Fälle auszuschließen die nicht auftreten dürfen. So eine Technik kann nur funktionieren wenn sei nebenbei, schon fast automatisch, abläuft (sei es nun wirklich automatisiert oder einfach nur durch den Autor). Aber irgendwie kämpfen die Methoden die mir spontan einfallen alle mit massig Overhead.

Wie handhabt ihr das? Schon mal TDD versucht? Geht es euch Ähnlich? Ich bin für Vorschläge dankbar!

2 Kommentare:

kb hat gesagt…

Ich halte nicht viel von testen, ich bin eher so der produktive Typ :-D

Aaron hat gesagt…

Es ist manchmal echt schwer, die Balance zwischen Tests und dem richtigen Code zu finden.
Wo ich Tests für ziemlich unnötig halte ist die, die "internes" Verhalten abtesten. Dieses sieht man meiner Meinung nach direkt am Code und mit etwas Erfahrung umgeht man hier schon die meisten Bugs. Was allerdings wichtig ist, sind User-Eingaben. Da kann alles passieren. Gerade bei Webseiten ist das ein großes Thema und viele vergessen hier das ausgiebige Testen. Da UnitTests schreiben ziemlich langweilig sind und keiner gerne viel Teit in Tests steckt, verwende ich z.B. bei Webseiten Selenium und mache Tests an der laufenden Anwendung (Systemtests quasi).

Beim Thema Pre/Post-Conditions bin ich gespaltener Meinung. Zum einen hat man hier die Absicherungen direkt an der Stelle, wo die Action passiert und gehen so "leichter von der Hand". Anderseits mach ich dieses Vermischen von Testcode und eigentlichem Code nicht wirklich. Mir sind hier klar getrennte Ansätze wie UnitTests oder Systemtests lieber.