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.

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.

Vorbedingungen in Unittests

Donnerstag, 18. März 2010, 17:43 Uhr
Permalink | Kommentare (6) | Kommentare als RSSRSS

Im November 2009 haben Peter Bucher und ich uns im Rahmen unseres monatlichen Streitgesprächs mit der Frage Wie viel Sinn machen Unittests? beschäftigt. Meine damals noch sehr skeptische Haltung ist inzwischen einer regelrechten Begeisterung für Unittests gewichen.

Auch Kirchen haben Kragsteine – zu dieser Einsicht hat mir Ralf Westphal verholfen, wodurch meine mentale Barriere in Bezug auf Unittests gelöst war und ich begonnen habe, mich tiefer mit der Materie zu beschäftigen. Erstes Ergebnis war mein Vorschlag, wie ein Ordnungssystem für Unittests aussehen könnte.

Seither ist nun ein Vierteljahr vergangen, in welchem ich mich viel mit dem tatsächlichen Schreiben von Unittests befasst habe. Auch Themen wie Stubs und Mocks wie auch der innere Aufbau von Tests an Hand des Arrange-Act-Assert-Musters (AAA) standen auf der Tagesordnung.

Dabei hat mir vor allem der Austausch mit anderen Entwicklern geholfen, doch auch das Buch Pragmatic Unit Testing konnte mir diverse Fragen und Unklarheiten beantworten, weshalb ich dieses Buch jedem an der Thematik Interessierten nur empfehlen kann.

Heute hat mich nun Roberto Bez mit einer interessanten Frage konfrontiert: Wenn ein einzelner Unittest nur einen einzelnen Aspekt testen soll, wie wird dann gesichert, dass die Vorbedingungen ihrerseits nicht fehlschlagen?

Unser Diskussiongegenstand war ein Unittest, der einen Datensatz in einer Datenbank löschen soll – dazu aber aus ebendieser zunächst gelesen werden muss. Dass das eigentliche Löschen in die Assert-Phase fällt, liegt auf der Hand – doch was, wenn bereits das Lesen fehlschlägt?

Meines Erachtens fällt das Lesen noch in die Arrange-Phase des Unittests, denn es dient der notwendigen Vorbereitung für den eigentlichen Test: Schließlich soll der Unittest das Löschen des Datensatzes testen – und nur das Löschen!

Alle Vorbedingungen, die erfüllt sein müssen, dürfen zwar ebenfalls nicht fehlschlagen, gehören aber nicht direkt zu dem getesteten Aspekt:

[TestFixture]
public class WhenDeleteIsCalled
{
    [Test]
    public void WithAnExistentRecord_TheRecordIsDeleted()
    {
        // Arrange.
        var input = this._repository.ReadRecord(this._id);
        var expected = this._repository.ReadRecords().Count - 1;
        // Act.
        this._repository.Delete(input);
        var actual = this._repository.ReadRecords().Count;
        // Assert.
        Assert.That(actual, Is.EqualTo(expected));
    }
}

Was also, wenn bereits das Lesen des Datensatzes fehlschlägt?

Naheligend wäre, auch das Lesen in die Assert-Phase zu ziehen, doch dies würde eine Vermischung von Vorbereitung und tatsächlicher Durchführung bedeuten. Ruft man sich zudem in Erinnerung, dass ein einzelner Unittest nur einen einzelnen Aspekt testen soll, wird klar, dass dies keine Lösung darstellt.

Um einen besseren Ansatz zu finden, muss man sich zunächst zwei Fakten vor Augen führen:

  • Ein fehlgeschlagener Unittest liefert nur in den seltensten Fällen auf den ersten Blick den Grund, warum etwas fehlgeschlagen ist – in der Regel steht nur die Information zur Verfügung, dass etwas fehlgeschlagen ist, so dass eine weitere Analyse erforderlich ist.
  • Neben dem exemplarisch genannten Unittest existieren weitere Unittests in dem Projekt – unter anderem auch solche, die das Lesen von Datensätzen testen.

Tritt also bereits beim Lesen des Datensatzes ein Problem auf, so schlägt nicht nur der Unittest fehl, der das Löschen abdeckt, sondern auch jener, der bereits das Lesen testet. Welcher von beiden in der zeitlichen Reihenfolge dabei zuerst ausgeführt wird, darf keine Rolle spielen, da Unittests per Definition unabhängig voneinander gestaltet werden müssen.

Nun stellt sich also die Frage – welcher Unittest wird zuerst begutachtet? Wird zuerst der Unittest analysiert, der das Lesen abdeckt, wird der Fehler direkt gefunden und behoben. Im nächsten Durchlauf wird dann auch der Unittest, der das Löschen abdeckt, erfolgreich durchgeführt.

Wird hingegen zuerst der Unittest analysiert, der das Löschen abdeckt, so stößt man während der Analyse auf den Punkt, dass das Problem eben nicht in der Act- oder Assert-, sondern bereits in der Arrange-Phase auftritt. An dieser Stelle wird klar, dass es einen anderen Test geben muss, der diese Funktionalität abdeckt, so dass der Fehler nun dort gesucht werden kann.

Es spricht im Hinblick auf die Fehlersuche also nichts dagegen, Funktionalität als Vorbedingung für andere Funktionalität zu nutzen – allerdings liegt ebenfalls auf der Hand, dass ein solches Vorgehen problematisch sein kann. Dann nämlich, wenn auf diese Art gegenseitige Abhängigkeiten von Funktionalitäten entstehen.

