Nachhaltige Verbesserung der Codequalität

Mehr Zeit für neue Features statt nerviger Bugs
Keine Kommentare

Der Frust steigt und die Produktivität nimmt rapide ab, wenn Entwicklerteams immer mehr Zeit für die Behebung von Bugs investieren müssen. Mit den richtigen Maßnahmen, die an der Ursache ansetzen, kann das Ruder im Kampf gegen die Fehlerflut herumgerissen werden.

Laut einer Umfrage von Tidelift investieren Entwicklerinnen und Entwickler im Durchschnitt zwischen 11 % und 50 % ihrer Arbeitszeit für die Wartung von Code. Ab welchem Maß genau von „zu viel“ gesprochen werden kann, lässt sich sicherlich nicht pauschal festlegen. Fakt ist jedoch, dass jede Minute, die für Fehlerbehebung investiert wird, nicht zur Weiterentwicklung von Funktionen in der Software beitragen kann – und nur damit wird schließlich Geld verdient.

Zunehmende Softwarekomplexität

Oft steigt der Aufwand für die Wartung von Software mit ihrem zunehmenden Alter. Während in frühen Phasen des Lebenszyklus tendenziell eher weniger Fehler auftreten, nimmt ihre Zahl zu, je mehr Funktionen ihr hinzugefügt wurden und je mehr Entwickler an ihr arbeiten. Die Komplexität steigt dadurch kontinuierlich an. Wie gut ihr Wachstum kontrolliert werden kann, ist unter anderem von der Softwarearchitektur, der Codequalität und den Entwicklungsprozessen abhängig. Wurden die Pflichten in diesen Bereichen über einen längeren Zeitraum vernachlässigt, wird das unweigerlich zu einer steigenden Fehleranfälligkeit der Software führen. Je länger das geschieht, desto schwieriger wird es, dieser Situation entgegenzuwirken und zu einem normalen Fehlerniveau zurückzukehren. In besonders extremen Fällen kann eine Art Machtlosigkeit entstehen, die dazu führt, dass Änderungen am Code zu völlig unerwarteten Folgereaktionen der Logik führen und Fehlerbehebungen neue Fehler hervorrufen.

Solche frustrierenden Situationen können Unternehmen sehr teuer zu stehen kommen oder regelrecht gefährlich werden, wenn die Konkurrenz ihre Hausaufgaben gemacht hat und neue Produkte auf den Markt bringt, während der Rivale mit seinen Fehlern kämpft. Es liegt auf der Hand, dass es das Beste wäre, von Beginn an alles dafür zu tun, damit es gar nicht erst so weit kommen kann. Doch was, wenn es dafür einfach zu spät ist? Wie können Teams aus diesem Dilemma wieder herausgelangen? Nun, eines sei vorweggenommen: Was über lange Zeit an Fehlerpotential herangezüchtet wurde, kann nicht in kurzer Zeit wieder abgebaut werden. Um der Lage wieder Herr zu werden, braucht es Maßnahmen, die zu nachhaltigen Verbesserungen der Codequalität und der Qualitätssicherung führen – in diesem Artikel schauen wir uns einige Möglichkeiten an.

Bevor es losgeht: Wie schlimm ist es?

Doch noch bevor über eigentliche Verbesserungen nachgedacht wird, sollte zunächst der aktuelle Stand in Sachen Softwaremängel festgestellt werden. Dass zu diesem Zweck unbedingt alle Bugs erfasst werden müssen, ist selbstverständlich und wurde bereits im ersten Teil des Artikels erläutert. Aus diesem Datenbestand und den im Prozess gesammelten Informationen lassen sich dann einige Kennzahlen ableiten. Am Verlauf dieser Kennzahlen lässt sich ablesen, in welche Richtung sich die Situation entwickelt und ob die Maßnahmen ihre Wirkung entfalten. Das hilft bei Entscheidungen, um einerseits dem Fehler-GAU zuvorzukommen und andererseits nicht in einen fruchtlosen Optimierungswahn zu verfallen. Typische Kennzahlen in diesem Zusammenhang sind:

  • Anzahl an Bugs getrennt nach Priorität
  • Dauer von Erfassung bis Behebung in Tagen
  • Aufwand für die Behebung in Stunden oder bei Scrum-Teams in Story Points
  • Entstandener Schaden, sofern sich dieser beziffern lässt, beispielsweise in der Dauer einer Downtime, Anzahl betroffener Kunden oder Anzahl entgangener Buchungen

