Featureitis

Man muss nicht gleich Softwareentwickler sein, um ein gutes Anwendungsprogramm erkennen zu können. Doch aus eigener Erfahrung ist es mir oft passiert, dass Programme, die zu Beginn vielversprechend und innovativ waren, ab einer ‚gewissen‘ Nutzerzahl zu unhandlichen Boliden mutiert sind. Da ich diese Beobachtung nun schon einige Jahrzehnte regelmäßig aufs Neue mache, habe ich mich gefragt, woran das wohl liegen kann.

Das Phänomen, dass Softwareprogramme oder Lösungen im Allgemeinen mit Details überladen werden, hat Brooks in seinem Klassiker ‚The Mythical Man-Month‘ als Featuritis bezeichnet. Wenn man überlegt, dass die Erstausgabe des Buches im Jahr 1975 erschienen ist, kann man wohl von einem lange bekannten Problem sprechen. Das wohl bekannteste Beispiel für Featuritis ist das Betriebssystem Windows von Microsoft. Natürlich gibt es noch unzählige andere Beispiele für Verschlimmbesserungen.

Windowsnutzer, die bereits Windows XP kannten und dann mit dem tollen Nachfolger Vista konfrontiert wurden, um dann mit Windows 7 wieder besänftigt zu werden, um mit 8 und 8.1 beinahe einen Herzinfarkt erlitten zu haben, sich zu Beginn von Windows 10 wieder beruhigen. Jedenfalls für kurze Zeit, bis der Updatezwang für schnelle Ernüchterung sorgte. Von Windows 11 ganz zu schweigen. Zu Windows hieß es einmal, jede zweite Version ist Schrott, die sollte man überspringen. Nun ja, das stimmt seit Windows 7 schon lange nicht mehr. Für mich war Windows 10 dann der ausschlaggebende Punkt, vollständig auf Microsoft zu verzichten, und ich habe mir wie viele andere auch ein neues Betriebssystem zugelegt. Einige sind zu Apple gewechselt, und wer sich die teure Hardware nicht leisten kann oder will, hat wie ich auf ein Linuxsystem gesetzt. Hier zeigt sich, wie Uneinsichtigkeit schnell zum Verlust signifikante Marktanteile führt. Da Microsoft aus diesen Entwicklungen keine Konsequenzen zieht, scheint dem Unternehmen dieser Umstand weniger wichtig zu sein. Andere Unternehmen wiederum kann so etwas schnell an den Rand der Existenz bringen, und darüber hinaus.

Eine Motivation, immer mehr Funktionen in eine bestehende Anwendung zu bringen, ist der sogenannte Produktlebenszyklus, der durch die BCG Matrix in Abbildung 1 dargestellt wird.

Mit der Einführung ist noch nicht sicher, ob das Produkt vom Markt akzeptiert wird. Wenn es die Nutzer annehmen, steigt es schnell zum Star auf und erreicht seine maximale Marktposition als Cash Cow. Sobald die Sättigung überschritten wurde, degradiert es zum Ladenhüter. Soweit, so gut. Leider herrscht im Management überwiegend die Idee, dass, wenn kein Wachstum zum vorherigen Quartal mehr erzeugt wird, die Sättigung bereits überschritten wurde. So kommt es zu der sinnbefreiten Annahme, den Nutzern müsste jedes Jahr eine aktualisierte Version des Produktes aufgedrängt werden. Die Motivation zu kaufen gelingt natürlich nur, wenn eine dickgefüllte Featureliste an Neuerungen auf die Verpackung gedruckt werden kann.

Da sinnvoll konzipierte Funktionen sich aber nicht wie am Fließband aus dem Ärmel schütteln lassen, kommt auch jedes Mal gleich ein Redesign der grafischen Benutzeroberfläche als Gratis-Schmankerl mit dazu. Schließlich hat man dann das Gefühl, man habe etwas völlig Neues, weil man erst wieder eine Eingewöhnung braucht, um die neue Platzierung alt bekannter Funktionen zu entdecken. Es ist ja nicht so, dass das Redesign in der Benutzung Wege verkürzen würde und die Produktivität erhöht. Die Zusammenstellung der Eingabemasken und Schaltflächen erscheint jedes Mal willkürlich zusammengewürfelt.

Aber keine Sorge, ich will nicht zum Updateboykott aufrufen, sondern einmal darüber sprechen, wie man es besser machen kann. Denn eines sei gewiss: Dank künstlicher Intelligenz wird sich der Markt für Softwareprodukte in wenigen Jahren massiv verändern. Ich erwarte nicht, dass komplexe und spezialisierte Anwendungsprogramme in absehbarer Zeit durch KI-Algorithmen produziert werden. Allerdings erwarte ich, dass in diesen Anwendungsprogrammen genügend KI generierte schlechte Codesequenzen, die der Entwickler nicht versteht, in die Codebasis eingebracht werden, was zu unstabilen Anwendungen führen wird. Diese Überlegung ist für mich ein Grund, wieder über saubere, handgemachte, leitungsfähige und verlässliche Software nachzudenken, denn ich bin mir sicher, dass dafür immer ein Markt bestehen bleiben wird.