Wenn sich zwei Bereiche gegenseitig bedingen, kann es aufwändig werden, den eigentlichen Fehler zu finden, ohne sich ständig im Kreis zu drehen. Einen äußerst interessanten Ansatz, wie man diesem Problem begegnen kann, hat Ralf Westphal in Zustand als Abhängigkeit beschrieben.

.NET Professionals im Profil: Neno Loje

Montag, 15. März 2010, 09:37 Uhr
Permalink | Kommentare (0) | Kommentare als RSSRSS

Neno Loje ist Technologieberater und Wissensvermittler für den Softwareentwicklungsprozess sowie erster Microsoft Most Valuable Professional für Visual Studio Team System in Europa. Als zertifizierter TFS-Experte unterstützt er Firmen bei der Einführung vom Team Foundation Server - von der Entscheidung über die Migration bis zur individuellen Anpassung. Sie erreichen ihn über sein Blog.

Golo Roden: Neno, wie bist Du zur Softwareentwicklung gekommen? Wie und wann hast Du angefangen?

Neno Loje: Das ist eine gute Frage. Seitdem ich denken kann, wusste ich, dass ich mal was "mit Computern" machen möchte, und die meisten denken ja auch heute noch genau das: Er macht etwas "mit Computern". Ich glaube, ich war damals sogar so klein, dass ich noch gar nicht wusste, was ein Computer ist, doch das sollte sich zuerst dank eines C64 und später dann in der Schule ändern. Mit 12 oder 13 Jahren gab es den ersten freiwilligen Computerkurs in der Schule, ein Jahr später den ersten Programmierkurs, mit der Programmiersprache Profan. Als ich das Gefühl hatte, alle Möglichkeiten eben dieser ausgereizt zu haben, folgten Visual Basic 4 in 16 und 32 Bit, Visual Basic 5 und 6.

Als ich mich nach dem Abitur auf die Suche nach einer Stelle als Programmierer, so hieß es auf "Altdeutsch", bewarb, war ich schon Autor in der basicpro – doch die Anforderungen der Projekte, die ich bis zum damaligen Zeitpunkt umgesetzt hatte, kamen ausschließlich von mir selbst. Eine tolle Zeit.

Nach ein paar Wochen im Job als Softwareentwickler auf Vollzeit kamen mir aber schon Zweifel: Will ich den Rest meines Lebens damit verbringen, Softwarebugs in altem, schmierigen Code zu beheben? Glücklicherweise durfte ich bei zahlreichen Projekten auf einer recht grünen Wiese anfangen, sodass ich diesen Gedanken nicht weiterzuspinnen brauchte.

Golo Roden: Du wurdest von Microsoft als erster MVP für Team Foundation Server in Europa ausgezeichnet. Wie kam es dazu, dass Du Dich speziell mit dem Team Foundation Server beschäftigt hast?

Neno Loje: Die Geschichte vom Team Foundation Server begann vor etlichen Jahren in den USA. Microsoft lieferte Windows als Anwendungsplattform sowie Visual Studio - und damals noch Visual Basic - an die Softwareentwickler und Softwarehäuser aus, damit diese sinnvolle Anwendungen für Windows schreiben konnten. Und obwohl Microsoft beide Produkte ständig weiter verbesserte, kam von den Kunden die Rückmeldung, dass nicht Visual Studio - sprich der Compiler, Debugger oder die Frameworks - schuld daran waren, dass es zum Teil sehr schwierig war, die Anforderungen umzusetzen, sondern vielmehr lag es an anderen Faktoren, wie der Zusammenarbeit im Team, der eingesetzten, häufig produktionsähnlichen Entwicklungsprozesse und mangelnder Qualitätssicherung, warum Projekte nicht so erfolgreich, pünktlich und kosteneffizient abgelaufen sind, wie man es sich gewünscht hatte.

Microsofts Antwort war ein Produkt, welches alle an der Softwareentwicklung beteiligten Menschen umschließt: Projektleiter, Architekten, Entwickler, Tester, ... Die ersten, die das neue Produkt zu Gesicht bekommen sollten, waren amerikanische Firmen, schließlich waren diese aus Microsofts Sicht einfach näher dran. Irgendwann kam eine Roadshow, die auch in München am Flughafen kurz halt macht. Da ist es dann passiert. Mir wurde klar: Ja, dieser Ansatz ist in der Lage, einigen Projekten wirklich zu helfen - und keine neue Programmiersprache der Welt vermag es, eben diese Probleme zu lösen.

Dass ich dann in Europa als erster MVP ausgewählt wurde, zur Kategorie "Team System" zu wechseln - ich war damals C# MVP - und mit der Produktgruppe zusammenzuarbeiten, war eine Abfolge von Zufällen beziehungsweise ich war zur richtigen Zeit am richtigen Ort.

Golo Roden: Für wie wichtig erachtest Du die Beschäftigung mit Infrastrukturthemen wie Versionsverwaltung, Buildserver, ... für einen Anfänger?

Neno Loje: Enorm wichtig! Und ich glaube, für die Anfänger wird das auch zunehmend normaler. Wir kommen noch aus einer Zeit, wo strukturierte Fehlerbehandlung von einigen Visionären in dunklen Kellerräumen gepredigt wurde. Heute ist das völlig normal.

