Foundations for Professionals .NET Professionals im Profil guide to C# guidgen.de

Blog

Nutze den Augenblick
und teile der Welt mit, was Du zu sagen hast.

Featurebezogene Entwicklung

Mittwoch, 18. August 2010, 16:55 Uhr
Permalink | Kommentare (2) | Kommentare als RSSRSS

Auf den Tag ein Jahr ist es heute her, dass ich erstmals über Extreme Programming geschrieben habe. Seither habe ich mich ausgiebig auch mit anderen agilen Methoden beschäftigt – allen voran mit Scrum, aber auch mit weniger bekannten beziehungsweise verbreiteten Methoden wie Kanban oder Feature Driven Development (FDD).

Interessant ist, dass all diese Methoden eine gemeinsame Forderung implizieren, ohne diese explizit zu formulieren. Diese implizite Forderung besagt, dass die Entwicklung von Software featurebezogen durchgeführt wird. Am ehesten findet man diese Forderung – dem Namen entsprechend wenig verwunderlich – noch in FDD.

Was besagt die Forderung nach featurebezogener Entwicklung? Ich persönlich würde sie wie folgt formulieren:

Der Kundennutzen weniger, aber voll funktionsfähiger Features wiegt höher als der vieler, aber kaum funktionsfähiger Features.

Anders formuliert: Wird ein laufendes Entwicklungsprojekt vorzeitig abgebrochen, kann der Kunde eine Software, bei der 25% der ursprünglich geplanten Features jeweils zu 100% implementiert wurden, zumindest partiell nutzen – eine Software, bei der 100% der ursprünglich geplanten Features jeweils zu 25% implementiert wurden, jedoch gar nicht.

Daraus folgt, dass es in der Regel sinnvoller ist, die Entwicklung an Hand logischer statt physischer Features auszurichten. Zudem bedeutet dies aber auch, dass ein Entwickler zu einem Zeitpunkt nach Möglichkeit nur an einem einzigen Feature arbeiten sollte – und nicht an verschiedenen gleichzeitig.

Auf diese Art kann ein Entwickler effizient dazu beitragen, dem Kunden beständig neue Features zur Verfügung zu stellen zu können, die für jenen einen eigenen Geschäftswert darstellen und den Wert der in der Entwicklung befindlichen Software stetig steigern.

Die Gefahr, dass der Kunde das Projekt auf Grund von fehlenden Resultaten vorzeitig abbricht, sinkt gleichzeitig – es wird also nicht nur der Gewinn für den Kunden erhöht, sondern zugleich auch das eigene Risiko vermindert.

Die verschiedenen agilen Methoden implizieren dieses Vorgehen, zumindest teilweise, formulieren es jedoch nicht explizit:

  • Scrum: Scrum fordert, dass das Team am Ende eines jeden Sprints die zuvor für diesen Sprint ausgewählten Backlog Items erledigt hat, wobei jedes dieser Backlog Items als User Story formuliert ist und einen Kundennutzen darstellt – abgesehen von der fehlenden Forderung, dass ein Entwickler zu jedem Zeitpunkt ausschließlich an einer Aufgabe arbeiten sollte, unterstützt Scrum featurebezogene Entwicklung vollständig.
  • XP: Extreme Programming fordert, dass die entwickelte Software häufig veröffentlicht wird, jeglicher Code durch Unittests abgedeckt wird, für jeden gefundenen Fehler neue Unittests geschrieben werden und Code sämtliche Unittests bestehen muss, bevor er veröffentlich werden darf. In Kombination folgt daraus, dass kein fehlerbehafteter Code ausgeliefert wird – da zudem gefordert wird, dass jede Iteration den Kundennutzen verbessert, fehlt in XP letztlich nur die gleiche Forderung wie in Scrum.
  • Kanban: Kanban ist das Missing Piece zu Scrum und XP – Kanban sagt zwar nichts darüber aus, wie die einzelnen Tasks aufgeteilt werden, beschränkt aber explizit die Last des einzelnen Entwicklers. Dabei wird in Kanban zwar nicht gefordert, dass zu einem Zeitpunkt nur ein Task bearbeitet werden darf, der Ansatz hierfür ist jedoch enthalten. Nicht zuletzt deshalb werden Scrum und Kanban in der Praxis häufig zu Scrumban kombiniert.
  • FDD: Feature-Driven Development schließlich fordert die Ausrichtung auf logische Features explizit, wobei der für die Implementierung eines einzelnen Features benötigte Zeitraum nicht länger als zwei Wochen betragen darf. Auch hier gelten also die gleichen Einschränkungen wie bei Scrum und XP.

Fasst man nun die genannten Aspekte in eine Regel zusammen, würde ich diese als Featurebezogene Entwicklung (Feature Related Development) bezeichnen und wie folgt formulieren:

Der Kundennutzen weniger, aber voll funktionsfähiger Features wiegt höher als der vieler, aber kaum funktionsfähiger Features. Daher arbeitet jeder Entwickler stets an nur einem einzigen Feature zur gleichen Zeit, wobei dieses Feature in sich logisch abgeschlossen ist und einen eigenen Kundennutzen darstellt.

Zu guter letzt verbleibt die Frage, wann ein Feature als abgeschlossen gilt – hier zahlt sich eine gute Definition of Done (DoD) aus, die den Prozess der Entscheidung, ob ein Feature abgeschlossen ist oder nicht, transparent und für alle Beteiligten jederzeit nachvollziehbar gestaltet.

In ihrer vollständigen Fassung besagt die Regel Featurebezogene Entwicklung (Feature Related Development) also:

Der Kundennutzen weniger, aber voll funktionsfähiger Features wiegt höher als der vieler, aber kaum funktionsfähiger Features. Daher arbeitet jeder Entwickler stets an nur einem einzigen Feature zur gleichen Zeit, wobei dieses Feature in sich logisch abgeschlossen ist und einen eigenen Kundennutzen darstellt. Ein Feature gilt dann als fertiggestellt, wenn es die Definition of Done erfüllt.

Wird diese Regel beachtet, wird der Nutzen für den Kunden maximiert und das eigene Risiko minimiert – zudem gelten natürlich alle Vorteile agiler Entwicklung wie zeitnahes Feedback weiterhin.

Terminologie von EBCs

Sonntag, 8. August 2010, 23:42 Uhr
Permalink | Kommentare (8) | Kommentare als RSSRSS

Nach meinem ersten Eindruck von Event-Based Components habe ich vor einigen Tagen über den Nutzen von EBCs geschrieben. Als Schlüssel zum Verständnis sehe ich dabei den Aspekt, dass EBCs topologieunabhängig sind – in einen einzigen Satz zusammengefasst habe ich dies folgendermaßen formuliert:

Im Gegensatz zu klassischen Komponenten rufen EBCs keine Methoden anderer Komponenten auf, sondern stellen nur den Wunsch nach Weiterverarbeitung ihrer Daten in den Raum.

Eine Frage dazu habe ich jedoch noch nicht beantwortet: Wie sieht eine konkrete Implementierung einer solchen topologieunabhängigen Komponente aus?