Ich möchte einfach keinen Internetbrowser, der zu einer Kommunikationszentrale mutiert ist und neben dem eigentlichen Anzeigen von Internetseiten noch Chat, E-Mail, Kryptobezahlmethoden und was weiß ich noch alles enthält. Ich möchte, dass mein Browser schnell startet, wenn ich irgendetwas klicke, dann schnell reagiert und die Inhalte der Internetseiten korrekt und schnell darstellt. Sollte ich einmal den Wunsch haben, etwas anderes mit meinem Browser zu tun, wäre es nett, wenn ich dies aktiv durch ein Plug-in aktivieren kann.

Nun gibt es zu dieser gerade beschriebenen Problemstellung oft die Argumentation, dass man ja mit den vielen Funktionen einen breiten Nutzerkreis erreichen möchte. Gerade wenn eine Anwendung zu Beginn alle möglichen Optionen auch aktiv eingeschaltet hat, holt das schnell den unkundigen Benutzer ab, der dann nicht erst begreifen muss, wie sein Programm überhaupt funktioniert. Ich kann diese Überlegung durchaus nachvollziehen. Es ist auch völlig in Ordnung, wenn ein Hersteller sich ausschließlich auf unkundige Anwender konzentriert. Es gibt aber einen Mittelweg, der alle Nutzergruppen gleichmäßig berücksichtigt. Diese Lösung ist auch nicht neu und sehr gut bekannt, die sogenannten Produktlinien.

In der Vergangenheit haben Hersteller immer Zielgruppen wie Privatpersonen, Unternehmen und Experten definiert. Diesen Nutzergruppen wurden dann oft Produktbezeichnungen wie Home, Enterprise und Ultimate zugeordnet. Das führte dazu, dass jeder die Ultimate Version wollte. Das Phänomen nennt sich Fear Of Missing Out (FOMO), also etwas zu verpassen. Deswegen sind die Bezeichnungen der Produktgruppen und deren zugeordneten Funktionen psychologisch ungeschickt gewählt. Wie kann man das also besser machen?

Ein Experte konzentriert sich bei seiner Arbeit auf spezielle Basisfunktionen, mit denen er seine Aufgaben schnell und ohne Ablenkung erledigen möchte. Das impliziert für mich Begriffe wie Essentials, Pure oder Core als Produktline.

Wenn das Produkt dann noch im Unternehmen von mehreren Personen verwendet werden soll, benötigt dies oft zusätzliche Funktionen wie zum Beispiel ein externes Benutzermanagement, wie LDAP oder IAM. Diese spezialisierte Produktlinie assoziiert Begriffe wie Enterprise (verbrannt), Company, Business und so weiter.

Das zugemüllte Endergebnis, das eigentlich für NOOPS gedacht ist und alle möglichen Sachen bereits über die Installation aktiviert hat. Wenn den Leuten die Zeit, bis die Anwendung gestartet ist und sie reagiert, egal ist, dann nur zu. Immer in die Vollen. Rein, was rein geht! Hier eignen sich Bezeichnungen wie Ultimate, Full und Maximized Extended als Bezeichnung der Produktlinie. Wichtig ist nur, dass die Profis erkennen, dass es sich um die zugemüllte Variante handelt.

Wer nun geschickt mit diesen Produktlinien spielt und möglichst alle Funktionen über sogenannte Module bereitstellt, die nachinstallierbar sind, ermöglicht eine hohe Flexibilität auch im Expertenmodus, denen durchaus die eine oder andere Zusatzfunktion genehm ist.

Installiert man auf das Modulsystem zuvor noch ein Tracking, um festzustellen, wie professionelle Anwender ihre Version upgraden, dann hat man schon eine gute Idee, was in die neue Version von Essentials hinzugefügt werden könnte. Bei dem Tracking sollte man sich aber nicht auf die Downloads als Entscheidungskriterium stützen. Ich selbst probiere oft Dinge aus und lösche Erweiterungen auch schneller, als der Installationsprozess gedauert hat, wenn ich der Meinung bin, dass diese nutzlos sind.

Ich möchte zu der gerade beschriebenen Problematik ein kleines Beispiel geben, das aus dem DevOps Bereich stammt. Zum einen gibt es das bekannte GitLab, das ursprünglich einmal ein reines Code Repository-Hosting-Projekt gewesen ist. Darauf deutet auch der Name, bis heute. Eine Anwendung, die auf einem Server bereits 8 GB RAM in der Basis-Installation benötigt, um ein Git Repository für andere Entwickler erreichbar zu machen, ist für mich unbrauchbar, denn diese Software wurde über die Zeit zur EierlegendenWollmilchSau. Langsam, unflexibel und mit allem Kram zugemüllt, der über Speziallösungen besser umgesetzt wurde.

GitLab gegenüber steht eine andere Lösung namens SCM-Manager, die weniger bekannt ist und sich ausschließlich auf das Bereitstellen der Code Repositories konzentriert. Ich selbst nutze und empfehle den SCM-Manager, weil er mit der Basis-Installation extrem kompakt ist. Aber dennoch gibt es eine gigantische Funktionsvielfalt, die man über Plug-ins nachrüsten kann.

Für mich sind Lösungen, die eine All In One Solution bereitstellen wollen, eher suspekt. Das ist für mich immer gleich dem Motto: alles und nichts. Es gibt keine EierlegendeWollMilchSau oder wie man in Österreich zu sagen pflegt, keinen Wunderwuzzi!