Auch denken wir viel zu häufig nicht als Team, da der Programmierer früher schließlich alles selbst gemacht hat - und auch die volle Herrschaft über den Rechner hatte. Heute gibt es kleine und große Teams, und selbst in 1-Mann-Projekten arbeitet man zumindest mit Leuten zusammen, welche die Anforderungen diktieren oder die Ergebnisse später prüfen und / oder verteilen. Der moderne Softwareingenieur, der seinen Beruf als eine professionelle Entwicklungsdisziplin sieht, die durchaus noch eher kreative Züge hat, für den sind viele Techniken wie Unit Tests zur technischen Überprüfung seines Werks normales Handwerkszeug. Statt einem Schraubenzieher nutzt er Versionsverwaltungssysteme und Buildserver, beziehungsweise optimalerweise ein System, welches den gesamten Application Lifecycle abdeckt, wie den TFS.

Golo Roden: Ein wesentliches Merkmal der IT ist, dass man beständig mit neuen Entwicklungen konfrontriert wird, und diesen folgen muss. Woher nimmst Du die Motivation, Dich quasi jeden Tag weiterzubilden und mit Neuem zu beschäftigen?

Neno Loje: Mein Beruf ist es ja Wissen zu vermitteln, insofern muss ich, noch etwas eher als ich es früher als Software-Entwickler tun musste, mich ständig und überall fortbilden. Ich habe zwei konkrete Alltagsbeispiele, die ab und zu in ähnlicher Form wiederkehren, die dafür sorgen, dass meine Motivation nicht erlischt:

Ich hielt einen Vortrag, den ich schon unzählige Male gehalten hatte, stets bemüht, ihn dennoch ein wenig mit Begeisterung zu vermitteln. Danach kam ein Entwickler zu mir und sagte: "Ich habe monatelang versucht, die Zusammenhänge zwischen den ganzen Technologien zu verstehen. Nach diesem Tag mit Ihnen ist mir alles klar. Danke dafür!". Er strahle über das ganze Gesicht und schien sich sehr zu freuen.

Das zweite Beispiel basiert auf klassischer Softwareentwicklung: Ich hatte nach einigen Diskussionen und Bemühungen meinerseits unseren Großkunden überredet, einen neuen Weg zur Softwareverteilung auszuprobieren. Die dafür zuständige Abteilung war permanent ausgebucht und brauchte verhältnismäßig lange für die Überprüfung und Bereitstellung von Updates. Nach einigen Wochen kam einer der skeptischsten Mitarbeiter der IT-Abteilung zu mir und sagte: "Seitdem wir die Software nach der neuen Methoden verteilen können, haben wir statt mehrerer Tage nur wenige Stunden Arbeit und können häufiger pünktlich nach Hause."

Golo Roden: Wenn sich ein Anfänger heute mit dem Thema Softwareentwicklung befassen will – welche Voraussetzungen sollte er Deiner Meinung nach dafür mitbringen, und was siehst Du als No-Go an?

Neno Loje: Ich glaube, aus den beiden eben genannten Beispielen sieht man ganz gut, dass ein Stück "Weltverbesserer" gerne in einem Entwickler stecken darf. Er sollte nicht bloß umsetzen, was ihm vorgesetzt wird, sondern in der Lage sein, das Kundenproblem zu analysieren und eine optimale technische Lösung anzubieten, statt sich mit traditionell gewachsenen und häufig veralteten Strukturen und Abläufen zufrieden zu geben.

Aber letztlich soll Software immer dem Kunden dienen. Deshalb ist es absolut notwendig, dass Softwareentwickler kundenorientiert denken und arbeiten. Ein ausgeprägtes Qualitätsbewusstsein und zwangsläufig permanente Lernbereitschaft tun den Rest. Auch wird kaum mehr Software alleine im Stillen Kämmerlein entwickelt: Teamfähigkeit wird also immer mehr erwartet und gefordert.

Golo Roden: Bei der Vielzahl an Technologien, die es heute gibt: Womit sollte ein Anfänger heutzutage anzufangen?

Neno Loje: Technologien kommen und gehen. Häufig entscheidet der erste Arbeitgeber beziehungsweise die Uni, was man sich ansieht. Die konkrete Sprache oder Technologie ist auch nicht entscheidend. Die Fähigkeit zu wissen, wie man sie erlernt ist wichtig. Bei mir klappt das mit dem Lernen - und vor allem dem Behalten - nur, wenn mich die Materie auch irgendwie interessiert. Es ist deutlich einfacher, eine Datenbankvorlesung an der Uni zu bestehen, wenn man vorher schon in richtigen Projekten Datenbanken, ihren Nutzen und ihre Eigenheiten kennengelernt hat. Sonst wäre das so, als würde man in einem theoretischen Technologievortrag über die Funktionsweise einer Weltraumfähre sitzen ...

Golo Roden: Welchen Rat würdest Du einem Anfänger abschließend mit auf den Weg geben?

Neno Loje: Es ist eine tolle Zeit, um Softwareentwickler zu werden! Alles fällt leichter, wenn man die Tätigkeiten wirklich gerne mag, wenn man sich wie ein kleines Kind freut, wenn man sieht, wie sich das Projekt von Version zu Version weiterentwickelt und wächst, und die automatischen Tests alle grün leuchten. Will man gut gerüstet sein als Softwareentwickler für das nächste Jahrzehnt, sollte man nicht nur programmieren können, sondern seinen Job "professionell" ausführen. Dazu gehören insbesondere Prinzipien, Werte und Vorgehensweisen. Als Startadressen können hier beispielsweise Was macht einen professionellen Software-Entwickler aus? von Stefan Lieser und das Schulungs- und Zertifizierungsprogramm: Professional Scrum Developer Program dienen.