Ich habe vor einigen Tagen ebenfalls darüber geschrieben, dass das Konzept von EBCs vergleichbar mit dem von Hardwarechips sei, und dass EBCs wie jene auch Input- und Output-Pins anbieten würden.

Die Frage lautet also, wie diese Pins in Code implementiert werden. Eine kurze Antwort darauf habe ich schon gegeben:

Input-Pins entsprechen klassischen Methoden, Output-Pins werden als Events deklariert, woher auch der Name der EBCs rührt.

Das bedeutet: Eine EBC ist zunächst nichts anderes als eine herkömmliche Klasse. Weder ist eine spezielle Basisklasse erforderlich, noch müssen spezielle Schnittstellen implementiert werden – eine EBC ist zunächst einfach nur eine Klasse.

Damit aus einer Klasse jedoch eine EBC wird, müssen von ihr drei Anforderungen erfüllt werden, die sich allesamt auf ihren Kontrakt – also die öffentliche Schnittstelle der Klasse – beziehen:

  • Methoden: Alle Methoden des Kontrakts verfügen über nur einen einzigen Parameter und verzichten auf einen Rückgabewert – ihr Rückgabetyp ist also void. Die Signatur jeder einzelnen Methode entspricht damit der Signatur des Action<T>-Delegaten. Diese Methoden stellen die Input-Pins dar und ermöglichen es, Requests an die Klasse zu stellen. Sind für einen solchen Request mehrere Parameter erforderlich, können diese in einem gemeinsamen Parameterobjekt gekapselt werden.
  • Ereignisse: Für manche Methoden des Kontrakts ist die Einschränkung auf void als Rückgabetyp problemlos – da sie ihre Aufgabe erfüllen und keinen Rückgabewert benötigen. Andere Methoden benötigen diese Möglichkeit jedoch. Für diese wird bei EBCs ein anderer Weg gewählt: Statt das Ergebnis direkt an den Aufrufer zurück zu geben, wird ein Ereignis ausgelöst, dem das Ergebnis als Parameter mitgegeben wird. Der Typ des Ereignisses entspricht auch hier wieder Action<T>, die Ereignisse an sich entsprechen den Output-Pins.

Zunächst mag dies ungewohnt erscheinen – der Sinn offenbart sich jedoch schlagartig, sobald mehr als eine EBC ins Spiel kommt: Es wird kein Verbund von Komponenten mehr benötigt, die untereinander funktionale Abhängigkeiten aufweisen. Statt dessen wird ein Fluss von Daten modelliert, bei dem Daten immer von einer EBC an die nächste weitergereicht werden.

Dieser Fluss wird modelliert, indem das Output-Pin einer EBC mit dem Input-Pin einer anderen EBC verbunden wird. Ein Methode mit Rückgabewert entspricht dann einfach zwei gegenläufig verdrahteten Input- und Output-Pins.

Damit ist die Topologieunabhängigkeit bereits umgesetzt – schließlich erfolgt die Verdrahtung der Ereignisse von außerhalb der EBCs, so dass diese gar nicht wissen, wer auf ein ausgelöstes Ereignis reagiert. Sie wissen noch nicht einmal, ob überhaupt jemand auf das Ereignis reagiert.

EBCs stellen also isolierte Funktionalität bereit, erhalten von irgendwoher Daten zur Verarbeitung, verarbeiten diese, und stellen diese wieder in den Raum zur weiteren Verarbeitung. Sie verhalten sich damit analog zu Chips: Auch diese erhalten von irgendwoher ihre Signale, verarbeiten diese, und senden Signale aus, ohne Kenntnis von vorhergehenden oder nachfolgenden Chips.

Natürlich können EBCs nicht nur sequenziell hintereinandergeschaltet werden: Neben dem sequenziellen sind prinzipiell auch alle anderen Signalflüsse denkbar, die sich mit Ereignissen modellieren lassen: So kann ein Ereignis beispielsweise auf mehrere ereignisbehandelnde Methoden gebunden werden, so dass es zu einer Verteilung der Daten kommt.

Es verbleibt die Frage, nach welchem Schema die Methoden und Ereignisse von EBCs benannt werden können. Ralf Westphal hat dazu in seinem EBC-Binder Konventionen definiert. Dies ist begrüßenswert, ermöglicht dies doch eine einfachere Entwicklung – allerdings gefällt mir persönlich der Vorschlag, Ereignisnamen mit dem Präfix On zu versehen, überhaupt nicht.

Schließlich ist das On-Prefix im Kontext von Ereignissen in .NET bereits anderweitig belegt, nämlich für die ereignisauslösenden Methoden. Deshalb habe ich für mich folgende Konventionen definiert:

  • Requests: Requestmethoden werden – unabhängig davon, ob sie ein Output-Pin ansteuern oder nicht – derart bezeichnet, wie man es auch klassischerweise machen würde. Eine Requestmethode kann also beispielsweise RunProcess oder aber auch LoadCustomer heißen, der Rückgabetyp ist aber stets void.
  • Ereignisse: Der Name des Ereignisses ergibt sich dann ebenfalls ganz klassisch auf Grund der zugehörigen Requestmethode. Ein Ereignis könnte also beispielsweise CustomerLoading oder CustomerLoaded heißen.

Von Verben wie Process oder Handle würde ich in den Namen der Requestmethoden von EBCs absehen – ebenso wie dies auch bei klassischem Code gilt. Das On-Präfix bleibt damit den ereignisauslösenden Methoden vorbehalten. Auf diese Art bewegt man sich trotz EBC-Ansatz ein wenig näher an der gängigen .NET-Terminologie, was EBCs –zumindest aus meiner Sicht – ein bisschen greifbarer macht.

Zu guter letzt bleibt noch die Frage, wer EBCs verdrahtet: Dies macht die Platine, auf der EBCs angeordnet werden – ganz so, wie auch die Leiterbahnen von Chips auf einer Platine verlötet werden. Da der Begriff Platine jedoch sehr hardwarelastig ist, weiche ich an dieser Stelle auf den Begriff Container aus.

Natürlich bleibt die Wahl der Terminologie letztlich jedem selbst überlassen. Vielleicht hilft die Übersetzung von Ralfs gewählter EBC-Terminologie in .NET-affine Begriff jedoch dem ein oder anderen beim grundlegenden Verständnis, weil man sich trotz einem neuen Konzept in syntaktisch vertrauten Gefilden bewegt.

Privaten Code testen

Samstag, 7. August 2010, 00:40 Uhr
Permalink | Kommentare (9) | Kommentare als RSSRSS

Vor einigen Tagen habe ich den Blogeintrag Wie viel kosten Unittests? geschrieben, um zu zeigen, dass der Einsatz von Unittests nicht aufwändiger und teurer ist als der Verzicht auf diese. Mein Fazit lautete:

Da ich – bevor ich die Entwicklung einer Komponente beginne – auf Grund von EBCs und TDD ohnehin detailliert plane, wie deren API aussehen und wie sich diese API für einen Verwender anfühlen wird, erfordert die Planung häufig deutlich mehr Zeit als die eigentliche Implementierung.