Wenn ich Programme für meinen Arbeitsprozess auswähle, orientiere ich mich ausschließlich an deren Kernfunktionalität. Sind die Grundeigenschaften, die das Marketing verspricht, wirklich vorhanden und möglichst intuitiv nutzbar? Gibt es eine aussagekräftige Dokumentation, die über ein bloßes ‚Hallo Welt‘ hinausgeht? Konzentriert man sich darauf, die Kernfunktionen stets zu optimieren, und berücksichtigt neue innovative Konzepte? Das sind Fragen, die für mich relevant sind.

Gerade im kommerziellen Umfeld werden oft Programme eingesetzt, die nicht halten, was das Marketing verspricht. Man wählt nicht aus, was man tatsächlich für die Erledigung der Aufgaben benötigt, sondern Anwendungen, deren Beschreibung mit sogenannten Buzzwords vollgestopft ist. Deswegen glaube ich, dass Unternehmen, die sich wieder auf die Kernkompetenzen fokussieren und dazu hoch spezialisierte Anwendungen nutzen, die Gewinner von morgen sind.


Von Missmanagement und Alpha-Geeks

Als ich neulich das Buch „The Manager’s Path“ von Camille Fournier in die Hand bekam, war ich recht schnell an Tom DeMarco erinnert. Dieser hat den Klassiker „Peopleware“ geschrieben und Anfang 2000 das Buch „Adrenalin Junkies und Formular Junkies“ veröffentlicht. Eine Liste an Stereotypen, die man in Softwareprojekten antreffen kann, mit Hinweisen, wie man mit ihnen umgeht. Nach einigen Jahrzehnten im Geschäft kann ich jedes einzelne Wort aus eigener Erfahrung bestätigen. Und es ist auch heute noch immer ein Thema, denn Menschen machen nun einmal Projekte und wir alle haben so unsere Eigenheiten.

Damit Projekte erfolgreich verlaufen, müssen nicht nur technische Herausforderungen gemeistert werden. Auch zwischenmenschliche Beziehungen spielen eine wesentliche Rolle für deren Erfolg. Ein wichtiger Faktor in diesem Zusammenhang, der oft wenig Beachtung findet, ist die Projektleitung. Es gibt Regale voller hervorragender Literatur, wie man ein guter Manager werden kann. Das Problem ist leider, dass nur wenige, die diese Position besetzen, diese nicht ausfüllen und noch weniger Interesse besteht, die eigenen Fertigkeiten weiterzuentwickeln. Das Ergebnis von schlechtem Management sind aufgeriebene und gestresste Teams, extremer Druck im Tagesgeschäft und oft auch Verzug von Lieferterminen. Da braucht man sich auch nicht wundern, wenn dies Auswirkungen auf die Qualität des Produktes hat.

Einer der ersten Sprüche, den ich in meinem Berufsleben gelernt habe, war: Wer glaubt, dass ein Projektleiter Projekte leitet, der glaubt auch, dass ein Zitronefalter Zitronen faltet. Es scheint also eine sehr alte Weisheit zu sein. Was ist aber das wirkliche Problem bei schlechtem Management? Jeder, der eine Stelle für einen Manager zu besetzen hat, ist in der Pflicht, dessen Fertigkeiten und charakterliche Eignung auf Herz und Nieren zu prüfen. Hier lässt man sich schnell von inhaltslosen Floskeln und einer Liste großer Namen in der Branche in der Vita beeindrucken, ohne die tatsächliche Leistung zu hinterfragen. In meiner Erfahrung bin ich vornehmlich auf Projektleiter gestoßen, die oft nicht die notwendigen fachlichen Kenntnisse hatten, um wichtige Entscheidungen zu treffen. Nicht selten wurde ich in IT-Projekten von Managern mit den Worten „Ich bin kein Techniker, macht das unter euch aus.“, abgefertigt. Das ist natürlich fatal, wenn die Person, welche die Entscheidungen treffen soll, diese nicht treffen kann, weil das notwendige Wissen fehlt. Ein Manager im IT-Projekt muss natürlich nicht wissen, welcher Algorithmus schneller terminiert. Hierfür gibt es die Möglichkeit von Evaluierungen, als Grundlage von Entscheidungen. Aber ein Grundverständnis der Programmierung sollte vorhanden sein. Wer nicht weiß, was eine API ist und wieso Module, die später zu einer Software zusammengesetzt werden sollen, durch Versionskompatibilitäten nicht zusammenarbeiten, hat keine Berechtigung, sich als Entscheidungsträger aufzuspielen. Ein Grundverständnis über die Prozesse in der Softwareentwicklung und die verwendeten Programmierparadigmen ist auch für Projektleiter, die nicht am Code arbeiten, unumgänglich.

Ich plädiere daher dafür, dass man nicht nur die Entwickler, die man einstellt, auf ihre Fertigkeiten hin prüft, sondern auch die Manager, die in ein Unternehmen aufgenommen werden sollen. Für mich ist ein absolutes No Go bei der Auswahl meiner Projekte, ein externes Projektmanagement. Das führt in aller Regel nur zu Chaos und Frustration bei allen beteiligten Personen, weswegen ich solche Projekte ablehne. Manager, die nicht ins Unternehmen eingebunden sind und deren Leistung nach dem Erfolg von Projekten bewertet wird, liefern erfahrungsgemäß keine saubere Arbeit ab. Zudem können interne Manager, ebenso wie die Entwickler, durch Mentoring, Trainings und Workshops ihre Fertigkeiten entwickeln und weiter ausbauen. Das Ergebnis sind ein gesundes, entspanntes Arbeitsklima und erfolgreiche Projekte.