Viele Bug-Tracker und Projektmanagement-Tools haben bereits von Haus aus Lösungen im Gepäck, um solche Kennzahlen erfassen zu können, sofern das überhaupt notwendig ist und nicht auf ohnehin vorhandene Daten zurückgegriffen werden kann. Von nun an muss diese Datenquelle also nur noch regelmäßig angezapft werden.

Mit Insiderinformationen schneller zur Lösung

Erfahrene Entwickler kennen solche hartnäckigen Fehler, deren Ursachen zunächst völlig unerklärlich erscheinen. Das Fehlerbild ergibt, so wie es sich darstellt, keinen Sinn, und es muss regelrecht Detektivarbeit geleistet werden, bis Stück für Stück nach zeitaufwendiger und mühsamer Analyse endlich Licht ins Dunkel gelangt. Solche Fehler kosten viele Ressourcen und strapazieren nicht nur die Nerven der Nutzer. Wenn mittels Logging vor und beim Auftreten des Fehlers wertvolle Informationen gesammelt werden, lassen sich diese Analysen in der Regel deutlich abkürzen, weil Rückschlüsse auf die Fehlerursache schneller möglich sind. Die dadurch eingesparte Zeit kann deutlich sinnvoller eingesetzt werden, um nicht nur einem Fehler, sondern allen Fehlerrisiken entgegenzuwirken – doch dazu gleich mehr.

Ein gut durchdachtes Error-Logging-Konzept gehört folglich immer zu den Pflichtaufgaben in jedem mittelgroßen Softwareprojekt und ist obendrein auch noch mit verhältnismäßig geringem Aufwand umsetzbar. Ob man dabei auf die Unterstützung von bekannten Frameworks wie Apache Log4j oder NLog, auf Bordmittel des Betriebssystems oder auf die gute alte Textdatei setzt, ist von nicht allzu großer Bedeutung, sofern die Lösung der Häufigkeit der Fehler gerecht wird und unkompliziert ausgewertet werden kann. Viel wichtiger ist da schon, dass die Aufzeichnung möglichst lückenlos, das heißt während des gesamten Laufzeitzyklus der Anwendung vom Start bis zu ihrer Beendigung, funktioniert und dabei alle relevanten Informationen aufzeichnet, die Rückschlüsse auf die Geschehnisse zulassen. Dazu gehören unter anderem:

  • Datum und Uhrzeit
  • Stacktrace
  • Informationen über das Endgerät
  • Eingaben des Benutzers
  • Laufzeitinformationen, z. B. Softwareversion oder Auslastung der Hardwareressourcen

Über das Error-Logging hinaus kann es auch sinnvoll sein, präventiv Informationen aufzuzeichnen, um beispielsweise Informationen für Vergleichsszenarien zu generieren. Das muss jedoch immer nach Anwendung und Risikosituation abgewogen werden, denn neben dem, dass ein solches Logging Hardwareressourcen verbraucht, muss es auch immer im Einklang mit dem Datenschutz stehen. Neben der gebotenen Datensparsamkeit ist auch unbedingt auf die Sicherheit der erhobenen Daten zu achten. Fälle, in welchen durch einen Log Passwörter preisgegeben wurden, gab es in der Geschichte schon einige, und daher sollte eine Anonymisierung besonders sensibler Informationen unbedingt berücksichtigt werden.

Hohe Komplexität als Treiber von Fehlerrisiken