Der Aufwand, die Implementierung im Sinne von TDD noch mit Unittests auszustatten, geht daher gegen Null.

Auf die Idee für diesen Blogeintrag haben mich der Blogeintrag TDD in der (meiner) Praxis – Wunsch und Wirklichkeit von Thomas Bandt und der Thread Tatsächlicher Nutzen von Unit-Tests? auf myCSharp.de gebracht, die beide ebendiesen Aspekt in Frage gestellt haben.

In besagtem Thread auf myCSharp.de hat sich zwischenzeitlich noch eine weitere interessante Diskussion ergeben: Die aktuell dort diskutierte Frage lautet, inwiefern privater Code mit eigenen Unittests ausgestattet werden sollte.

Hierzu empfiehlt es sich, zunächst die verschiedenen Varianten zu evaluieren, wie dies überhaupt geschehen kann – schließlich ist der Zugriff auf privaten Code von außen per se zunächst nicht möglich.

  • Reflection: Die einfachste Variante, privaten Code von außen zugreifbar zu machen, liegt darin, per Reflection auf ihn zuzugreifen. Dies ist auch die Variante, die Visual Studio von Haus aus im Rahmen der automatischen generierten Zugriffsklassen für Tests bietet. Die Nachteile liegen auf der Hand: Da der Zugriff mit Hilfe von Reflection naturgemäß nicht typisiert erfolgen kann, muss auf Magic Strings zurückgegriffen werden – die spätestens bei umfangreichen Refaktorisierungen für Probleme und Unannehmlichkeiten sorgen.
  • Vererbung: Als zweite Möglichkeit bietet sich an, den Zugriffsmodifizierer private durch den Zugriffsmodifizierer protected zu ersetzen – auf diese Art wird privater Code zumindest für abgeleitete Klassen zugreifbar. Da prinzipiell nichts dagegen spricht, auch eine Testklasse von der zu testenden Klasse abzuleiten, kann der Zugriff auf diese Art hergestellt werden. Doch private und protected weisen eine unterschiedliche Semantik auf und sind daher nicht bedingungslos gegeneinander austauschbar. Zudem funktioniert dieser Trick nicht bei versiegelten Klassen, von denen nicht abgeleitet werden kann.
  • InternalsVisibleTo: Als weitere Möglichkeit kann der Zugriffsmodifizierer private auch durch den Zugriffsmodifizierer internal ersetzt werden. Wird zusätzlich das Attribut InternalsVisibleTo verwendet, fühlt sich der Zugriff auf privaten Code aus Unittests de facto so an wie der Zugriff auf nicht-privaten Code. Allerdings trifft der Nachteil der zweiten Variante hier ebenfalls zu – sogar in gravierenderem Ausmaß, da internal noch freigiebiger ist als protected.
  • Zustand als Abhängigkeit: Die vierte Variante schließlich wurde von Ralf Westphal in seinem sehr lesenswerten Blogeintrag Zustand als Abhängigkeit – IoC konsequent gedacht beschrieben. Diese Variante ist deutlich aufwändiger, packt das Problem jedoch an der Wurzel an und stellt – zumindest in meinen Augen – die beste der hier vorgestellten vier Varianten dar. Allerdings verfügt sie neben dem hohen Aufwand noch über einen weiteren Nachteil: Sie erfordert die Einführung eines zusätzlichen, nur für Tests gedachten Konstruktors – die Tests verwenden also nicht mehr den gleichen Konstruktor, der auch im Produktivcode zur Anwendung kommt.

Alles in allem ist das Testen von privatem Code also nicht einfach und wirft etliche Probleme auf. In TDD gilt die Regel, dass schwer testbarer Code in der Regel eine Schwäche in seiner Architektur aufweist. Nimmt man beides zusammen, folgt aus dem Bedürfnis, privaten Code testen zu wollen, dass die zu Grunde liegende Architektur des zu testenden Codes eine Schwachstelle aufweist.

Bernd Hengelein schreibt dazu in seinem Blogeintrag TDD – ein Garant für heiße Diskussionen:

Wenn man den Drang verspürt eine private Methode testen zu wollen, ist das ein Code Smell der auf eine Umstrukturierung der Klasse hindeutet.

Auf stackoverflow.com wird in How do you unit test private methods? ähnlich argumentiert:

If you want to unit test a private method, something may be wrong. Unit tests are (generally speaking) meant to test the interface of a class, meaning its public (and protected) methods.

Auch Wikipedia geht in dem Abschnitt Eigenschaften von Modultests auf ähnliche Art auf die Frage ein:

Modultests sollen gemäß dem Design by contract Prinzip möglichst nicht die Interna einer Methode testen, sondern nur ihre externen Auswirkungen (Rückgabewerte, Ausgaben, Zustandsänderungen, Zusicherungen). Werden auch interne Details der Methode geprüft (dies wird als White-Box-Testing bezeichnet), droht der Test fragil zu werden, er könnte auch fehlschlagen, obwohl sich die externen Auswirkungen nicht geändert haben. Daher wird in der Regel das sogenannte Black-Box-Testing empfohlen, bei dem man sich auf das Prüfen der externen Auswirkungen beschränkt.

Das Buch Pragmatic Unit Testing äußert sich ebenfalls ähnlich:

In general, we don’t want to break any encapsulation for the sake of testing (or as Mom used to say, “Don’t expose your privates!”).

Kurzum: Die Fachwelt ist sich einig, dass man privaten Code nicht testen sollte. Doch warum ist dies so? Wie liegt die architektonische Schwachstelle, deren Existenz man aus der bloßen Schwierigkeit, einen Test zu formulieren, ableiten kann?

Die Schwachstelle liegt im Kontrakt der zu testenden Klasse. Jeder Code weist einen Kontrakt auf – implizit oder explizit als dedizierte Schnittstelle – der für die Verwender dieses Codes gedacht ist: Auf das, was mir öffentlich zugesichert wird, kann ich mich verlassen. Dies gilt dabei nicht nur im Hinblick auf die Syntax, sondern explizit auch im Hinblick auf die Semantik.

Alles, was nicht im Kontrakt enthalten ist, wird mir als Verwender auch nicht explizit zugesichert. Anders formuliert: Alles, was nicht im Kontrakt enthalten, sondern private ist, zählt zu den Interna – die mich als Verwender nichts angehen. Das ist das Prinzip einer Blackbox.

Was ist nun der Sinn eines Unittests? Der Unittest soll dem Entwickler einer Klasse garantieren, dass er diese derart refaktorisieren kann, dass er ihre innere Struktur ändert, ohne zugleich den nach außen zugesicherten Kontrakt zu ändern. Ein Unittest ermöglicht dem Entwickler, das Innere der Blackbox nach Belieben zu verändern, sofern ihre Funktionsweise nach außen erhalten bleibt.