Der Titel dieses Artikels weist auf toxische Stereotypen im Projekteschäft hin. Ich bin mir sicher, jeder ist auf den ein oder anderen Stereotypen bereits im beruflichen Umfeld getroffen. Es wird viel diskutiert, wie man mit diesen Zeitgenossen umgehen soll. Ich möchte aber anmerken, dass kaum jemand als „Monster“ geboren wurde. Dass Menschen so sind, wie sie sind, ist das Resultat ihrer Erfahrungen. Lernt ein Kollege, dass er, wenn er gestresst ausschaut und immer hektisch wirkt, als guter Arbeiter wahrgenommen wird, perfektioniert er dieses Verhalten über die Zeit.

Camille Fournier hat es mit dem Begriff „The Alpha Geek“ sehr auf den Punkt gebracht. Jemand, der seine Rolle im Projekt unersetzlich gemacht hat und auf alles eine Antwort hat. Meist verächtlich auf die Kollegen herabschaut, aber nie eine Aufgabe wirklich ohne Nacharbeiten anderer sauber zu Ende bringen kann. Unrealistische Abschätzungen für umfangreiche Aufgaben sind ebenso typisch wie die Relativierung komplexer Sachverhalte. Natürlich ist das der Liebling aller Projektleiter, die sich wünschen, das ganze Team würde aus diesen „Alpha Geeks“ bestehen. Ich bin mir sehr sicher, wenn dieser Traum wahr werden könnte, es die optimale Strafe für die Projektleiter wäre, die solche Menschen erst möglich machen.

Damit man sich im eigenen Unternehmen keine „Alpha Geeks“ züchtet, ist es notwendig, keinen Personenkult zu etablieren und die persönlichen Favoriten gegenüber dem restlichen Team zu überhöhen und auf ein Podest zu stellen. Natürlich ist es auch unabdingbar, stets die Arbeitsergebnisse zu hinterfragen. Wer eine Aufgabe als erledigt markiert und diese Nacharbeiten erfordert, sollte diese Nacharbeiten stets erneut zugewiesen bekommen, bis das Ergebnis zufriedenstellend ist.

Ich persönlich habe die selbe Auffassung wie Tom DeMarco, was die Dynamiken eines Projektes betrifft. Die Produktivität kann man zwar über die Menge der erledigten Aufgaben bewerten, dennoch gibt es auch noch weitere Einflüsse, die eine wichtige Rolle spielen. So habe ich durch meine Erfahrung die Auffassung, dass man, wie bereits erwähnt, sehr viel Wert darauf legen sollte, dass Mitarbeiter die begonnenen Aufgaben sauber und vollständig lösen, bevor sie eine neue Aufgabe annehmen. Kollegen, die eine bestimmte Aufgabe „kleinreden“ und zu geringe, unrealistische Einschätzungen abgeben, genau diese Aufgabe übernehmen. Zudem gibt es auch Kollegen, die zwar einen recht geringen Output haben, dafür aber sehr viel für die Harmonie im Team beitragen.

Wenn ich über Menschen spreche, die ein gesundes Team formen, meine ich damit nicht jeden Tag Süßigkeiten hinzustellen. Es geht um die, die durchaus gute Fähigkeiten haben und diese als Mentor anderen Kollegen beibringen. Meist haben diese Personen einen guten Stand im Team und ihnen wird viel Vertrauen entgegengebracht, weswegen sie auch oft als Mediatoren bei Konflikten gute Ergebnisse erzielen. Es sind nicht die Menschen, die durch falsche Versprechen versuchen, everybodys Darling zu sein, sondern die, die zuhören und sich Zeit nehmen, eine Lösung zu finden. Sie sind oft das Mädchen für alles und haben nicht selten ein ruhiges, unscheinbares Auftreten. Da sie Lösungen haben und oft eine helfende Hand reichen, haben sie selbst eine mittelmäßige Bewertung ihrer Leistung in den typischen Prozessmetriken. Ein guter Manager erkennt diese Personen rasch, weil auf sie in aller Regel Verlass ist. Sie sind ausgeglichen und wirken wenig gestresst, weil sie mit Ruhe und Kontinuität vorgehen.

Natürlich kann man über die Stereotypen in einem Softwareprojekt noch viel mehr sagen, aber ich denke, die bereits getroffenen Ausführungen geben ein gutes Grundverständnis über das, was ich zum Ausdruck bringen möchte. Denn ein erfahrener Projektleiter kann viele der beschriebenen Probleme bereits während ihrer Entstehung wieder abstellen. Dazu gehören natürlich auch solides technisches Wissen und etwas Menschenkenntnis.

Es muss uns natürlich auch bewusst sein, dass erfahrene Projektleiter nicht einfach so da sind. Man muss sie genau wie die Mitglieder eines Teams entwickeln und fördern. Dazu gehört durchaus auch die Rotation durch alle technischen Abteilungen wie Entwicklung, Test und Betrieb. Dazu eignen sich Paradigmen wie Pairprogramming hervorragend. Denn es geht nicht darum, aus einem Manager einen Programmierer oder Tester zu machen, sondern ihm oder ihr ein Verständnis der täglichen Abläufe zu verschaffen. Das stärkt auch das Vertrauen in die Fertigkeiten des gesamten Teams, und Mentalitäten wie: Man müsse die faulen und unfähigen Programmierer kontrollieren und antreiben, damit sie einen Finger rühren, kommen gar nicht erst auf. In Projekten, die regelmäßig gute Qualität abliefern und ihre Termine einhalten, kommt selten der Wunsch auf, alle möglichen Prozessmetriken einzuführen.