Welche Bedeutung wohnt einer Schnittstelle inne?

Donnerstag, 11. März 2010, 20:32 Uhr
Permalink | Kommentare (4) | Kommentare als RSSRSS

Der russische Schriftsteller Isaac Asimov hat neben zahlreichen Werken, die primär der Science-Fiction angehören, auch zahlreiche Kurzgeschichten geschrieben: Dazu gehört unter anderem eine kriminalistische Kurzgeschichte mit dem Titel What’s in a Name?, in der es um die Aufklärung eines Mordes geht.

Den Titel What’s in a Name? könnte man im Deutschen sinngemäß am Besten mit der Frage wiedergeben, welche Bedeutung einem Namen innewohnt – und ob einem Namen überhaupt eine Bedeutung innewohnt.

Angelehnt an diese Semantik möchte ich heute die Frage stellen, welche Bedeutung einer Schnittstelle innewohnt. Ausgangslage für diese Frage ist eine Diskussion, die ich vor einigen Tagen mit Peter Bucher geführt habe.

Thema dieser Diskussion war ursächlich, inwiefern LightCore benannte Registrierungen von Typen unterstützen soll. Nahezu allen Microkerneln liegt das Prinzip zu Grunde, einen konkreten Typ auf eine abstrakte Schnittstelle zu registrieren, über die er dann instanziiert werden kann.

Zur Instanziierung bieten die meisten Microkernel zwei Varianten an: Entweder wird die einzig vorhandene Registrierung genutzt, um eine Instanz des konkreten Typs zu erzeugen, oder – im Fall mehrerer vorliegender Registrierung – wird von jedem Typ eine Instanz erzeugt und eine entsprechende Liste zurückgegeben.

In LightCore geschieht dies mit Hilfe der Methoden Resolve<T> und ResolveAll<T>, die eine einzelne Instanz des Typs T oder ein IEnumerable<T> zurückgeben – je nachdem, wie viele Registrierungen für den angeforderten Kontrakt vorliegen.

So weit, so gut. Nun bieten die meisten Microkernel – so derzeit auch LightCore – allerdings zusätzlich noch die Möglichkeit, im Falle mehrerer Registrierungen diese an Hand von Namen zu unterscheiden. Auf diese Art können beispielsweise mehrere Addins auf die Schnittstelle IAddIn registriert werden, dennoch kann die verwendende Anwendung gezielt die Instanz eines dedizierten Addins anfragen.

Ich persönlich konnte mich mit diesem Vorgehen noch nie anfreunden – und auch heute noch halte ich es für falsch. In unserer Diskussion ging es nun darum, die Beweggründe für meine Abneigung gegenüber benannten Registrierungen herauszufinden.

Peter hat mir als konkrete Diskussionsgrundlage die Schnittstelle IController aus dem Namensraum System.Web.Mvc in ASP.NET MVC genannt, die von jedem Controller implementiert wird. Wird nun in einer ASP.NET MVC-basierten Anwendung auf einen Microkernel gesetzt, um die einzelnen Controller zu instanziieren, so ist klar, dass alle Controller auf die gleiche Schnittstelle registriert werden.

Sobald eine Anfrage an eine solche Webanwendung gestellt wird, besteht die Aufgabe nun darin, den richtigen Controller zu instanziieren: Die Auswahl allein an Hand der Schnittstelle zu treffen, funktioniert nicht – hierüber lassen sich die einzelnen Controller nicht unterscheiden. Ein Aufruf von ResolveAll<IController> nützt aber auch nichts, da auf diese Art alle Controller instanziiert werden würden.

Die vermeintliche Lösung liegt nun darin, jede Registrierung eines Controllers mit einem Namen auszustatten, und vom Microkernel sodann eine benannte Instanz anzufordern. Dass dieses Vorgehen funktioniert, steht außer Frage – doch ist es sinnvoll? Ich meine, nein. Denn es widerspricht dem Sinn einer Schnittstelle.

Die Idee einer Schnittstelle ist, verschiedene Implementierungen eines Aspekts zu ermöglichen, die potenziell gegeneinander austauschbar sind: Eine Schnittstelle dient also dazu, nach außen gleiches Verhalten anzubieten, aber mit verschiedenen Implementierungen – wobei die konkret gewählte Implementierung für den Verwender transparent ist.

In anderen Worten: Angenommen, es liegen eine Schnittstelle IDataSource sowie zwei Implementierungen, SqlServerDataSource und XmlDataSource, vor. Dann ist es für die Anwendung, die auf IDataSource aufsetzt, irrelevant, welche konkrete Implementierung konfiguriert wurde – denn das Verhalten der Implementierungen nach außen ist gleich, nur die Implementierung unterscheidet sich.

Dieses Muster dient als Basis für austauschbare Komponenten: Austauschbarkeit gelingt nur, wenn neben der Syntax auch die Semantik gleich bleibt – lediglich die Umsetzung darf schwanken.

Auf das Beispiel der Controller in ASP.NET MVC bezogen trifft dieses Muster aber nicht zu – denn die Syntax der einzelnen Controller ist zwar identisch, die Semantik aber nicht. Denn beispielsweise ein HomeController verhält sich schlicht und ergreifend anders als ein DownloadController, obwohl beide durchaus die Schnittstelle IController implementieren.