Würde man nun beginnen, auch das innere der Blackbox mit Unittests abzusichern, so würde man dem Entwickler diese Freiheit nehmen und in seinem Revier wildern. Eine interne, durchaus sinnvolle, aber ausgesprochen große Refaktorisierung würde hiermit unmöglich gemacht – schließlich könnten potenziell alle Unittests, die auf das Innere der Blackbox abzielen, fehlschlagen.

Die Unittests müssten also angepasst werden – was aber der ursprünglichen Absicht widerspricht, mit den Unittests sicherzustellen, dass das Verhalten vor und nach der Refaktorisierung identisch ist. Kurzum – das Testen von privatem Code ist nicht nur umständlich, sondern geht auch noch an der eigentlichen Absicht der Tests vorbei.

Die einzig verbleibende Frage lautet nun noch, wie mit privatem Code umgegangen werden sollte – wenn er sich schon als ungeeignet für Unittests erweist?

Die Antwort auf diese Frage ist einfach: Zunächst einmal gilt, dass auch privater Code getestet wird – nur eben indirekt. Ruft eine öffentliche Methode privaten Code auf, so wird dieser implizit ebenfalls getestet – ist dies nicht der Fall, handelt es sich eventuell um Code, der gar nicht benötigt wird, oder um einen Hinweis darauf, dass noch nicht alle potenziellen Fälle per Test überprüft werden.

Für private Methoden gibt es jedoch noch einen weiteren Ansatz, wie mit ihnen umgegangen werden kann: Häufig enthalten private Methoden nämlich Code, der gar nichts mit dem eigentlichen Zweck der sie enthaltenden Klasse gemeinsam hat – streng genommen handelt es sich also sogar um eine Verletzung des Single Resposibility-Prinzips (SRP).

Die Lösung hierfür lautet: Auslagern des privaten Codes in eine eigene Klasse mit dedizierter Verantwortlichkeit. Diese weitere Einführung zusätzlicher Klassen oder Komponenten mit eigener Verantwortlichkeit führt damit nicht nur zu besserer Testbarkeit, sondern automatisch auch zu einer Verbesserung der Architektur – und kann in nahezu allen Fällen dieser Art angewandt werden.

Am Anfang mag sich dies ein wenig ungewohnt anfühlen – doch wenn man darüber nachdenkt, findet man beinahe in jedem Fall entsprechende Verantwortlichkeiten, die vermischt wurden. Diese aufzulösen, ist dann ein Leichtes.

Abschließend kann noch festgestellt werden, dass das Problem des Testens von privatem Code hauptsächlich Entwickler betrifft, die nicht nach TDD vorgehen. Dies ist jedoch nicht verwunderlich, denn TDD-affine Entwickler schreiben zuerst den Test und dann erst den Code: Privater Code kann hier also nur durch Refaktorisieren von bereits bestehendem und getestetem Code entstehen.

TDD sorgt also auch in diesem Fall für eine bessere Architektur – für mich persönlich, ein weiterer Grund, den mit TDD eingeschlagenen Weg weiter zu verfolgen.

Wann ist Code “fertig”?

Donnerstag, 22. Juli 2010, 11:21 Uhr
Permalink | Kommentare (4) | Kommentare als RSSRSS

Scrum als agile Methode verlangt ein dediziertes Dokument, in dem definiert wird, wann Code als “fertig” anzusehen ist. Dieses Dokument wird als Definition of Done bezeichnet und dient allen Beteiligten als gemeinsame Basis zur Abnahme von Code.

Mir gefällt diese Idee.

Allerdings fordert Scrum nur, dass es ein solches Dokument geben muss – es wird jedoch keinerlei Aussage dazu getroffen, wie eine entsprechende Definition aussehen könnte.

Hieran zeigt sich wieder das typische Problem von Scrum: Es bietet nur einen Rahmen, aber keinen Inhalt. Das macht Scrum sehr portabel und vielfältig einsetzbar, das macht Scrum aber auch sehr unspezifisch.

Aus diesem Grund habe ich mir Gedanken gemacht, welche Eigenschaften Code für mich aufweisen muss, um als “fertig” gelten zu können. Entstanden ist meine ganz persönliche Definition of Done.

Code is considered to be “done”, when:

1. It does satisfy all its functional and non-functional requirements.
2. It does not contain any known errors.
3. It has been commented and documented.

4. It has been either pair-programmed or reviewed.
5. It has been developed test-driven using 4-Step TDD.
6. It does not need to be refactored or rearranged.

7. It has been written according to well-known best practices.
8. It does conform to accepted coding standards.
9. It does pass static code analysis without any errors or warnings.

10. It has been integrated and does not break the integration build.
11. It has been checked in into source control.

Wenn Code all diese Punkte erfüllt, kann man den dazugehörigen Task in meinen Augen guten Gewissens als abgeschlossen betrachten.

tech:lounge in München und Köln

Samstag, 29. Mai 2010, 13:29 Uhr
Permalink | Kommentare (4) | Kommentare als RSSRSS

Jeder, der sich mit der Konzeption und der Entwicklung von Software beschäftigt, kennt das Problem: Man ist so sehr in das Projekt oder den zu schreibenden Code vertieft, dass es nicht gelingt, die notwendige kritische Distanz zu bewahren, um Denkfehler frühzeitig aufdecken und Sackgassen vermeiden zu können.

Da Softwareentwicklung als Prozess selten nach außen kommuniziert wird, kommt Hilfe häufig spät oder gar zu spät – denn in der Regel wird erst dann um Hilfe gebeten, wenn das Kind schon in den Brunnen gefallen ist. Was fehlt, ist ein geeigneter, unabhängiger Sparringspartner, der eine zweite Sicht und einen anderen Standpunkt einbringen kann.

Deshalb biete ich am 25. Juni 2010 in München und am 28. Juni 2010 in Köln ebendies an: An beiden Tagen bin ich jeweils von 9 bis 17 Uhr in einem Café und setze mich mit Entwicklern, Architekten, Team- und Projektleitern zusammen, um deren Probleme zu besprechen und gemeinsam zu versuchen, nach bestem Wissen und Gewissen einen geeigneten Lösungsansatz zu finden – kostenlos, wobei ich gegen eine Einladung auf eine Cola nichts einzuwenden habe.

Thematisch wird es sich dabei um .NET, Codequalität und agile Methoden drehen: Ob es dabei um die Planung, die Architektur, die konkrete Entwicklung oder den Prozess geht, ob die Fragen eher genereller oder eher spezieller Natur sind, spielt keine Rolle – das entscheiden Sie, je nachdem, was Sie mitbringen!

Damit verschiedene Interessenten die Gelegenheit erhalten, sich mit mir zu besprechen, ist eine Sitzung auf 90 Minuten begrenzt – wobei sich in 90 Minuten häufig deutlich mehr besprechen lässt, als man zunächst vermuten würde: Wichtig ist nur, dass Sie das zu besprechende Thema in kompakter, aber vollständiger Form mitbringen, so dass mir genug Zeit bleibt, mich hineinzudenken.