RTFM – benutzbare Dokumentationen

Ein alter Handwerksmeister pflegte immer zu sagen: Wer schreibt, der bleibt. Seine Intention war vor allem, ein vernünftiges Aufmaß und Wochenberichte seiner Gesellen zu bekommen. Diese Informationen benötigte er, um eine korrekte Rechnung stellen zu können, was für den Erfolg des Unternehmens maßgeblich war. Dieses Bild lässt sich auch gut auf die Softwareentwicklung übertragen. Erst als die in Japan von Yukihiro Matsumoto entwickelte Programmiersprache Ruby eine englischsprachige Dokumentation besaß, begann der weltweite Siegeszug von Ruby.

Wir sehen also, dass Dokumentation einen durchaus hohen Stellenwert für den Erfolg eines Softwareprojektes haben kann. Es beschränkt sich nicht nur auf einen Informationsspeicher im Projekt, wo neue Kollegen notwendige Details erfahren. Natürlich ist für Entwickler Dokumentation ein recht leidiges Thema. Stetig muss diese aktuell gehalten werden und oft fehlen auch Fertigkeiten, um die eigenen Gedanken sortiert und nachvollziehbar für andere auf Papier zu bringen.

Ich selbst kam vor sehr vielen Jahren erstmalig mit dem Thema Dokumentation durch das Lesen des Buches „Softwaretechnik“ von Johannes Siedersleben in Berührung. Dort wurde Ed Yourdon mit der Aussage zitiert, dass vor Methoden wie UML die Dokumentation oft in der Form einer viktorianischen Novelle vorlag. Während meines Berufslebens habe ich auch einige solcher viktorianischen Novellen angetroffen. Das Ärgerliche daran war: Nachdem man sich durch die Textwüste gekämpft hatte – anders als mit Überwindung und Kampf kann man das Gefühl nicht beschreiben –, hatte man die gesuchten Informationen immer noch nicht. Frei nach Goethes Faust: „So steh ich da ich armer Tor und bin so klug als wie zuvor.“

Hier sehen wir bereits einen ersten Kritikpunkt für schlechte Dokumentationen: eine unangemessene Länge der Ausführungen, die wenig Informationen enthalten. Hier müssen wir uns der Tatsache bewusst werden, dass das Schreiben nicht jedem in die Wiege gelegt wurde. Schließlich ist man ja Softwareentwickler und nicht Buchautor geworden. Das bedeutet für das Konzept „erfolgreiche Dokumentation“, dass man möglichst niemanden zu seinem Glück zwingen sollte und sich besser im Team nach Personen umschaut, die für Dokumentation ein Händchen haben. Das soll nun aber nicht bedeuten, dass alle anderen von der Aufgabe Dokumentation freigestellt sind. Ihr Input ist für die Qualität essenziell. Korrekturlesen, auf Fehler hinweisen und Ergänzungen vorschlagen sind durchaus notwendige Punkte, die sich gut auf vielen Schultern verteilen lassen.

Es ist durchaus ratsam, das Team, oder einzelne Teammitglieder gelegentlich rhetorisch zu schulen. Dabei sollte der Fokus auf einer präzisen, kompakten und verständlichen Ausdrucksweise liegen. Dabei geht es auch darum, die eigenen Gedanken so zu sortieren, dass diese auf Papier gebracht werden können und dabei einem roten Faden folgen. Die dadurch verbesserte Kommunikation wirkt sich sehr positiv auf die Entwicklungsprojekte aus.

Eine aktuell gehaltene Dokumentation, die gut zu lesen ist und wichtige Informationen enthält, wird schnell zu einem lebenden Dokument, ganz gleich, welche Formen gewählt wurden. Dies ist auch ein grundlegendes Konzept für erfolgreiches DevOps und agile Vorgehensmodelle. Denn diese Paradigmen setzen auf einen guten Informationsaustausch und adressieren auch das Vermeiden von sogenannten Informationssilos.

Ein Punkt, der mich wirklich triggert, ist die Aussage: Unsere Tests sind die Dokumentation. Nicht alle Stakeholder können programmieren und sind daher auch nicht in der Lage, die Testfälle zu verstehen. Zudem demonstrieren Tests zwar das Verhalten von Funktionen, sie demonstrieren aber nicht per se die richtige Verwendung. Meist fehlen auch Variationen von verwendbaren Lösungen. Damit Testfälle einen dokumentativen Charakter haben, ist es notwendig, spezielle Tests exakt für diesen Zweck zu entwickeln. Dieses Vorgehen hat meiner Ansicht nach zwei gravierende Vorteile. Zum Ersten bleibt die Dokumentation zur Implementierung aktuell, denn bei Änderungen schlägt der Testfall fehl. Ein weiterer positiver Effekt ist, dass der Entwickler über die Verwendung seiner Implementierung bewusst wird und einen ungünstigen Entwurf zeitnah korrigieren kann.