Der Sinn der beiden Controller beziehungsweise ihre Existenzberechtigung sind verschiedener Natur, sie erfüllen verschiedene Aufgaben und sind auch nicht gegen einander austauschbar. Zwei Implementierungen eines HomeControllers wären austauschbar – aber nicht ein Home- und ein DownloadController.

Bei den Controllern gilt also das genaue Gegenteil von oben genanntem Paradigma: Hier wird nicht gleiches Verhalten auf unterschiedliche Art implementiert, statt dessen wird unterschiedliches Verhalten auf die gleiche Art implementiert! Das ist ein gravierender Unterschied in Bezug auf die Semantik. Dass die Syntax bei all dem gleich bleibt, ist lediglich ein Nebeneffekt – der aber für die Komponentenorientierung an dieser Stelle nichts zur Sache tut.

Kurzum: Es ist schlicht und ergreifend falsch, zwei unterschiedliche Typen von Controllern auf die gleiche Schnittstelle zu registrieren. Wenn man diese Tatsache erst einmal akzeptiert hat, wird auch schnell deutlich, was genau sich an benannten Registrierungen falsch anfühlt: Sie sind keine Lösung, sie sind lediglich ein Workaround für ein tiefer liegendes Problem.

Die Frage lautet nun – was wäre eine korrekte Lösung, die sich nicht im Nachhinein als halbherziger Workaround entpuppt?

Eine Möglichkeit wäre, zwischen die Schnittstelle IController und die jeweils konkreten Implementierungen eine weitere Schnittstelle einzuziehen – wie IHomeController oder IDownloadController. Diese wären geeignet, eine semantische Unterscheidung zwischen den einzelnen Controllern zu begründen, würden benannte Registrierungen überflüssig machen, bei all dem aber die gemeinsame Syntax auf Grund der gemeinsamen Basis IController erhalten.

Abgesehen von der dadurch fehlenden Notwendigkeit für benannte Registrierungen hätte diese Variante zum anderen den Vorteil der echten Austauschbarkeit: Da nun auf eine semantisch korrekt definierte Schnittstelle registriert wird, könnten auch verschiedene Implementierungen beispielsweise des HomeControllers problemlos gegeneinander ausgetauscht werden.

Letztlich wage ich die Behauptung aufzustellen, dass bereits die Existenz benannter Registrierungen in Microkerneln ein Fehler ist – denn entweder ist es tatsächlich sinnvoll, verschiedene gleichwertige konkrete Typen auf die gleiche Schnittstelle zu registrieren – dann sollten diese aber auch als gleichwertig behandelt werden – oder eben nicht. Ein Mischmasch aus beidem ergibt keinen Sinn.

Sofern die Einführung zwischengeschalteter Schnittstellen nicht möglich ist, gibt es darüber hinaus immer noch die Möglichkeit, einzelne Typen mit einem entsprechenden Attribut oder einer Markerschnittstelle auszustatten – um diese dann nach dem Laden durch den Microkernel eindeutig identifizieren zu können.

Abschließend lautet mein Plädoyer daher, dass die Fähigkeit, benannte Registrierungen durchzuführen, kein Feature eines Microkernels ist, sondern ein Bug – der enthalten ist, weil alle anderen es ebenso machen. Wünschenswert wäre aus meiner Sicht, die Unterstützung für benannte Registrierungen zu entfernen und statt dessen auf ein sauberes Verfahren zu wechseln – einige Ansätze hierfür habe ich genannt.

.NET UG Konstanz-Kreuzlingen: .NET 4 und Visual Studio 2010

Donnerstag, 11. März 2010, 01:38 Uhr
Permalink | Kommentare (0) | Kommentare als RSSRSS

Gestern Abend war ich bei der .NET Usergroup Konstanz-Kreuzlingen bereits zum zweiten Mal zu Gast – das erste Treffen in neuen Räumlichkeiten, das zudem von einem – zumindest teilweise – neuen Team organisiert wurde.

Als Thema des Abends hatten wir im Vorfeld

.NET 4 und Visual Studio 2010

ausgewählt- also das gleiche Thema wie bei meinem Besuch bei der .NET Usergroup Nordwestschweiz vor rund einer Woche. Auch gestern habe ich deshalb die aus meiner Sicht wichtigsten Aspekte herausgegriffen und in einem kompakten, aber detaillierten Überblick zusammengefasst:

  • Visual Studio 2010 als Editor
  • Visual Studio 2010 als Plattform
  • CLR 4.0
  • Visual C# 4.0
  • Visual F#
  • Parallelprogrammierung
  • Managed Extensibility Framework
  • Historical Debugging

Auch gestern bin ich neben der reinen Vorstellung dieser Aspekte auch auf potenzielle Nachteile eingegangen, insbesondere C# 4.0 bietet doch Anlass für einige Kritikpunkte.

Eingebettet in all dies habe ich auch zahlreiche Detailverbesserungen angesprochen – von Verbesserungen in der grafischen Oberfläche von Visual Studio bis hin zu verbesserten Best Practices.

Auch eine kurze Präsentation meines derzeit favorisierten Werkzeugs .NET Reflector Pro durfte zu Anfang nicht fehlen.

Im Anschluss an den rund 90-minütigen Vortrag gab es noch interessante Gespräche, so dass mir auch der gestrige Abend wieder sehr viel Spaß gemacht hat – und den Teilnehmern augenscheinlich ebenfalls.

Die Planung für die nächsten Treffen der .NET Usergroup Konstanz-Kreuzlingen ist zwar bereits in trockenen Tüchern, aber ich bin mir dennoch sehr sicher, dass es nicht mein letzter Besuch bei dieser Usergroup war.