Aus diesem Grund möchte ich auch darum bitten, dass sich Interessenten vorher bei mir per E-Mail anmelden – auch um auszuschließen, dass ich zu einem Thema nichts beitragen kann oder dass sich mehrere Interessenten zeitlich überschneiden, um die Diskretion zu wahren: Eine Sitzung ist also nicht öffentlich zugänglich für interessierte Zuhörer.

Wenn dieses Angebot Ihr Interesse geweckt hat, finden Sie abschließend noch die Termine und Lokationen im Überblick:

Ich freue mich auf spannende Themen und interessante Diskussionen!

Was ist “einfach”?

Samstag, 8. Mai 2010, 23:22 Uhr
Permalink | Kommentare (3) | Kommentare als RSSRSS

Eines der grundlegenden Prinzipien in der Softwareentwicklung ist das KISS-Prinzip: In der Formulierung Keep it simple, stupid ist im roten Grad von Clean Code Developer (CCD) enthalten, Extreme Programming propragiert Einfachheit als Regel und auch im Agilen Manifest ist Einfachheit als essenzielles Prinzip enthalten.

Softwareentwicklung soll sich also an Einfachheit orientieren – doch was bedeutet das? Was gilt als einfach und was nicht?

Eine eindeutige und allgemein akzeptierte Definition erscheint schwierig, entsprechend verschieden fallen die jeweiligen Erklärung auch aus. Während es auf der CCD-Webseite zunächst

Wer mehr tut als das Einfachste, lässt den Kunden warten und macht die Lösung unnötig kompliziert.

heißt, was sich mit dem im blauen Grad enthaltenen YAGNI-Prinzip überschneidet, wird dann im weiteren Text jedoch erläutert, es sei

für die Evolvierbarkeit des Codes zwingende Voraussetzung, dass der Code verständlich sei.

Zwar ist verständlich ein ebenso subjektiver Begriff wie einfach, deutet aber schon eher in die gewünschte Richtung. Verständlichkeit war auch das Ziel, das ich in meinem Blogeintrag Anmut und Eleganz verfolgt habe – insofern ziehen CCD und ich hier an einem Strang.

Schön ist, dass CCD dabei auch auf Code zu sprechen kommt:

Wenn ein IEnumerable reicht, sollte kein ICollection oder sogar IList verwendet werden.

Auch das entspricht meiner Vorstellung und deckt sich mit dem, was ich als eine gelungene innere Form von Code bezeichne.

XP nennt ebenfalls Verständlichkeit, allerdings nur als einen von vier Aspekten, die gemeinsam Einfachheit definieren. Nach XP zählen hierzu noch die drei Aspekte Testbarkeit, Navigierbarkeit und Erklärbarkeit:

  • Testbarkeit: Nur Code, der sich auf das Notwendige beschränkt, ist zugleich auch einfach zu testen – ansonsten müssen nämlich Spezial- und Sonderfälle wie auch die Auswirkungen von Seiteneffekten im Test berücksichtigt werden. Auch wenn weder CCD noch ich in Anmut und Eleganz die Testbarkeit explizit im Zusammenhang mit Einfachheit ansprechen – auch hier sind wir letztlich einer Meinung.
  • Navigierbarkeit: Zur Einfachheit von Code zählt neben einer guten Verständlichkeit und einer guten Testbarkeit auch eine gute Navigierbarkeit: Das heißt, in einfachem Code findet man sich zurecht, und es fällt leicht, zwischen den beteiligten Klassen und Komponenten hin und her zu navigieren.
  • Erklärbarkeit: Während die Testbarkeit in gewissem Sinne messbar ist, sind Einfachheit und Navigierbarkeit in höchstem Maße subjektiv: Negativ fällt dies vor allem dann auf, wenn ein Team eingespielt ist und bereits lange Zeit an einer Software arbeitet – in diesem Fall wird sich jeder Entwickler des Teams im Code zurechtfinden und ihn verstehen. Aus diesem Grund muss Code für Einfachheit zusätzlich auch einem Uneingeweihten einfach zu erklären sein.

Das Agile Manifest schließlich definiert Einfachheit als

the art of maximizing the amount of work not done.

Hier wird also letztlich nur der Effekt der Reduktion, des minimalen Codes betont – was jedoch für mein Empfinden nicht genügt. Sicherlich kann man argumentieren, dass in dieser Aussage implizit auch die Arbeit angesprochen wird, die zum späteren Verstehen von Code investiert werden muss – viele Entwickler werden diese Interpretation jedoch zunächst überlesen.

All zu oft wird Einfachheit ansonsten nämlich mit dem YAGNI-Prinzip gleichgesetzt – dabei handelt es sich jedoch um zwei verschiedene Konzepte: Das YAGNI-Prinzip empfiehlt, keine Funktionalität im guten Glauben auf die zukünftige Erforderlichkeit umzusetzen, ohne eine konkrete Anforderung dafür zu haben. Das ist dann auch genau das, was im Rahmen von CCD kritisiert wird:

Wer mehr tut als das Einfachste, lässt den Kunden warten und macht die Lösung unnötig kompliziert.

Einfachheit im Sinne des KISS-Prinzips läuft jedoch Gefahr, dass während der Entwicklung lediglich simple und naheliegende Sprachkonstrukte verwendet werden – obwohl ein fortgeschritteneres Konstrukt eventuell deutlich besser geeignet wäre. Ein typisches Beispiel hierfür ist das Schlüsselwort yield, das viel zu selten Verwendung findet.

Außerdem wird dabei häufig auf Design für zukünftige Erweiterbarkeit verzichtet, obwohl dies mit nur unwesentlich mehr Aufwand möglich wäre – so wird beispielsweise eher auf verschachtelte und zahlreiche if-Anweisungen statt auf besserpassende objektorientierte Konstrukte gesetzt.

Kurzum: Einfachheit bedeutet nicht zwingend, das Naheliegendste und mit möglichst wenig Aufwand umsetzbare zu implementieren, sondern erstens, keine Funktionalität zu entwickeln, für die keine Anforderung besteht, zweitens, der Verständlichkeit des Codes höheren Stellenwert einzuräumen als der Entwicklung, und drittens, die zukünftige Erweiterbarkeit nicht außer Acht zu lassen.

Schlussendlich kann man daher festhalten, dass der Begriff einfach nicht besonders glücklich gewählt ist: Häufig wird darunter nämlich die minimalistische Reduktion auf einfache Sprachkonstrukte und wenige Codezeilen verstanden – die Verständlichkeit und zukünftige Erweiterbarkeit bleiben hierbei außen vor.

Daher wäre es meines Erachtens sinnvoll, an Stelle von Einfachheit – oder zumindest zusätzlich – einen anderen, treffenderen Begriff zu propagieren, der Verständlichkeit und zukünftige Erweiterbarkeit einschließt. Am ehesten treffen dies meiner Meinung nach die Begriffe Anmut und Eleganz.

Anmut und Eleganz

Samstag, 1. Mai 2010, 09:37 Uhr
Permalink | Kommentare (9) | Kommentare als RSSRSS