Natürlich gibt es mittlerweile unzählige technische Lösungen, die je nach Sichtweise auf das System für unterschiedliche Personengruppen geeignet sind. Issue- und Bug-Tracking Systeme wie beispielsweise das kommerzielle JIRA oder das freie Redmine bilden ganze Prozesse ab. Sie erlauben es den Testern, erkannte Probleme und Fehler der Software einer Releaseversion zuzuordnen. Projektleiter können mit dem Release Management eine Priorisierung der Korrekturen vornehmen und die Entwickler dokumentieren die verwendete Korrektur. Soweit die Theorie. In der Praxis habe ich in nahezu jedem Projekt erlebt, wie in diesen Systemen die Kommentarfunktion als Chat missbraucht wurde, um den Änderungsstatus zu beschreiben. Als Ergebnis hat man eine Fehlerbeschreibung mit unzähligen nutzlosen Kommentaren und wirkliche weiterführende Informationen fehlen komplett.

Eine weitverbreitete technische Lösung in Entwicklungsprojekten sind auch sogenannte Enterprise Wikis. Sie ergänzen einfache Wikis durch eine Navigation und ermöglichen das Erstellen geschlossener Spaces, auf die nur explizit zugelassene Nutzergruppen feingranulare Berechtigungen wie Lesen oder Schreiben erhalten. Neben der weitverbreiteten kommerziellen Lösung Confluence gibt es auch eine freie Variante namens Blue Spice, die auf dem MediaWiki basiert. Wikis erlauben die kollaborative Arbeit an einem Dokument und die einzelnen Seiten können auch über verschiedene Zusammenstellungen als PDF exportiert werden. Damit die Wikiseiten auch benutzbar bleiben, sollte man Wert auf eine saubere und möglichst einheitliche Formatierung legen. Tabellen sollten mit ihrem Inhalt auf eine A4 Seite passen, ohne dass es zu unerwünschten Umbrüchen kommt. Das verbessert den Lesefluss. Es gibt auch viele Fälle, in denen Aufzählungen der Übersichtshalber Tabellen vorzuziehen sind.

Dies bringt uns auch zu einem weiteren sehr heiklen Thema, den Grafiken. Es ist durchaus korrekt, dass ein Bild oft mehr als tausend Worte sagt. Aber eben nicht immer! Im Umgang mit Grafiken ist es wichtig, sich bewusst zu sein, dass Bilder oft einiges an Zeit für die Erstellung benötigen und oft auch nur mit viel Aufwand angepasst werden können. Daraus ergeben sich einige Konsequenzen, um sich das Leben zu erleichtern. Zum Erstellen von Grafiken wird sich auf ein Standardprogramm (Format) festgelegt. Auf teure Grafikprogramme wie Photoshop und Corel ist zu verzichten. Grafiken, die für Wikiseiten erstellt wurden, sind in ihrem Original, also der änderbaren Datei, an die Wikiseite anzufügen. Es kann auch ein eigenes Repository dafür aufgebaut werden, um so eine Wiederverwendung in anderen Projekten zu ermöglichen.

Wenn ein Bild keinen Mehrwert bedeutet, sollte man darauf besser verzichten. Dazu ein kleines Beispiel. Es ist nicht notwendig, eine Grafik anzufertigen, auf der 10 Strichmännchen abgebildet sind, unter denen dann der Rollenname oder eine Person steht. Hier ist es zielführend, eine einfache Aufzählung anzufertigen, die sich im Übrigen auch leichter ergänzen beziehungsweise anpassen lässt.

Aber auch auf überfrachtete Grafiken sollte man verzichten. Treu nach dem Motto „Viel hilft viel“: Sorgen zu detaillierte Informationen eher für Verwirrung und können zu Missinterpretationen führen. Eine Buchempfehlung ist „Softwarearchitekturen dokumentieren und kommunizieren“ von Stefan Zörner. Er arbeite in diesem Titel optimal heraus, wie wichtig die verschiedenen Sichtweisen auf ein System sind und welche Personengruppen mit einer expliziten Sicht angesprochen werden. Dazu möchte ich auch die Gelegenheit nutzen, um seine 7. Regeln für eine gute Dokumentation wiederzugeben.

  1. Schreibe aus Sicht des Lesers.
  2. Vermeide unnötige Wiederholungen.
  3. Vermeide Mehrdeutigkeiten, wenn nötig Notation erläutern.
  4. Verwende Standards wie z. B. UML.
  5. Halte Begründungen (Warum) fest.
  6. Die Dokumentation ist aktuell zu halten, aber nie zu aktuell.
  7. Überprüfe die Gebrauchstauglichkeit (Review).

Wer im Projekt damit beauftragt ist, die Dokumentation zu schreiben beziehungsweise deren Fortschritt und Aktualität sicherzustellen hat, sollte sich immer bewusst sein, dass wichtige Informationen enthalten sind, diese auch korrekt und verständlich dargestellt werden. Eine kompakte und übersichtliche Dokumentation lässt sich auch bei fortschreitendem Projekt problemlos anpassen und erweitern. Anpassungen gelingen immer dann am besten, wenn der betroffene Bereich möglichst zusammenhängend ist und möglichst nur einmal vorkommt. Diese Zentralisierung erreicht man durch Referenzen und Verlinkungen, so dass die Änderung im Original sich auf die Referenzen auswirkt.

Natürlich gibt es zum Thema Dokumentation noch viel mehr zu sagen, schließlich ist es Gegenstand verschiedener Bücher, aber das würde den Rahmen dieses Artikels übersteigen. Mir ging es vor allem darum, für das Thema eine Sensibilisierung zu schaffen, denn Paradigmen wie Agilität und DevOps basieren auf einem guten Informationsfluss.


