Software ist, abgesehen von wirklich trivialen Programmen, umfassend und komplex zugleich. Das bedeutet auch, dass es nach heutigem Kenntnisstand keine fehlerfreien Anwendungen gibt. Dennoch tun wir uns schwer, mit Fehlern umzugehen, und wir beseitigen sie möglichst schnell und ohne Aufsehen zu erregen. Damit vertun wir eine Möglichkeit, dazuzulernen – und zwar sowohl für uns selbst als auch für das gesamte Entwicklungsteam.
Beginnen wir diesen Artikel mit einem Auszug aus einem Bericht des Portals SPIEGEL ONLINE vom 14. Oktober 2018. Zitiert wird dort eine Studie der Unternehmensberatung Ernst & Jung. In dieser Studie hat ein beachtlicher Teil der Angestellten angegeben, dass Fehler in Unternehmen nicht angesprochen werden. Hauptgründe für das Vertuschen von Fehlern sind nach Einschätzung der Befragten Angst vor Nachteilen für die eigene Karriere oder die Sorge, den Job verlieren zu können. Wie steht es im Bereich der Softwareentwicklung um die Fehlerkultur? Wie gehen wir damit üblicherweise um? Sehen wir zu, dass die Fehler möglichst klammheimlich korrigiert werden, bevor jemand etwas davon mitbekommen hat, oder diskutieren wir die Ursachen und die Beseitigung von Fehlern offen? In diesem Artikel wollen wir uns dem Thema aus verschiedenen technischen und nicht technischen Perspektiven nähern und daraus Schlüsse ziehen, wie heutzutage ein adäquater Umgang damit aussehen kann. Beginnen wir mit den möglichen Folgen von Softwarefehlern.
Nicht alle Softwarefehler haben drastische Folgen. Gern publiziert werden die folgenden Fehler und ihre Wirkungen:
Grundsätzlich ist jeder Softwarefehler ein Ärgernis für den Kunden. Das System arbeitet nicht so, wie es soll. Im betrieblichen Umfeld werden zum Beispiel Produktionsprozesse gestört; bei der privaten Nutzung muss der Anwender mit Einschränkungen und Unzulänglichkeiten leben.
Was ist eigentlich ein Fehler in Software? Diese triviale Frage führt zu folgenden, nicht ganz einheitlichen Definitionen:
Und welche Definition ist für uns Entwickler heute am besten greifbar? Wir verwenden die folgende Begrifflichkeit: Unter einem Fehler in einem Softwaresystem verstehen wir eine Abweichung von einem gewünschten Sollzustand. Nicht jeder Fehler muss zu einer Unbenutzbarkeit der Anwendung führen. Dabei muss man immer den Zeitpunkt der Vereinbarung zwischen den Vertragsbeteiligten zugrunde legen. Ein Beispiel: Wurde für eine Software vor fünfzehn Jahren ein benutzerfreundliches User Interface (UI) vereinbart, kann dieses nicht mit den Maßstäben für ein UI aus dem Jahr 2018 beurteilt werden. Damals waren Touch-Interaktion und heutige Designvorgaben noch nicht bekannt. Man kann deshalb die Software nicht als fehlerbehaftet ansehen. An diesem Beispiel mag die Beurteilung noch einfach sein. Was ist mit Anwendungen, die für Windows XP geschrieben wurden? Wenn eine solche Software unter Windows 10 nicht ohne Probleme läuft, liegt dann ein Fehler vor? Sofern das Projekt und der Vertrag keine kontinuierliche Anpassung an die technische Entwicklung vorsehen, haben wir es nicht mit einem Fehler zu tun. Wir lernen daraus: Heute problemlos funktionierende Software kann aufgrund des technischen Fortschritts morgen durchaus zu Fehlern führen. Es kommt darauf an, ob wir als Hersteller zur Anpassung verpflichtet sind. Der Begriff Fehler hat damit nicht nur eine technische Dimension, sondern auch Bedeutung aus vertraglicher Sicht zwischen Hersteller und Nutzer einer Software.
Man unterscheidet mehrere Arten von Fehlern. Grobe Kategorien sind Syntaxfehler, logische Fehler, Laufzeit und Designfehler. Sehen wir uns diese Fehlerarten etwas genauer an:
Fassen wir also zusammen: Fehler sind genauer nach ihrer Art zu klassifizieren.
Dass es keine fehlerfreie Software gibt, ist also klar. Wir wissen aber auch: Manche Fehler wiegen einfach schwerer als andere. Um die Wichtigkeit von Fehlern einzuteilen, arbeitet man mit Fehlerklassen. Eins gleich zu Beginn: Es gibt keine einheitliche Norm für eine Fehlerklassifizierung. Unterschiedliche Ziele der Klassifizierung führen zu ähnlichen, aber im Detail unterschiedlichen Definitionen (Tabelle 1).
Standard | Fehlerklassifikation |
---|---|
DIN 66271 | Die Klassifizierung ist an der Bewertung der Fehlerfolgen ausgerichtet und unterscheidet nach der Beeinträchtigung des Einsatzes und dem Schadensrisiko drei Stufen: hoch, mittel und niedrig. |
DIN 55350 Teil 31 | Die Einteilung ist an den Folgen ausgerichtet. Man unterscheidet kritische Fehler (gefährliche, kritische Situationen werden geschaffen), Hauptfehler (eventueller Ausfall oder die Brauchbarkeit wird wesentlich herabgesetzt) und Nebenfehler (Brauchbarkeit oder Betrieb werden nur geringfügig beeinflusst). |
Nach IEEE Standard 1044-1993 „Classification for Software Anomalies“ | Die Klassifizierung wird indirekt über den aus einem Fehler resultierenden Produktstatus definiert. Das sind: unbrauchbar, degradiert, beeinträchtigt (Workaround existiert) und nicht beeinträchtigt. |
Telecom Standard TL9000 | Drei Fehlerklassen (critical, major und minor) werden unterschieden. |
Six Sigma | Zwei Fehlerklassen: A-Fehler sind fehlerhafte oder fehlende Anforderung und nicht entdeckte Bedürfnisse des Nutzers. B-Fehler sind fehlerhafte Implementierungen. |
ISTQB – International Software Testing Qualification Board | Es werden folgende Kategorien unterschieden: „Critical“: Dieser Fehler kann zu einem Totalausfall des Systems führen. „Major“: Die Folgen sind nahezu identisch, aber es gibt einen Workaround. „Moderate“: Das System produziert fehlerhafte Ergebnisse. Es kommt nicht zum Programmabbruch. „Minor“: Die Folgen entsprechen im Wesentlichen denen von Moderate. Das System liefert teilweise fehlerhafte Ergebnisse. Es kommt nicht zum Programmabbruch. Es gibt eine Möglichkeit, die Fehler zu umgehen. „Cosmetic“: Das Aussehen bzw. das Verhalten der Software sollten verbessert werden. |
Tabelle 1: Klassifikationsansätze für Softwarefehler (Bitkom.org, insights.qa4software.de)
Um einen Fehler in einer Software beseitigen zu können, muss man diesen kennen. Als Entwickler müssen wir möglichst genaue Kenntnis von den Umständen haben, wie der Fehler aufgetreten ist, und es wäre gut, wenn er reproduzierbar wäre. Bei Individualsoftware bestehen gute Chancen, dass eine Dokumentation des Fehlers durchgeführt werden kann. Beispielsweise können Anwender eine Hotline kontaktieren, die dann nach einem vorgegebenen Muster den Fehler des Kunden aufnehmen kann, oder aber der Fehler kann mithilfe eines Formulars online gemeldet werden. Ein interessanter Vorschlag zur Dokumentation von Fehlern findet sich in. Demnach sollten im Idealfall folgende Punkte dokumentiert werden:
Das wichtigste Instrument zum Finden vorhandener Fehler sind Softwaretests. Dazu kommen wir jetzt.
Die Überschrift sagt es bereits: Tests in jeder Phase der Entwicklung sollen Fehler in einem Programm beseitigen bzw. deren Entstehung von vornherein verhindern. Ein erfolgreicher Test entdeckt möglichst viele Fehler. Jeder Fehler, der im Test entdeckt wird, bleibt in diesem Sinne dem Kunden erspart. Üblicherweise kann man zwischen den folgenden Testarten unterscheiden:
Sehen wir uns das im Einzelnen an. Bei einem funktionalen Test wird das System auf sein Ein- und Ausgabeverhalten geprüft. Basis für die Bewertung sind die sogenannten funktionalen Anforderungen, die sich u. a. aus den Dokumenten der Anforderungsanalyse wie Lasten- und Pflichtenheft (klassische Vorgehensweise) oder dem Backlog (agile Vorgehensweise) ergeben. Meist werden diese Tests als Blackboxverfahren durchgeführt. Eine funktionale Anforderung wird in der Regel nicht durch einen einzigen Testfall, sondern durch das Zusammenspiel mehrerer Testfälle überprüft. Bei nichtfunktionalen Tests geht es im Wesentlichen darum, wie gut das untersuchte System funktioniert. Typische nichtfunktionale Tests sind: Lasttest, Performancetest, Stresstest und der Usabilitytest.
Kommen wir zu den sogenannten strukturbezogenen Tests: Ihr Ziel ist es, die interne Struktur des betreffenden Softwareelements zu durchleuchten. Diese Art Test erfolgt als White-Box-Test. Dabei sollen der Kontroll und Datenfluss des Objekts überprüft werden. Idealerweise wird eine vollständige Testabdeckung erreicht, d. h., alle Strukturen und Verzweigungen werden mindestens einmal durchlaufen und überprüft.
Da Software i. d. R. ständig an neue Anforderungen angepasst werden muss bzw. Updates bereitgestellt werden, die bekannte Fehler beseitigen sollen, muss auch immer wieder neu getestet werden. Als änderungsbezogene Tests bzw. Regressionstests bezeichnet man Softwaretests, die nach einer Modifikation des Anwendungssystems durchgeführt werden. Dabei soll sichergestellt werden, dass durch die Änderungen keine neuen Fehler in die Anwendung gekommen sind. Diese möglichen neuen Fehler bezeichnet man als Seiteneffekte, und sie sind natürlich unerwünscht. Die Tests umfassen sowohl funktionale als auch nichtfunktionale Aspekte. Je öfter Änderungen des Softwaresystems stattfinden, desto umfangreicher ist auch der Bedarf an solchen Tests. Um die Menge der Tests handhaben zu können, ist das Ziel eine weitgehende Testautomatisierung. Ein Regressionstest kann dabei folgende Teilbereiche umfassen:
Nicht nach jeder Programmänderung kann ein vollständiger Testzyklus des gesamten Programmsystems durchgeführt werden; das wäre zu umfangreich. Der ideale Testumfang muss durch Abwägen des Aufwands für einen Test und des Risikos, das verbleibt, wenn bestimmte Tests nicht ausgeführt werden, ermittelt werden.
Die Situation in der Praxis können Sie sich wunderbar anhand von Abbildung 1 verdeutlichen. Dazu sind ein paar Erläuterungen angebracht: Auf der horizontalen x-Achse sehen Sie den Zeitverlauf. Auf der vertikalen y-Achse können Sie den Anteil an getestetem und ungetestetem Code ablesen. Wir befinden uns am Punkt der vertikalen Linie mit der Bezeichnung „Beginne Tests“. Ab diesem Zeitpunkt finden auch Anpassungen an der Software statt. Der Quellcode besteht zu jedem Zeitpunkt aus einem bestimmten Anteil an ungetestetem Code. Wie groß dieser Anteil ist, ist nicht immer bekannt. Das ist zum Beispiel der Fall, wenn Sie als Entwickler ein bestehendes Projekt übernehmen und dieser Fakt nicht aus der Dokumentation hervorgeht. Änderungen am Softwareprojekt, d. h. am Quellcode, finden auf zwei Ebenen statt: Zum einen kann das Projekt erweitert werden, d. h., es wird neuer Programmcode
hinzugefügt. Dieser neue Programmcode wird natürlich bestmöglich getestet, sodass sich der Anteil von ungetestetem Code relativ betrachtet verringert. Zum anderen werden bestimmte Programmfunktionen auch angepasst bzw. geändert. Diese Programmänderungen werden ebenso getestet. Dieses Vorgehen ist praxisrelevant, da man auf diese Weise bestehende Programme erweitern bzw. anpassen kann und gleichzeitig den Abdeckungsgrad des Quelltexts durch Tests sukzessive erhöht. Altanwendungen können dadurch bei notwendigen Anpassungen in der Qualität erhöht und für den weiteren Betrieb bzw. die Pflege fit gemacht werden. Die Aufwendungen für die Tests können wir gegenüber unseren Auftraggebern auch leichter rechtfertigen.
Die testgetriebene Entwicklung ist inkrementell, d. h., jeder Zyklus erweitert die Software um neue Fähigkeiten. Dabei soll sehr kleinteilig vorgegangen werden; ein Zyklus sollte nur ein paar Minuten dauern. Herausragend an dieser Vorgehensweise ist, dass die Tests geschrieben werden, bevor die Komponente implementiert wird. Man bezeichnet dieses Vorgehen auch als „Test First“. Der Entwickler bekommt direkt während der Entwicklung das Feedback. Tests und Produktivcode können durchaus von unterschiedlichen Personen geschrieben werden. Damit vermeidet man den eigenen Interessenskonflikt, schreibt einen Test, ohne auf die Implementierung zu achten, und produziert nur so viel Produktivcode, wie wirklich notwendig ist. Beim Refactoring werden die Tests und der Produktivcode gleichermaßen aufgeräumt. Ziel hierbei ist, die Software einfach, redundanzfrei und verständlich zu gestalten. TDD ist ein Kernelement der agilen Softwareentwicklung und soll einen großen Beitrag zu einer qualitativ hochwertigeren und fehlerfreieren Software liefern. Die Vorzüge dieses Vorgehens liegen unmittelbar auf der Hand:
Gelegentlich hören wir, dass die testgetriebene Softwareentwicklung insgesamt zu aufwendig ist. Der Grund ist, dass gewissermaßen jedes Problem zweimal angefasst werden muss, d. h. einmal im Test und einmal beim Schreiben des Produktivcodes. Dieses Argument lässt sich jedoch sehr schnell entkräften, da Software auf jeden Fall nahezu flächendeckend getestet werden sollte und bei dieser Vorgehensweise gerade Einsparungen bei unnötigem Code erfolgen. Das Hauptziel bleibt jedoch die größere Fehlerfreiheit und damit eine höhere Softwarequalität.
In den meisten Entwicklungsprojekten wird heute mehr oder weniger agil vorgegangen. Oft wird Scrum als Projektmanagementmethode angewendet. Für eine Darstellung des Ansatzes verweisen wir auf die umfangreiche Literatur zum Thema. Uns interessiert an dieser Stelle lediglich die Retrospektive in Projekten, die mittels Scrum geplant werden. Die Retrospektiven sind ein wichtiger Bestandteil der Projektarbeit – sie dienen dazu, den vorangegangenen Sprint zu analysieren, um sowohl aus Fehlern als auch aus positiven Geschehnissen zu lernen. Am Ende ist man immer schlauer als am Anfang. Diese Erkenntnis versucht man sich in Scrum zum Vorteil zu machen, weswegen das Reflektieren über die letzten Arbeitsschritte und die damit erzielten Ergebnisse einen hohen Stellenwert genießt. Dazu dient ein Meeting am Ende eines Sprints, in dem von allen Mitgliedern des Teams die wichtigsten Fakten ausgewertet werden. Das Verbinden von Theorie und Praxis steht im Mittelpunkt. Die Theorie zur Durchführung einer solchen gemeinsamen Veranstaltung klingt gut. Die Umsetzung scheitert in der Praxis oftmals am herrschenden Zeitdruck und am fehlenden Budget für diese Treffen. Gelegentlich fehlt auch die notwendige Einsicht. Schließlich kostet ein solches Review wertvolle Zeit; daher vertreten manche Beteiligte die Auffassung, dass man besser etwas anderes erledigen könnte. Eine retrospektive Betrachtung, bei der sich die Entwickler gemeinsam über die Erfahrungen und die neu erlangten Erkenntnisse des vergangenen Sprints austauschen, ist wichtig und sorgt für neue Synergien im Team. Dennoch empfinden viele Teammitglieder Retrospektiven als lästige Pflicht, deren Erfolg ihnen zweifelhaft erscheint. In der Tat gelingt es vielen Teams nicht, wirklichen Nutzen aus Retrospektiven zu ziehen. Sie verlieren dann die Motivation für ihre Durchführung und führen sie schließlich nur noch sporadisch durch oder verzichten darauf, um die Zeit für „Produktiveres“ zu nutzen.
Woran liegt das? Was will Scrum mit Retrospektiven erreichen? Im Gegensatz zu traditionellen Entwicklungsprozessen, die versuchen, den gesamten Projektablauf zu definieren, bleibt Scrum offen und beschreibt nur die Rahmenbedingungen. Dadurch muss der Entwicklungsprozess fortlaufend an die Bedürfnisse angepasst werden. Diese Anpassung soll mit der Durchführung von Retrospektiven gelebt werden. Hier ist Raum für die notwendigen Reflektionen. Es werden Verbesserungen für die laufenden Prozesse und das Produkt gefunden. Die Retrospektiven sind also der Schlüssel zum Erfolg, sie sollten daher nicht entfallen. Für eine erfolgreiche Durchführung muss der Scrum Master ein Gespür für das Zwischenmenschliche und die Visionen im Team haben und die Grundprinzipien der agilen Methoden, wie zum Beispiel Selbstorganisation, konsequent anwenden. Nutzen Sie das Feedback Ihrer Teammitglieder. Haben Sie keine Angst vor Kritik – in jedem Fehler finden Sie Potenzial für die Verbesserung.
Wie oft sollte eine Retrospektive durchgeführt werden? Die meisten erfolgreichen agilen Teams führen nach jeder Iteration, also alle zwei bis vier Wochen, Retrospektiven durch. Zusätzlich ist es aber oft hilfreich, auch in einem größeren Zeitabstand längere Retrospektiven durchzuführen, zum Beispiel alle drei bis sechs Monate. Die Durchführung umfasst die folgenden Phasen:
Wichtig: Eine Retrospektive ist kein „Kaffeekränzchen“, d. h., sie braucht eine genaue zeitliche Begrenzung. Je nach behandelten Themen, betrachteten Zeitintervallen und der Häufigkeit der Durchführung sollten Sie sich unbedingt klare zeitliche Limits setzen, zum Beispiel:
Die Teilnehmer müssen den Nutzen einer Retrospektive im Verhältnis zum Aufwand als positiv einschätzen. Zusammengefasst: Agile Methoden wie Scrum tragen dem Umstand Rechnung, dass Softwareentwicklung ohne Fehler nicht möglich ist. Die feste Verankerung von Retrospektiven in die Arbeitsabläufe soll die Qualität der laufenden und vor allem der kommenden Sprints verbessern. Wir lernen sozusagen aus den Fehlern – aber das geht nur dann, wenn die Fehler offen kommuniziert werden und man bereit ist, die Fehler gemeinsam im Team zu diskutieren. Dieser Weg des kontinuierlichen Verbesserungsprozesses (KVP) kann aber nur dann gelingen, wenn Sie in Ihrem Scrum-Team eine faire Arbeitsweise implementieren, d. h. beispielsweise, dass die geäußerte Kritik dem gemeinsamen Fortschritt dienen muss und nicht zur Schuldzuweisung an einzelne Personen werden darf.
Bisher wurden vielfältige technische Aspekte zum Vermeiden, Entdecken und Beseitigen von Fehlern aufgezeigt. Damit die Mitarbeiter aktiv gegen Fehler vorgehen und langfristig ein Lerneffekt aus den gemachten Fehlern entsteht, muss man die eingangs beschriebenen, negativen Einstellungen gegenüber Fehlern in eine möglichst positive Fehlerkultur im Unternehmen wandeln. Bestandteile einer solchen positiven Fehlerkultur sind folgende Merkmale (mediapp.ch, t3n.de):
Kultur und damit auch Fehlerkultur meint das nicht Sichtbare in einem Unternehmen. Das hat zur Folge, dass Sie den Weg zu einer besseren Fehlerkultur im Team auch nicht vorschreiben können. Diese muss unbedingt vom Management vorgelebt werden.
Software ohne Fehler bleibt wohl weiterhin ein Traum. Mit einer gesunden Fehlerkultur und einem umfassenden Testmanagement gelingt es jedoch, viele Fehler von unseren Kunden fernzuhalten. Im Übrigen sind Produktnachbesserungen in vielen Branchen – in der Softwareentwicklung in Form von Updates – mittlerweile auch durch die Nutzer akzeptiert. Der Nutzer weiß, dass es keine vollständig fehlerfreien Anwendungen gibt.