Am 13. Oktober 2008 haben Peter Bucher und ich unter dem Titel Noch Fragen, Bucher? Ja, Roden! angekündigt, jeweils zum ersten eines jeden Monats einen Kommentar zu einem vorab gemeinsam gewählten Thema verfassen zu wollen. Bisher sind in dieser Reihe folgende Kommentare erschienen:

Heute, am 1. Mai 2010, ist es nun wieder so weit, und unser Thema für diesen Monat lautet:

Anmut und Eleganz

So wohl Peter wie auch ich haben uns unabhängig voneinander im Vorfeld unsere Gedanken gemacht, wie wir diesem Thema gegenüberstehen. Peters Kommentar findet sich zeitgleich in seinem Blog, folgend nun mein Kommentar zu diesem Thema:

Anmut und Eleganz – zwei Begriffe der Ästhetik und der Schönheit. Laut Wikipedia wird Anmut als

Form des Schönen, […] der unwillkürliche Ausdruck einer Harmonie

definiert, wohingegen Eleganz als

Ausdruck von besonderem Stil und Geschmack in Design, Architektur, […]

gilt und nicht nur ein ästhetisches Konzept, sondern gar ein entsprechendes Ideal darstellt.

Intuitiv werden diese Begriffe nicht zwingend mit Code assoziiert, doch spielen sie auch in der Entwicklung eine entscheidende Rolle: Je eher Code eine gewisse Anmut und Eleganz ausstrahlt, desto eher folgt seine innere Form wohlüberlegten Gedanken.

In anderen Worten: Code, an dessen innerer Form gefeilt wurde, ist nicht nur wart- und evolvierbarer als über die Zeit emergierter Code – er folgt zudem einer eigenen Ästhetik: Der des im mathematischen Sinne Eleganten.

Jeder Entwickler, der in seiner Ausbildung mit Mathematik in Berührung gekommen ist, kennt den Term einer schönen Beweisführung. Der Term schön bedeutet in diesem Kontext, dass der Beweis elegant geführt und kompakt ist wie auch auf überflüssige Haken und Wendungen verzichtet wird.

Dies klingt zunächst nach dem Konzept der minimalistischen Architektur: Ein Entwurf ist dann gelungen, wenn kein weiterer Bestandteil aus dem Gesamtbild entfernt werden kann, ohne dass die Qualität oder die Funktionalität des Entwurfs darunter leidet.

Insofern liegt die Vermutung nahe, dass anmutiger und eleganter Code gleichzusetzen sei mit minimalistischem Code: Dass Code so lange reduziert, Ausdrücke vereinfacht und zusammengefasst, und aufwändige Konstrukte durch kompakte ersetzt werden sollten, bis eine minimalistische Form erreicht ist, die ohne Qualitäts- oder funktionale Einbußen nicht weiter reduziert werden kann.

Gilt also die Gleichung anmutiger und eleganter Code gleich minimaler, kompakter und reduzierter Code?

Während dieses Ziel in der Mathematik durchaus in dieser Form gelten könnte, lässt es einen wesentlichen Aspekt von Code aus: Im Gegensatz zu einem mathematischen Beweis, der – einmal erfolgt – nur noch zu Referenzzwecken nachvollzogen wird, wird Code immer und immer wieder gelesen.

Die gängige Wendung

It was hard to write, so it should be hard to read.

ironisiert diesen Aspekt. Fakt ist, dass Code im Idealfall nur ein Mal geschrieben, aber potenziell unzählige Male gelesen wird – nicht nur von anderen Entwicklern, auch vom ursprünglichen Autor: In der Regel gilt dies spätestens dann, wenn Code auf Grund neuer oder geänderter Anforderungen entweder erweitert oder zumindest angepasst werden muss.

Code sollte also für den Leser leicht verständlich sein. Hierzu trägt zunächst die äußere, optische Form von Code einen essenziellen Teil bei. Entsprechende Fragen lauten beispielsweise:

  • Tragen Bezeichner verständliche und beschreibende Namen, die semantisch zum Inhalt passen?
  • Erläutern Kommentare, aus welchen Gründen der kommentierte Code auf diese Art und nicht anders entwickelt wurde?
  • Hielt der Entwickler formale Kriterien wie Verwendung von Elementen wie Einrückung und Leerzeilen ein?
  • Folgen so wohl Code wie auch Kommentare und Dokumentation der gültigen Rechtschreibung und Grammatik?

Doch neben dieser äußeren Form spielt auch die innere Form für die Verständlichkeit eine bedeutende Rolle. Die entscheidende Frage hierzu lautet, ob dem jeweiligen Zweck angemessene Sprachkonstrukte angewandt wurden. Ausgewählte Beispiele dafür bieten folgende Aspekte:

  • Werden LINQ-Abfragen an Stelle von aufwändigen Schleifen mit zahlreichen, unter Umständen verschachtelten if-Anweisungen genutzt?
  • Wie werden Schnittstellen, abstrakte Basisklasse und konkrete Klassen miteinander kombiniert?
  • Werden Konstrukte wie beispielsweise das yield-Schlüsselwort dort eingesetzt, wo sie sinnvoll sind und Arbeit ersparen können?

Um diese Sprachkonstrukte angemessen einsetzen zu können, ist es erforderlich, sein Werkzeug zu beherrschen – sprich, die dargebotenen Mittel der persönlich gewählten Programmiersprache nicht nur zu kennen, sondern sie zu beherrschen.

Zur Verbesserung der inneren Form gibt es zahlreiche Regeln – bereits Werkzeuge zur statischen Codeanalyse wie FxCop oder die in Visual Studio integrierte Codeanalyse können hierbei eine wesentliche Hilfestellung leisten.

Je länger man sich jedoch als Entwickler mit diesen Themen beschäftigt und versucht, während der Entwicklung bewusst so wohl auf die innere wie auch auf die äußere Form von Code zu achten, desto eher stellt sich – auf Basis der nach und nach verinnerlichten Regeln – ein Gespür für ebenjene Anmut und Eleganz ein, die wart- und evolvierbaren Code auszeichnet.

Da dieses Gespür für ästhetisch gelungenen Code von handfesten Regeln getragen wird, besteht keine Gefahr, Initiativen wie beispielsweise Clean Code Developer zuwider zu handeln: Im Gegenteil – da dem Gespür für Ästhetik und Eleganz die Internisierung der Regeln zu Grunde liegt, erleichtert es letztlich den Umgang mit ebendiesen.

Eine Anmerkung in eigener Sache: Dieser Beitrag ist der – inzwischen achtzehnte – Kommentar im Rahmen von Noch Fragen, Bucher? Ja, Roden!. Unsere Reihe geht nun vorerst in eine Kreativpause, in der wir am Konzept und neuen Themen feilen werden. Auf absehbare Zeit werden wir diese Reihe fortsetzen.

Freiberuflicher Wissensvermittler und Technologieberater

Samstag, 3. April 2010, 20:24 Uhr
Permalink | Kommentare (4) | Kommentare als RSSRSS