BugChaser – Die Grenze der Testabdeckung

Die mittlerweile im Software Engineering etablierten Paradigmen wie Test Driven Development (TDD) und Behavior Driven Development (BDD) mit entsprechend einfach zu bedienenden Werkzeugen haben eine neue pragmatische Sichtweise auf das Thema Software Tests eröffnet. Ein wichtiger Faktor in kommerziellen Softwareprojekten sind automatisierte Tests. Deshalb spricht man in diesem Kontext von einer erfolgreichen Teststrategie, wenn die Testausführung ohne menschliches Zutun vonstattengeht.

Testautomatisierung bildet die Grundlage, um Stabilität und Risikoreduzierung bei kritischen Arbeiten zu erreichen. Zu solchen kritischen Tätigkeiten zählen insbesondere das Refactoring, Maintenance und Fehlerkorrekturen. Allen diesen Aktivitäten obliegt eine Gemeinsamkeit: dass sich keine neuen Fehler in den Code einschleichen dürfen.

In dem Artikel „The Humble Programmer“ von 1972 stellte Edsger W. Dijkstra folgendes fest:

„Programmtests können ein sehr effektiver Weg sein, um das Vorhandensein von Fehlern aufzuzeigen, sind aber völlig unzureichend, um deren Abwesenheit nachzuweisen.“

Eine alleinige Automatisierung der Testausführung ist deshalb nicht ausreichend, um sicherzustellen, dass Änderungen der Codebasis keine unerwünschten Effekte auf bestehende Funktionen haben. Aus diesem Grund muss die Qualität der Testfälle bewertet werden. Hierzu gibt es bereits bewährte Werkzeuge.

Bevor wir tiefer in die Thematik einsteigen, wollen wir uns zuerst überlegen, was eigentlich automatisiertes Testen bedeutet. Diese Frage ist recht einfach zu beantworten. Nahezu jede Programmiersprache hat ein entsprechendes Unit Test Framework. Unit Tests rufen eine Methode mit verschiedenen Parametern auf und vergleichen den Rückgabewert mit einem Erwartungswert. Stimmen beide Werte überein, gilt der Test als bestanden. Zusätzlich kann noch überprüft werden, ob eine Ausnahme geworfen wurde.

Für den Fall, dass eine Methode keinen Rückgabewert hat oder keinen Fehler wirft, kann diese Methode nicht getestet werden. Auch als private gekennzeichnete Methoden oder innere Klassen, sind nicht ohne Weiteres zu testen, da sie nicht direkt aufgerufen werden können. Diese sind über öffentliche Methoden, welche die ‚versteckten‘ Methoden aufrufen, zu testen.

Im Umgang mit als private gekennzeichneten Methoden ist es keine Option, die dadurch abgebildete Funktionalität über Techniken wie die Verwendung der Reflection API zu erreichen und zu testen. Denn wir müssen uns bewusst machen, dass solche Methoden oft auch dazu verwendet werden, Fragmente zu kapseln, um Dopplungen zu vermeiden.

public boolean method() {
    boolean success = false;
    List collector = new ArryList();
    collector.add(1);
    collector.add(2);
    collector.add(3);
    
    sortAsc(collector);
    if(collector.getFirst().equals(1)) {
        success = true;
    }
    return success;
}

private void sortAsc(List collection) {
    collection.sort( 
            (a, b) -> { 
                return -1 * a.compareTo(b); 
            });
}

Um also effektiv automatisierte Tests schreiben zu können, ist es notwendig, einem gewissen Codingstil zu folgen. Das vorangegangene Listing 1 demonstriert auf einfache Weise, wie testbarer Code aussehen kann.

Da Entwickler für ihre eigene Implementierung auch die zugehörigen Komponententests schreiben, ist das Problem von schwer testbarem Code in Projekten, die einem testgetriebenen Ansatz folgen, weitgehend eliminiert. Die Motivation zu testen liegt nun beim Entwickler, da dieser mit diesem Paradigma feststellen kann, ob seine Implementierung sich wie gewünscht verhält. Dabei müssen wir uns aber fragen: Ist das bereits alles, was wir tun müssen, um gute und stabile Software zu entwickeln?

Wie wir uns bei solchen Fragen immer denken können, lautet die Antwort nein. Ein essenzielles Werkzeug, um die Qualität der Tests zu bewerten, ist das Erreichen einer möglichst hohen Testabdeckung. Dabei wird zwischen Branch und Line Coverage unterschieden. Um den Unterschied etwas besser zu verdeutlichen, schauen wir kurz auf den Pseudocode in Listing 2.

if( Expression-A OR Expression-B ) {
print(‘allow‘);
} else {
print(‘decline‘);
}

Unser Ziel ist es, nach Möglichkeit alle Zeilen zu durchlaufen. Dazu brauchen wir bereits zwei separate Testfälle. Einen für das Betreten des IF-Zweiges und einen für das Betreten des ELSE-Zweiges. Damit wir aber auch eine hundertprozentige Branch-Coverage erzielen, müssen wir alle Varianten des IF-Zweiges abdecken. Für das Beispiel heißt das: ein Test, der Expression-A true werden lässt, und ein weiterer Test, der Expression-B true werden lässt. Daraus ergeben sich insgesamt drei verschiedene Testfälle.

Der Screenshot aus dem Projekt TP-CORE zeigt, wie eine solche Testabdeckung in ‚echten‘ Projekten aussehen kann.