Accuracy und Präzision

Dienstag, 9. März 2010, 17:37 Uhr
Permalink | Kommentare (2) | Kommentare als RSSRSS

Im August 2009 habe ich Accuracy als Charakteristik für einen guten Entwickler gefordert.

Im Wesentlichen habe ich mich bei der Definition des Begriffs von dem Computerspiel Quake III Arena leiten lassen, in dem Accuracy für eine überdurchschnittlich hohe Treffgenauigkeit steht. Auf dieser Basis und übertragen auf die Softwareentwicklung habe ich Accuracy damals als

Die Liebe für Details, und die Fähigkeit, auch auf jeden Punkt und jedes Komma zu achten – und nicht nur oberflächlich mal eben schnell über Code hinweg zu gehen.

definiert. Nachdem ich diese Begriffsdefinition inzwischen eine Weile verwende, ist mir bewusst geworden, dass Accuracy alleine nicht genügt, sondern noch ein weiteres Maß erforderlich ist, um die Arbeitsweise eines Entwickler bewerten zu können: Präzision.

Auch – zumindest die englischsprachige – Wikipedia trennt zwischen Accuracy und Präzision, wobei dort wiederum die Analogie zur Waffenkunde aufgegriffen wird:

  • Accuracy bezeichnet die Treffgenauigkeit, wie weit Treffer also vom gewünschten Ziel abweichen.
  • Präzision hingegen bezeichnet die Wiederholbarkeit, wie groß die Varianz der Treffer also ist.

Natürlich sind beide Werte unabhängig voneinander, denn sie lassen sich in jeder beliebigen Ausprägung miteinander kombinieren – weder erfordert eine hohe Accuracy eine hohe Präzision, noch gilt dies umgekehrt.

Was bedeuten diese beiden Begriffe nun übertragen auf die Softwareentwicklung?

Accuracy bedeutet für mich, dass ein Entwickler der idealen Form von Quellcode nahe kommt, das heißt, akkurat geschriebener Code zeichnet sich durch die Abwesenheit von Formfehlern aus. Als Merkmale für derartigen Code können beispielsweise

  • die korrekte Einrückung sowie der korrekte Gebrauch von Leerzeilen,
  • die korrekte Klammerung,
  • die korrekte Schreibweise von Bezeichnern
  • und korrekte Rechtschreibung und Grammatik in Kommentaren

dienen. Entwicklern, die in der Lage sind, solchen Code zu erzeugen, kann man daher durchaus eine hohe Treffgenaugkeit zugestehen.

Nun stellt sich zusätzlich die Frage nach der Präzision. Dass Fehler vorkommen, kann kaum verhindert werden – die Frage ist aber, wie breit gestreut die Varianz der Arten von Fehlern ist.

Wird beispielsweise korrekt eingerückt, korrekt geklammert und es finden sich lediglich ab und an Kommafehler in Kommentaren, so kann man durchaus eine hohe Präzision zugestehen. Umgekehrt gilt – wenn die Arten von Fehlern weit gestreut sind, verfügt ein Entwickler über eine nur niedrig ausgepägte Präzision – wobei er dennoch, wenn er nur wenige Fehler macht, eine hohe Accuracy aufweisen kann.

Ich glaube, dass diese Trennung in Accuracy und Präzision wichtig ist, um die Qualität von Code und damit auch die Fähigkeiten des entsprechenden Entwicklers angemessen beurteilen zu können.

Natürlich beziehen sich beide Werte nicht nur auf die formale Gestalt von Code, sondern beispielsweise auch auf das Durchdenken dessen, was eigentlich codiert wird – auch hier können Ergebnisse mit beiden Maßen gemeinsam besser beurteilt werden als nur mit einem.

Review von Geschichten vom Scrum

Mittwoch, 3. März 2010, 18:55 Uhr
Permalink | Kommentare (0) | Kommentare als RSSRSS

Es war einmal … mit diesen Worten beginnen üblicherweise Märchen – und genau darum handelt es sich bei dem Buch Geschichten vom Scrum von Holger Koschek, das am 1. November 2009 im dpunkt Verlag erschienen ist.

In einem weit entfernten Königreich lebten die Menschen mit sich und der Natur im Einklang. Sie betrieben Ackerbau und Viehzucht, waren in der Lage, sich selbst zu versorgen – und im Grunde wären sie glücklich und zufrieden gewesen, wenn ihre Lebensgrundlage nicht immer wieder Drachen zum Opfer gefallen wäre.

Zwar gab es Drachenfallen, doch die Drachen lernten schnell, so dass es ihnen immer wieder gelang, sich aus den Fallen zu befreien. Im Grunde herrschte ein beständiges Wettrüsten zwischen den Drachenbauern und den Drachen.

Eines Tages hatte der König genug von diesem ewigen Wettlauf und beschloss, ein Team zusammenzustellen, das die ultimative Drachenfalle bauen sollte: Da jedoch alle Drachenfallenbaumeister unentbehrlich waren, blieb dem König letztlich nichts anderes übrig, als ein zusammengewürfeltes Team zu akzeptieren.

Die ultimative Drachenfalle sollte gebaut werden von dem Aschenputtel, der Hexe, dem Großväterchen, dem Ritter und dem königlichen Schlossgespenst – unter Anführung des Prinzen.