Am 1. Juni 2010 wird es so weit sein: Ich mache den Schritt in die endgültige und vollständige Selbständigkeit. Ab diesem Tag werde ich auf freiberuflicher Basis als Wissensvermittler und Technologieberater für .NET, Codequalität und agile Methoden arbeiten.

Im Rahmen von .NET habe ich mich dabei auf die Sprache C# und die Architektur von Webanwendungen spezialisiert. Meine Auszeichnung als Microsoft Most Valuable Professional (MVP) für C# am 1. April 2010 wie auch meine zweifache Zertifizierung als Microsoft Certified Professional (MCP), unter anderem für die Entwicklung .NET-basierter Webanwendungen, unterstützen diese Schwerpunkte.

Besonderes Augenmerk richte ich bei der Entwicklung einer Webanwendung auf eine auch in der Zukunft tragfähige Architektur wie auch auf die Erreichung einer sehr guten Wartbarkeit und Evolvierbarkeit des Codes. Zu diesem Zweck lege ich besonderen Wert auf hohe Codequalität, von der Einhaltung etablierter Coderichtlinien über testgetriebene Entwicklung bis hin zu Codeanalyse und -reviews.

Dabei unterstützen mich mein akkurates, präzises und reflektiertes Vorgehen wie auch ausgeprägte analytische Fähigkeiten: Ich plane, entwerfe und entwickle mit Liebe zum Detail und achte stets auch auf Feinheiten. Als bekennender Clean Code Developer trete ich zudem für Professionalität in der Softwareentwicklung ein.

Meine Arbeitsweise richtet sich dabei nach den agilen Werten, die durch das Agile Manifest definiert werden, zu dessen Unterzeichnern ich gehöre. Inbesondere schätze ich Extreme Programming (XP) und Scrum als einander ergänzende agile Methoden zur Durchführung von Projekten.

Zu all diesen Themen vermittle ich Wissen und berate Firmen, die auf Basis von .NET Softwareentwicklung durchführen, bei der Evaluierung, Erforschung und Verwendung geeigneter Technologien und Methoden, wobei mir meine langjährige Erfahrung als Softwarearchitekt und Technologieberater wie auch als Autor, Referent und Trainer zu Gute kommt.

Darüber hinaus werde ich auch zukünftig journalistisch für Fachzeitschriften wie auch als Referent und Content Manager für Konferenzen tätig sein. .NET-affinen Usergroups stehe ich als INETA-registrierter Referent ebenfalls zur Verfügung. Weiterhin werde ich außerdem Webcasts für die deutschsprachige MSDN erstellen.

Wenn Sie meine Unterstützung bezüglich Beratung oder Training zu den Themen .NET, Codequalität und agilen Methoden anfordern möchten, können Sie mich gerne jederzeit per E-Mail oder telefonisch kontaktieren. Ich freue mich auf Ihre Anfrage!

Unit- vs Integrationtests

Freitag, 2. April 2010, 12:53 Uhr
Permalink | Kommentare (5) | Kommentare als RSSRSS

Warum der Einsatz von Unittests generell Sinn ergibt, habe ich in vor einigen Tagen in meinem Blogeintrag Vom Saulus zum Paulus beschrieben. Doch was ist überhaupt ein Unittest? Wie grenzt er sich von anderen Arten von Tests ab?

Häufig werden insbesondere Unit- und Integrationtests vermischt – doch um überhaupt eine Trennung erreichen zu können, ist zunächst eine Klärung des Begriffs Unit nötig, ist sie schließlich das individuelle Merkmal eines Unittests. Es gilt also, zwei Fragen zu beantworten:

  • Wie wird der Begriff Unit definiert?
  • Welcher Unterschied besteht zwischen Unit- und Integrationtests?

Einen ersten Anhaltspunkt liefert die deutschsprachige Wikipedia auf ihrer Seite zum Thema Unittest, werden Unittests dort nämlich als

Modultest[s] (auch Komponententest[s])

bezeichnet. Ein Unittest testet also eine Komponente. Dieser Begriff hat nicht zuletzt dank der zunehmenden Verbreitung des Entwurfsmusters Inversion of Control und entsprechenden Dependency Injection-Containern wie beispielsweise LightCore eine gewisse Verbreitung erfahren.

Eine Komponente wird in diesem Zusammenhang in der Regel als ein Softwarepart definiert, der folgende Anforderungen erfüllt:

  • Unabhängig: Eine Komponente ist unabhängig von einem konkreten Projekt, sondern kann flexibel in verschiedenen Projekten genutzt und nach Bedarf hinzugefügt werden. Komponenten dürfen dabei natürlich Abhängigkeiten auf andere Komponenten aufweisen, wobei diese jedoch die gleichen Anforderungen erfüllen müssen.
  • Kontrakt: Eine Komponente verfügt über einen wohldefinierten Kontrakt, der neben der Syntax auch eine gewisse Semantik vorgibt. Der Verwender einer Komponente kennt nur deren Kontrakt, weshalb Komponenten gegen andere Komponenten austauschbar sind, sofern von beiden der gleiche Kontrakt erfüllt wird.
  • Blackbox: Eine Komponente fungiert als Blackbox, das heißt, die Interna – auf welche Art ist welche Funktionalität implementiert – spielen für den Verwender keine Rolle. Da der Verwender eine Komponente nur auf Basis ihres Kontrakts verwendet, interessieren ihn diese Interna in der Regel auch nicht.

Unittests sind zunächst lediglich ein weiterer Verwender von Komponenten. Das heißt, dass die Unittests die an eine Komponente gestellten Anforderungen respektieren und keinen Weg suchen, diese zu umgehen.

Insbesondere bedeutet das, dass ein Unittest nur den Teil einer Komponente testet, der über ihren Kontrakt öffentlich erreichbar ist. Konkrete Implementierung ist gemäß dem Blackbox-Prinzip von Komponenten nicht nach außen – also auch nicht für Unittests – sichtbar.

Außerdem muss ein Unittest eine Komponente unabhängig von anderen Komponenten testen – ansonsten könnten Fehler nicht nur von der Komponente an sich, sondern auch von der Interaktion zwischen verschiedenen Komponenten verursacht werden. Stubs und Mocks sind aus diesem Grund essenziell für die Entwicklung von Unittests.

Zusammengefasst garantiert ein Unittest also, dass eine Komponente ihrem Kontrakt in syntaktischer und semantischer Hinsicht entspricht, Ein Integrationtest hingegen testet das Zusammenspiel verschiedener Komponenten, wobei implizit von der Korrektheit der einzelnen Komponenten ausgegangen wird.

Wann immer also eine Komponente nicht isoliert, sondern im Verbund mit anderen Komponenten getestet wird, handelt es sich bei einem Test um einen Integration- und nicht mehr um einen Unittest. Daraus ergibt sich, dass sich Unit- und Integrationtests ergänzen, aber sauber getrennt werden sollten – schließlich handelt es sich auch um verschiedene Arten von Tests.

Vom Saulus zum Paulus

Mittwoch, 31. März 2010, 19:39 Uhr
Permalink | Kommentare (2) | Kommentare als RSSRSS