Die wahrscheinlich häufigste Ursache für hohes Fehleraufkommen ist, wie bereits erläutert, die hohe Komplexität des Source Codes, der von den Entwicklern aufgrund seiner Unübersichtlichkeit und mangelnden Struktur nicht mehr vollständig nachvollzogen werden kann. In diesem Umfeld von technischer Schuld müssen auf Basis von teils sehr vagen Annahmen Schlussfolgerungen getroffen werden, die nicht immer richtig sind und dann zu Fehlern führen. Treiber einer solchen gefährlichen Komplexität sind ausgesprochen vielfältig. Ein regelmäßig auftretendes Problem sind beispielsweise schlecht gewählte Namen für Variablen, Methoden, Klassen sowie andere Konstrukte im Code, die nichtssagend, verwechselbar oder schlicht falsch sind. Ebenso führen eine hohe Kopplung, Methoden oder Klassen, die mehrere Zwecke erfüllen sowie Defizite im Datenmodell zu Komplexität, die viele Risiken birgt. Ich könnte diese Liste noch lange weiterführen und die Dimensionen solcher Risken deuten bereits an, dass dieses Problem nicht in Kürze aus der Welt geschafft werden kann. Eine solcher Berg an unnötiger Komplexität baut sich in der Regel über einen längeren Zeitraum auf und kann nur mit Gegenmaßnahmen bekämpft werden, die eine gehörige Portion Durchhaltevermögen erfordern.

Es ist dringend anzuraten, dass die Gegenmaßnahmen nicht dem Team vorgeschrieben, sondern vom gesamten Team gemeinsam und Schritt für Schritt erarbeitet werden. Meistens wissen Entwickler ganz genau, an welchen Stellen der Schuh drückt und haben sogar konkrete Ideen für bessere Lösungen. Sie setzen diese aber nicht um, weil ihre Mühe als Einzelkämpfer verpuffen oder ihr Alleingang Konflikte mit anderen Teammitgliedern provozieren könnte. Verständigt sich dagegen das Team auf konkrete Maßnahmen, entstehen ein Konsens und ein Ziel, auf das gemeinsam hingearbeitet wird. Zu solchen konkreten Maßnahmen zählen beispielsweise einheitliche Namenskonventionen, bessere Dokumentation, die Etablierung von Clean-Code-Prinzipien oder der Einsatz von Dependency Injection. All das sind jedoch Maßnahmen, die nach ihrem Beschluss konsequent und mit viel Disziplin umgesetzt werden müssen. So wie beschlossene Maßnahmen zur Kostenreduktion in einem Unternehmen erfordern auch Maßnahmen zum Abbau unnötiger Komplexität Geduld und den Einsatz von Ressourcen, bis sie ihren Nutzen voll entfalten können. Und daher sei nicht zuletzt empfohlen, auch Stakeholder außerhalb des Teams im Produktmanagement, dem Management und anderen Funktionen in den Plan einzuweihen und auf diesen Weg mitzunehmen.

Qualitätssicherung ist das Rückgrat qualitativ hochwertiger Software

Bei aller Disziplin werden sich Fehler jedoch nie gänzlich vermeiden lassen. Neben dem übergeordneten Ziel, sie bestmöglich zu verhindern, ist es daher auch von essentieller Bedeutung, sie – wenn sie denn auftreten – möglichst früh zu erkennen. Die Maxime gelungener Qualitätssicherung lautet: Möglichst alle Fehler möglichst früh zu erkennen. Je später im Entwicklungsprozess ein Fehler erkannt wird, desto höher werden die Kosten für seine Behebung ausfallen. Mit jeder Stufe steigt der Aufwand für Analyse und Kommunikationen, ebenso wie das Risiko für den potenziellen Schaden wie verschobene Release-Termine oder gar Beeinträchtigung des Benutzererlebnisses nach dem Release.