Da nicht alle Anforderungen an eine solche Falle bekannt waren, wurde beschlossen, die Falle agil zu entwickeln – nach Scrum.

Unter diesen Voraussetzungen nimmt das Märchen seinen Lauf, und Holger Koschek schildert in einer wunderschönen Erzählweise, wie die einzelnen Personen nach und nach zu einem Team zusammenwachsen, auf welche Probleme sie dabei stoßen, wie sie Scrum umsetzen, …

Als Leser begleitet man das Team auf dieser Reise und fiebert mit, ob es ihnen gelingen wird, die hoch gesteckten Ziele zu erreichen, und welche Windungen die Geschichte dabei nehmen wird.

Insgesamt handelt es sich bei Geschichten vom Scrum um ein sehr ungewöhnliches, aber äußerst gelungenes Buch. Es gehört damit in die Reihe der wenigen Fachbücher, in denen nicht nur die fachliche Materie vermittelt wird, sondern deren Lektüre auch schlichtweg Spaß macht.

Wer sich für Scrum im Speziellen und agile Methoden im Allgemeinen interessiert, sollte auf jeden Fall einen längeren Blick in dieses Buch werfen.

.NET Usergroup Nordwestschweiz: .NET 4 und Visual Studio 2010

Dienstag, 2. März 2010, 00:01 Uhr
Permalink | Kommentare (0) | Kommentare als RSSRSS

Am heutigen Abend fand das initiale Treffen der .NET Usergroup Nordwestschweiz in Basel in den Räumen der YooApplications AG statt – und ich hatte die Ehre, von den beiden Veranstaltern Laurin Stoll und David Richter als Referent eingeladen worden zu sein.

Als Thema des Abends hatten wir im Vorfeld

.NET 4 und Visual Studio 2010

ausgewählt. Auf Grund der Vielzahl an Neuerungen habe ich deshalb die aus meiner Sicht wichtigsten Aspekte herausgegriffen und in einem kompakten, aber detaillierten Überblick zusammengefasst:

  • Visual Studio 2010 als Editor
  • Visual Studio 2010 als Plattform
  • CLR 4.0
  • Visual C# 4.0
  • Visual F#
  • Parallelprogrammierung
  • Managed Extensibility Framework
  • Historical Debugging
  • Code Contracts

Neben der reinen Vorstellung bin ich dabei auch auf potenzielle Nachteile eingegangen, quasi ein wenig abseits der Hochglanzfolien. Außerdem habe ich viele Kleinigkeiten und Detailverbesserungen angesprochen – von Verbesserungen in der grafischen Oberfläche von Visual Studio bis hin zu verbesserten Best Practices.

Auch eine kurze Präsentation meines derzeit favorisierten Werkzeugs .NET Reflector Pro durfte zu Anfang nicht fehlen.

Im Anschluss an den rund 90-minütigen Vortrag haben sich mit den Teilnehmern noch zahlreiche interessante Gespräche ergeben, so dass mir der Abend ausgesprochen viel Spaß gemacht hat, und ich mich schon sehr auf das nächste Mal freue.

Danken möchte ich den beiden Veranstaltern dabei nicht nur für die Einladung, sondern auch für die viele Mühe, die sie sich gemacht haben: Man merkt ihnen an, dass sie sehr motiviert sind und ihnen das Thema Community sehr am Herzen liegt.

Insgesamt bleibt mir damit nur noch, der .NET Usergroup Nordwestschweiz viel Erfolg für die Zukunft zu wünschen!

Abstraktion

Montag, 1. März 2010, 09:37 Uhr
Permalink | Kommentare (1) | 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. März 2010, ist es nun wieder so weit, und unser Thema für diesen Monat lautet:

Abstraktion

So wohl Peter wie auch ich haben uns unabhängig voneinander im Vorfeld unsere Gedanken gemacht, wie wir diesem Thema gegenüberstehen. Außerdem nimmt diesen Monat auch Roberto Bez an unserem Streitgespräch teil.

Peters und Robertos Kommentare finden sich zeitgleich in den entsprechenden Blogs, folgend nun mein Kommentar zu diesem Thema:

Das Selbstverständnis der Informatik, anderen Fachgebieten eine Hilfswissenschaft zu sein, impliziert die Anwendung von Abstraktion bereits: Letztlich werden deren Objekte, Daten und Prozesse nämlich in geeinete informationstechnische Strukturen abstrahiert.

Abstraktion an sich wird dabei beispielsweise von Wikipedia als

induktiven Denkprozess des Weglassens von Einzelheiten und des Überführens auf etwas Allgemeineres oder Einfacheres

definiert. Es geht also letztlich darum, gegebene Probleme besser lösen zu können, indem für die Lösung unnötige Komplexität verringert wird – in der Regel, indem diese durch eine einfachere Darstellung verborgen wird.

Bereits im schulischen Mathematikunterricht findet Abstraktion in dieser Form statt, wenn beispielsweise die Lösung von Textaufgaben gefordert wird, die die eigentliche mathematische und abstrakte Aufgabe in einer mehr oder weniger anschaulichen Beschreibung verpacken.

Ohne Abstraktion wäre die Lösung mancher Aufgaben nicht nur bedeutend schwieriger und komplexer oder gar unmöglich, ohne sie wäre auch die Kommunikation mit anderen Disziplinen bedeutend erschwert.