Am 1. November 2009 habe ich den Blogeintrag Wie viel Sinn machen Unittests? verfasst, in dem ich mich skeptisch gegenüber dem Einsatz von Unittests geäußert habe – und empörte Reaktionen geerntet habe.

Meine Skepsis gründete sich im Wesentlichen auf drei Kritikpunkte, die im folgenden noch einmal aufgelistet werden:

  • Der bestehende objektorientierte Aufbau ist unter OOP- und stilistischen Aspekten sauber umgesetzt, läuft einer guten Testbarkeit allerdings zuwider.
  • Es bestehen zahlreiche Abhängigkeiten zu externen Komponenten, die sich nicht oder nur mit sehr viel Aufwand simulieren lassen.
  • Es bestehen Abhängigkeiten zur konkreten Laufzeitumgebung, die sich nicht oder nur mit sehr viel Aufwand nachbilden lassen.

Wie sich inzwischen herausgestellt hat, können alle Aspekte zufriedenstellend gelöst werden. Letztlich läuft es im Zweifelsfall in der Regel darauf hinaus, eine zusätzliche Abstraktion einzuführen, um besser entkoppeln zu können – schließlich ermöglicht erst ein gewisses Abstraktionsniveau den sinnvollen Einsatz von Stubs und Mocks.

Doch auch wer – wie ich – helfende Hände gereicht bekommt und es daraufhin geschafft hat, alle technischen Hindernisse aus dem Weg zu räumen, mag noch über den Sinn von Unittests grübeln: Die Frage lautet also nicht, wie viel, sondern warum der Einsatz von Unittests überhaupt Sinn ergibt?

Auf diese Frage möchte ich heute eingehen. Da die Entwicklung einer Anwendung irgendwann bei Null auf der grünen Wiese beginnt, liegt es nahe, sich zunächst über den Einsatz von Unittests bei neu zu schreibendem Code Gedanken zu machen: Hier wirken Unittests häufig als Bremse – schließlich müssen neben dem eigentlichen produktiven Code noch zahlreiche Unittests geschrieben werden.

Wer dieser Aussage in dieser Form ohne Widerspruch zustimmt, tappt in die gleiche Falle wie ich vor einigen Monaten: Er vergisst, dass dieser neu geschriebene produktive Code gründlich getestet werden muss – wenn nicht vom Entwickler, dann hoffentlich von irgendjemandem.

Hier liegt der Hund bereits begraben: Wenn nicht der Entwickler selbst sich um das Testen kümmert, muss es jemand anderes machen – der nicht weiß, was sich der Entwickler im Detail gedacht hat, warum manche Entscheidungen so und nicht anders getroffen wurden, der auch nicht weiß, welche potenziellen Sonderfälle berücksichtigt werden müssen.

Zudem weiß dieser jemand auch nicht, ob er alle potenziellen Codepfade getestet hat – er kennt schließlich die Interna nicht. Er kann nur hoffen, alles abgedeckt zu haben, weiß es aber nie.

Schließlich werden Tests, die erst zu einem relativ späten Zeitpunkt durchgeführt werden, in der Regel nicht automatisiert durchgeführt: Die Durchführung hängt also von der Tagesform des Testers ab und kann – unbeabsichtigt – schwanken und eventuell bei zwei Testläufen in verschiedenen Ergebnisse resultieren.

Kurzum: Die Qualität der Tests ist nicht so hoch, wie sie sein könnte. Die Quantität stimmt gegebenenfalls nicht. Und die Ergebnisse sind weder reproduzierbar noch verlässlich.

Wird hingegen von Anfang an mit Unittests entwickelt, ergeben sich all diese Probleme erst gar nicht: Der Entwickler weiß, was zu testen ist, er kennt die Spezialfälle, er weiß oder kann zumindest herausfinden, wie hoch die Testabdeckung ist, und die Testergebnisse sind – da die Unittests automatisiert ausgeführt werden – jederzeit reproduzierbar.

Außerdem entsteht zwar zusätzlicher Aufwand für das Schreiben der Unittests, aber die Ausführung erfordert kaum noch Aufwand. Da Unittests nur einmal geschrieben, aber immer und immer wieder ausgeführt werden, ergibt sich mittelfristig eine deutliche Zeitersparnis – bei qualitativ besseren Ergebnissen.

Aber es kommt noch besser: Da zumindest bei der testgetriebenen Entwicklung gilt, dass immer nur gerade so viel Code geschrieben werden sollte, wie notwendig ist, um einen Unittest zu erfüllen, wird zum einen nur Code geschrieben, der auch getestet ist, zum anderen wird aber auch nur jener Code geschrieben, der tatsächlich erforderlich ist. Das heißt, der Einsatz von testgetriebener Entwicklung hilft, das YAGNI-Prinzip umzusetzen.

Nun kommt aber in jedem Projekt irgendwann der Zeitpunkt, an dem nicht mehr nur neuer Code hinzugefügt, sondern ab dem auch bestehender Code geändert werden muss. Sei es, weil sich Anforderungen geändert haben oder weil Fehler behoben werden müssen.

In jedem Fall muss bestehender Code geändert werden. Typisch für gewachsene Projekte ist, dass die ursprünglichen Entwickler gar nicht mehr verfügbar sind, oder sich nicht mehr genau an spezielle Implementierungsdetails erinnern können. Da zudem in den meisten Softwareprodukten innere Abhängigkeiten bestehen, findet eine Änderung von Code selten isoliert statt, sondern beeinflusst anderen Code ebenfalls – mal mehr, mal weniger.

Das Problem liegt dann meistens darin, dass Änderungen, die im guten Glauben auf Korrektheit durchgeführt werden, Seiteneffekte haben, die ihrerseits neue Fehler produzieren. Irgendwann werden aus diesem Grund keine Änderungen mehr durchgeführt, sondern vermehrt Workarounds geschrieben – und irgendwann dann Workarounds um die Workarounds, und wieder später Workarounds um die Workarounds um die Workarounds, und so weiter …

Was also fehlt, ist ein Sicherheitsnetz, das den Entwickler beim Auftreten von unerwünschten Seiteneffekten auffängt und ihn darauf hinweist, dass seine Änderung an anderer Stelle ungewollte Auswirkungen hat. Genau dies erfüllen Unittests, wenn sie gepflegt werden und stets sämtlichen Code abdecken.

Das ist meines Erachtens der größte Vorteil von Unittests: Sie erlauben es Entwicklern, Code unbefangen ändern zu können, ohne Befürchtungen wegen Nebenwirkungen haben zu müssen.

Fasst man all diese Vorteile zusammen, so überwiegen die Vorteile ganz deutlich den initialen Aufwand, sich in Unittests einzuarbeiten und die eigene Arbeitsweise auf testgetriebene Entwicklung umzustellen: Unittests vereinfachen die Entwicklung und helfen, sie zielgerichteter und sicherer zu gestalten.

Auch wenn der Anfang schwer ist und eine saubere testgetriebene Entwicklung viel Reflektion und Disziplin erfordert – es lohnt sich.