Natürlich ist dieses Beispiel sehr einfach und es gibt im wirklichen Leben oft Konstrukte, bei denen man trotz aller Bemühungen nicht alle Lines beziehungsweise Branches erreicht. Sehr typisch sind Exceptions aus Fremdbibliotheken, die zu fangen sind, aber unter normalen Umständen nicht provoziert werden können.

Aus diesem Grund versuchen wir zwar, eine möglichst hohe Testabdeckung zu erreichen, und streben natürlich die 100 % an, aber es gibt genügend Fälle, in denen dies nicht möglich ist. Eine Testabdeckung von 90% gelingt aber durchaus. Der Industriestandard für kommerzielle Projekte liegt bei 85 % Testabdeckung. Mit diesen Erkenntnissen können wir also sagen, dass die Testabdeckung mit der Testbarkeit einer Anwendung korreliert. Das bedeutet, die Testabdeckung ist ein geeignetes Maß für die Testbarkeit.

Hier muss man allerdings auch zugeben, dass die Metrik der Testabdeckung ihre Grenze hat. Reguläre Ausdrücke und Annotationen zur Datenvalidierung sind nur einige einfache Beispiele für eine nicht aussagefähige Testabdeckung.

Ohne allzu sehr in die Implementierungsdetails einzugehen, stellen wir uns vor, wir müssten einen regulären Ausdruck schreiben, um Eingaben auf ein korrektes 24 Stunden Format zu überprüfen. Haben wir das korrekte Intervall nicht im Auge, ist unser regulärer Ausdruck möglicherweise nicht korrekt. Das richtige Intervall für das 24-Stunden-Format lautet: 00:00 – 23:59. Beispiele für fehlerhafte Werte sind 24:00 oder 23:60. Ist uns dieser Fakt nicht bewusst, können trotz Testfällen Fehler in unserer Anwendung versteckt sein, bis sie verwendet werden un zu Fehlern führen.

Dies ist ein hervorragendes Beispiel für das Zitat von Dijkstra zu Beginn des Artikels. Zudem möchte ich noch ein weiteres Zitat aus einem Artikel anführen, an dem Christian Bird mitgewirkt hat. Der Artikel heißt „The Design of Bug Fixes“ und ist aus dem Jahr 2013.

„… In a few cases, participants were unable to think of alternative solutions …“

Hier ging es um die Fragestellung, ob eine Fehlerkorrektur immer die optimale Lösung darstellt. Abgesehen davon wäre zu klären, was in kommerziellen Softwareentwicklungsprojekten eine optimale Lösung darstellt. Sehr demonstrativ ist die Aussage, dass es Fälle gibt, in denen Entwickler nur einen Weg kennen beziehungsweise verstehen. Das spiegelt auch unser Beispiel der RegEx wider. Softwareentwicklung ist ein Denkprozess, der sich auch nicht beschleunigen lässt. Unser Denken wird durch unsere Vorstellungskraft bestimmt, die wiederum von unserer Erfahrung beeinflusst ist.

Dies zeigt uns bereits ein weiteres Beispiel für Fehlerquellen in Testfällen. Ein Klassiker sind z. B. inkorrekte Vergleiche in Collections. Es geht unter anderem um das Vergleichen von Arrays. Die Problematik, mit der wir hier zu kämpfen haben, ist das Thema, wie auf Variablen zugegriffen wird: Call by Value oder Call by Reference. Bei Arrays erfolgt der Zugriff über Call by Reference, also direkt auf die Speicherzelle. Weist man nun ein Array einer neuen Variable zu und vergleicht beide Variablen, sind diese immer gleich, da man das Array mit sich selbst vergleicht. Ein Beispiel für einen Testfall, der eigentlich keine Aussagekraft hat. Ist die Implementierung dennoch korrekt, wird dieser fehlerhafte Testfall nie ins Gewicht fallen.

Diese Erkenntnis zeigt uns, dass ein blindes Erreichen der Testabdeckung für die Qualität nicht zielführend ist. Natürlich ist es verständlich, dass im Management diese Metrik einen hohen Stellenwert hat. Wir haben aber auch nachweisen können, das man sich darauf alleine nicht verlassen darf. Wir sehen also, dass auch für Testfälle Codeinspektionen und Refactorings ein Bedarf besteht. Da man aus Zeitgründen nun nicht den ganzen Code von vorn bis hinten lesen und auch verstehen kann, ist es wichtig, auf problematische Bereiche zu konzentrieren. Wie kann man aber diese Problembereiche finden? Hier hilft uns eine vergleichsweise neue Technik. Die theoretischen Arbeiten dazu sind bereits etwas älter, es hat nur eine Weile gedauert, bis entsprechende Implementierungen verfügbar wurden.

Mutation Testing. Dies erlaubt, durch Veränderung des Originalcodes Testfälle zu finden, die trotz verschiedener Mutationen nicht fehlschlagen. Damit haben wir ein weiteres Werkzeug an der Hand, die Qualität der vorhandenen Tests zu bewerten. In diesem Artikel habe ich zeigen können, dass man sich nicht komplett auf die Testcoverage verlassen darf. Um dieses Problem zu lösen, können wir uns des Mutation Testing bedienen. Wie konkret Mutation Testing funktioniert, kann wiederum Thema eines eigenständigen Artikels sein und würde an dieser Stelle den Rahmen sprengen.