Für einen Informatiker spielt es in der Regel nämlich zunächst keine Rolle, ob zum Beispiel Gepäckstücke an einem Flughafen oder aber Reagenzgläser in einem medizinischen Labor automatisiert befördert und verarbeitet werden sollen – beide Aufgaben können zu einem Logistikproblem abstrahiert werden, das mit entsprechenden Verfahren lösbar oder zumindest annäherbar ist.

Abstraktion nimmt also eine essenzielle Rolle in der Informatik ein – doch Abstraktion ist nicht per se als positiv zu bewerten. Abstraktionen verbergen nämlich nicht nur unnötige Komplexität, sondern neigen bewusst oder unbewusst auch dazu, Details zu verbergen, die für die Lösungsfindung allerdings relevant sind.

Joel Spolsky bezeichnet diesen Umstand als The Law of Leaky Abstractions. Er fasst seine Erkenntnisse bezüglich Abstraktion in dem Satz

All non-trivial abstractions, to some degree, are leaky.

zusammen, was letztlich so viel bedeutet, dass Abstraktionen nicht nur dabei helfen, Probleme zu lösen, sondern ihrerseits lecken und neue Probleme verursachen, weil die notwendigen Details nicht mehr bekannt sind – ironischerweise desto eher, je stärker abstrahiert wird.

Beispiele für Abstraktionen und die dazugehörigen Lecks sind – wie in der gesamten übrigen Entwicklung von Software – auch in .NET ohne weiteres zu finden:

So abstrahiert die Garbage Collection von .NET die Notwendigkeit, Speicher selbst zu verwalten – dies geschieht implizit. Doch sie löst nicht alle Probleme, zudem verursacht sie neue Probleme, derer sich Entwickler zumindest in kleinen Anwendungen nicht bewusst werden.

Denn nach wie vor haben Entwickler mit hängenden Referenzen zu kämpfen, die beispielsweise dadurch entstehen, dass Ereignisse nicht auch wieder abgemeldet werden.

Ebenfalls problematisch ist das vermeintlich günstige Erzeugen neuer Objekte im Verlass darauf, dass die Garbage Collection die erzeugten Objekte wieder entfernen wird – Speicherverschwendung und damit Performanceprobleme auf Grund falsch angewandter Instanziierung sind eher die Regel denn die Ausnahme.

Auch der grafische, in Visual Studio enthaltene Designer für WPF stellt eine solche Abstraktion dar: Er entledigt den Entwickler von der lästigen Aufgabe, WPF-Code per Hand schreiben zu müssen – kann aber natürlich nie die wahre Absicht des Entwicklers erahnen und muss daher gewisse Kompromisse zwischen Generalität und vernünftigem, das heißt performanten und wohlstrukturierten Code eingehen.

Last but not least stellt selbst der Konkatenationsoperator für Zeichenketten, dargestellt durch ein vermeintlich harmloses +, eine Abstraktion über die Vorgänge dar, die im Speicher stattfinden, um zwei Zeichenketten miteinander zu einer neuen zu verbinden. Die Speicher- und Performanceimplikationen dürften hinlänglich bekannt sein.

All diese Probleme werden durch Abstraktionen verursacht, die ursprünglich angetreten sind, bestehende Probleme zu lösen. Natürlich kann man auf Abstraktionen auch nicht verzichten – zu essenziell sind sie für die überschaubare Lösbarkeit eines gegebenen Problems.

So erspart beispielsweise ASP.NET als Abstraktion über das HTTP-Protokoll – das seinerseits übrigens nichts anderes als eine weitere Abstraktion über den Datenverkehr im Netzwerk darstellt – dem Entwickler eine ganze Menge an Aufgaben, die in der Regel ohnehin immer nach dem gleichen Schema erledigt werden.

Doch – wer nicht weiß, was bei ASP.NET unter der Haube geschieht, neigt schnell dazu, die vorgegebenen Pfade als das Nonplusultra anzusehen und dabei auftretende Probleme zu ignorieren, schlichtweg, weil sie auf Grund der Abstraktion nicht wahrgenommen werden.

Das Paradoxe an dieser Situation ist, dass es – je mehr und mächtigere Abstraktionen verfügbar sind, desto schwieriger wird es, zu wissen, was man eigentlich macht. Damit schließe ich mich nahtlos Joel Spolsky an, wenn er in seinem eingangs erwähnten Artikel schreibt:

[…] all this means that paradoxically, even as we have higher and higher level programming tools with better and better abstractions, becoming a proficient programmer is getting harder and harder.

Schlussendlich lässt sich zusammenfassen, dass es für hervorragende Entwickler nicht genügt, sich mit ihrer jeweils präferierten Technologie auseinanderzusetzen, sondern dass sie sich – um wirklich verstehen und nachvollziehen zu können, was geschieht – auch mit den zu Grunde liegenden Technologien auseinandersetzen müssen.

Das bedeutet, dass auch – oder erst recht – in Zeiten von Hochsprachen, Frameworks, Designern und Assistenten die Berechtigung oder gar die Pflicht gegeben ist, sich mit den Grundlagen zu beschäftigen.

Dass dies nicht zuletzt auch eine große Herausforderung für die Ausbildung darstellt, sei es in Firmen, an Hochschulen oder in sonstigen Formen, liegt auf der Hand.

Werden die Grundlagen nämlich zu Gunsten schnellerer und vermeintlich besserer Lernerfolge systematisch ignoriert, bedeutet das, Oberflächlichkeit zu fördern und verstehendes Wissen unter Anwendung der Root Cause Analysis, die unter anderem auch von der Clean Code Developer-Initiative wertgeschätzt wird, zu missachten.