Wie also könnte ein solcher Qualitätssicherungsprozess aussehen? Generell orientiert er sich natürlich immer an den individuellen Gegebenheiten des Produkts und des Teams. Die folgenden Schritte sind auf viele dieser Fälle übertragbar und sollten als Anregung verstanden werden:

  1. Entwicklerselbstkontrolle: Alle Entwickler können durch eine diszipliniert und routiniert ablaufende Kontrolle ihres geänderten Codes vor dem Commit sicherstellen, dass dieser vollständig, richtig und frei von Flüchtigkeitsfehlern ist.
  2. Automatisierte Tests: Code-gestützte und automatisierte Tests wie Unit-Tests, Snapshot-Tests, Integrationstests etc. tragen zu massiver Qualitätssteigerung bei, indem sie unbeabsichtigte Auswirkungen durch Änderung von bestehendem Source Code verhindern.
  3. Code Review: Getreu dem Motto „Vier Augen sehen mehr als zwei“ sollten Code Reviews immer in kleinen Schritten nach wenigen Stunden Entwicklungsarbeit durchgeführt werden, um insbesondere Abweichungen von Konventionen, Schwachstellen in der Softwarearchitektur sowie fachliche Mängel frühzeitig korrigieren zu können.
  4. Manueller Test: Bei aller erstrebenswerter Automatisierung kann und sollte auf manuelle Tests durch Menschen in der Regel nicht verzichtet werden. Sie decken mit der Brille des Benutzers optische Makel, unzureichende Texte, Defizite in der Benutzererfahrung und Mängel in der Umsetzung von konkreten Anwendungsfällen auf.
  5. Logging im Produktivsystem: Nach dem Release ist vor dem Fehler: Um möglichst noch bevor Benutzer es bemerken auf Fehler reagieren zu können, sollte das Logging im Live-Betrieb eine Selbstverständlichkeit sein.

Wie schon beim Kampf gegen unnötige Komplexität sollte auch bei der Festlegung des Qualitätssicherungsprozesses das gesamte Entwicklungsteam miteinbezogen werden, indem zum einen Anregungen mit eingebracht werden können und zum anderen Zuständigkeiten sowie Kommunikationswege für jede Stufe klar bestimmt werden. So muss beispielsweise möglichst konkret festgelegt werden, welchen Umfang ein Code Review hat, das heißt, ob es über eine reine Prüfung der Einhaltung von Konventionen hinaus geht und ein praktischer Funktionstest oder eine Kontrolle der automatisierten Tests auch dazu gehören. Sobald dabei ein Fehler auftritt, muss festgelegt sein, ob er vom Reviewer oder vom Entwickler persönlich behoben wird.

Ein solcher Prozess lebt davon, dass alle Beteiligten für ihre Verantwortung einstehen, das heißt, dass jeder in seinem Zuständigkeitsbereich höchste Disziplin walten lässt. Doch wenngleich auch Zuständigkeiten möglichst präzise festgelegt werden, müssen alle Beteiligten stets über den eigenen Tellerrand hinausblicken. Ein pauschales „dafür bin ich nicht zuständig“ ist der Tod qualitativ hochwertiger Softwareprodukte, denn eine Zuständigkeit im Sinne von „aufmerksam sein und auf Missstände aufmerksam machen“ betrifft grundsätzlich alle Teammitglieder.

Fazit

Wer das Fehlerniveau seiner Softwareprodukte reduzieren möchte, sollte sich vor dem Ergreifen von Optimierungsmaßnahmen einen Überblick über den Ist-Zustand verschaffen und mittels Error-Logging dafür sorgen, dass der Aufwand beim Beheben von Fehlern möglichst klein ausfällt. Großes Fehlerpotential entsteht häufig aus einer unnötig hohen Komplexität durch qualitativ schlechten Code, der vom gesamten Team mit einheitlichen Konventionen, Verbesserungen in der Architektur und viel Disziplin bekämpft werden muss. Unterstützt wird das von einer ausgereiften Qualitätssicherungsstrategie, die dafür sorgt, dass Fehler – die trotz aller Bemühungen immer wieder auftreten – im besten Fall möglichst früh entdeckt werden.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Abonnieren
Benachrichtige mich bei
guest
0 Comments
Inline Feedbacks
View all comments
X
- Gib Deinen Standort ein -
- or -