Softwareentwicklung: Verwaltung zuerst – aber wie steht es um Softwarequalität?

In den letzten Jahren habe ich in verschiedenen Unternehmen gearbeitet, die zwar unterschiedlich waren, aber mit ähnlichen Problemen konfrontiert waren. Mit diesem Text möchte ich meine Perspektive auf diese Herausforderungen teilen.

Als Softwareentwickler und Administrator habe ich an zahlreichen Projekten im Softwarebereich, in der Administration sowie im Aufbau von Netzwerken und Client-Server-Strukturen gearbeitet. Einige dieser Projekte habe ich selbst geleitet, andere in Zusammenarbeit mit verschiedenen Personen betreut.

In meinen letzten Positionen war ich jedoch hauptsächlich auf die Programmierung beschränkt. Ich hatte keine Entscheidungsbefugnis und konnte mein Wissen und meine Expertise nicht sinnvoll einbringen. Stattdessen führte ich eine monotone und ermüdende Fließbandarbeit aus. Diese Erfahrung ermöglichte es mir jedoch, die Probleme aktueller Softwareunternehmen zu beobachten, die in einem ewigen Kreislauf der Selbstoptimierung gefangen sind, aber im IT-technischen Kernbereich keine bedeutenden Fortschritte erzielen.

Zwei Hauptthemenbereiche traten in diesen Unternehmen hervor (nicht in jedem, aber mindestens in einem):

  1. Die Notwendigkeit, agil zu werden und zu bleiben.
  2. Die Implementierung von CI/CD um jeden Preis.

Für die meisten klingen diese beiden Punkte sinnvoll, und in gewisser Weise sind sie es auch, insbesondere der zweite Punkt. Die Umsetzung dieser Ansätze erfolgte jedoch nicht als strukturierter Prozess, sondern eher als eine übergeordnete Aufgabe, die den Fokus von den wirklich wichtigen Aufgaben ablenkte.

Beginnen wir mit dem Thema Agilität. Was neue Softwarefirmen (Startups) von Anfang an praktizieren, wird in etablierten Unternehmen oft nur mühsam eingeführt. Ich erinnere mich noch gut daran, wie wir uns in einem Unternehmen, das von technischen Schulden geplagt war, ausschließlich auf die Einhaltung agiler Prinzipien konzentrierten, anstatt die eigentlichen Probleme anzugehen. Das bedeutete, dass wir mit Hochdruck Tools einführten und Schulungen durchführten, die die Mitarbeiter hauptsächlich beschäftigten. Da es Architekten, Product Owner und Scrum Master und so weiter gab, wollte kaum noch jemand Techniker sein, sondern einen solchen Titel tragen und Tickets und Epics durch die Gegend schieben. Das Softwareprodukt selbst blieb dabei auf der Strecke.

Wir hatten mehrere Schulungen mit einem Unternehmen, das sich meiner Meinung nach auf die Vermittlung esoterischer Agilität spezialisiert hatte. Alle zwei Wochen fand die Retrospektive statt, in der jeder seine Gefühle offenbaren musste und wir uns an wilden (Kinder-)Spielen wie „Wer ist schneller mit dem Mauszeiger?“ beteiligten. Wichtige Themen wie Bugs, Features, Refactoring, Neuausrichtung, Ideen und die Erweiterung unseres technischen Wissens wurden dabei völlig vernachlässigt. Stattdessen mussten wir uns gegenseitig loben und betonen, wie großartig unser Team sei. Das Produkt blieb oft ein einziges Chaos, und mir war die ganze Nummer mehr als peinlich.

Fahren wir mit CI/CD fort. Obwohl ich es für wichtig halte, ist es nicht so entscheidend wie das Produkt selbst. In einer Firma musste ich ein Frontend entwickeln, das zwei Kommandozeilenprozesse startete und überwachte. Es war eine relativ einfache Software, die schnell geschrieben werden konnte. Tatsächlich musste ich sie zweimal schreiben. Zuerst verwendete ich Qt und C++, was zu einem funktionsfähigen, modernen und effizienten Produkt führte. Es wurde jedoch entschieden, dass es einfacher sein und lieber mit wxWidgets geschrieben werden sollte, da die Firma bereits Erfahrung mit dieser Technologie hatte. Daher begann ich mit dem Schreiben einer neuen Version. Obwohl der Funktionsumfang geringer war, dauerte die Entwicklung aufgrund der geringeren Abstraktion von wxWidgets länger. Schließlich war das Produkt fertig, funktionsfähig und bereit für die Auslieferung. Es handelte sich um eine relativ kleine Codebasis von etwa 4.000 Zeilen C++. Nachdem ich das Produkt fertiggestellt hatte, hatte ich aufgrund von schlechtem Management (man war ja mit Agilität beschäftigt) nichts mehr zu tun. Man schlug vor, dass ich mich mit dem Testing befassen sollte, da das Produkt in CI/CD integriert werden musste. Dies umfasste drei Hauptaspekte:

  1. Unit- und Integrations-Tests mit Google-Test
  2. Statische Codeanalyse mit SonarQube
  3. Automatische GUI-Tests

Ich habe mich dann über ein halbes Jahr ausschließlich mit diesem Projekt beschäftigt. Während die Unit- und Integrations-Tests noch relativ einfach zu handhaben waren, erwies es sich als deutlich schwieriger, Anwendungsfälle für eine Software mit über 4.000 Zeilen Quellcode zu entwickeln, von denen der Großteil GUI-bezogen war. Das agile Testmanagement verlangte eine Code-Coverage von mindestens 70 %. Anstatt mich also auf die eigentliche Kernsoftware zu konzentrieren, die dieses von mir entwickelte Frontend lediglich startete und überwachte, widmete ich mich der ABM und der Erstellung von Tests, die letztendlich keinen wirklichen Zweck erfüllten. Der Grund dafür ist einfach: Die Software war funktionsfähig, sollte nicht weiterentwickelt werden und war nicht anfällig für Fehler. Sie war ausgebaut und lediglich im Bestand. Darüber hinaus sollte sie in naher Zukunft durch eine andere Software ersetzt werden.

Trotz dieser Umstände durfte ich mich weiterhin mit der Umsetzung der teils eigenwilligen Standard-Regeln von SonarQube befassen. In den gesamten 4.000 Zeilen Code entdeckte SonarQube lediglich einen einzigen Bug (einen gelöschten Pointer, der in einem anderen Codesegment erneut aufgerufen wurde). Die restlichen Hinweise bezogen sich auf unbedeutende Aspekte wie die Anzahl der Member-Variablen und die Begrenzung der Methodenanzahl pro Klasse auf 30.

Da die Software auch zwei klickbare Buttons enthielt, musste ich vollautomatische GUI-Tests implementieren. Dies stellte sich bei einer wxWidgets-Anwendung mit C++ als Herausforderung dar, konnte aber erfolgreich gemeistert werden. Dadurch wurde das gesamte GUI vollautomatisch testbar.

Obwohl dies die Software, die im Mittelpunkt stand, nicht direkt voranbrachte, fügte es doch ein weiteres Puzzleteil zur Pipeline hinzu. Ich konnte wertvolle Erkenntnisse gewinnen, die ich bei zukünftigen Softwareprojekten, wo es sinnvoll ist, anwenden kann.

In einer anderen Firma war ich an zwei oder drei Softwareteilen beteiligt. Auch hier wurde, im Zuge des Wachstums des Produkts, zunehmend Wert auf die Pipeline gelegt. Folglich mussten zahlreiche Tests nachimplementiert werden. Dies war nur bedingt sinnvoll, da drei wesentliche Probleme vorlagen:

  1. Die Qualität des Codes und insbesondere der gesamten Architektur war mangelhaft, wenn nicht gar nicht vorhanden.
  2. Es gab Fachverfahren, die eine umfassende Einarbeitung erforderten oder eine gezielte Einweisung notwendig gemacht hätten.
  3. Die Software sollte vollständig durch eine neue Version in einer anderen Programmiersprache ersetzt werden. Diese neue Architektur war jedoch ebenfalls fehlerhaft und entsprach nicht den Qualitätsstandards, die ich vertrete.

Einige der Tests hätten durchaus auch für die zweite Generation der Software verwendet werden können, was sinnvoll war, um das Verhalten der alten und neuen Software zu vergleichen. Doch meiner Meinung nach war das Projekt dazu verdammt, gegen die Wand zu fahren.

Unit-Tests, Integrationstests, Code-Reviews, CI/CD, statische Codeanalyse, GUI-Tests, Dokumentation, nachverfolgbare Ticketbearbeitung, Kompetenztransfer und vieles mehr sind entscheidend für eine nachhaltige Softwareentwicklung. Diese Elemente sollten jedoch von Anfang an integriert sein. Sie später, insbesondere bei großen Projekten, einzuführen, ist ineffektiv, vor allem bei komplexen Projekten mit Hunderttausenden von Codezeilen. Darüber hinaus können andere technische Schulden so hoch sein, dass sie dringendere Aufmerksamkeit erfordern.

Ich habe es immer wieder erlebt, dass Unternehmen weiterhin auf veraltete Frameworks setzen. Ich bin auf Systeme gestoßen, die 20 Jahre alt oder noch älter waren und längst nicht mehr unterstützt wurden. Oder auf Plattformen, die über 20 Jahre alt waren und auf denen immer noch die Software für aktuelle Systeme kompiliert wird, weil die Migration auf aktuelle Plattformen vernachlässigt wurde.

Doch das ist nicht alles. Die Qualität von Software-Code ist ein entscheidender Faktor in der Softwareentwicklung. Ich habe Ein-Personen-Projekte erlebt, die aufgrund ihrer zunehmenden Komplexität oder der Sorge um die langfristige Verfügbarkeit des ursprünglichen Entwicklers zusätzliche Teammitglieder erforderten. Diese neuen Teammitglieder haben jedoch oft Schwierigkeiten, den Code, die Fachverfahren und das gesamte System zu verstehen.

Aber warum ist das so? Ein Ein-Personen-Projekt hat von Natur aus seine Grenzen. Der ursprüngliche Entwickler versteht den Code vielleicht noch, aber das gilt nicht unbedingt für andere. In den letzten Jahren bin ich immer wieder auf schlecht geschriebenen Code gestoßen. Schon die Verwendung von Funktionen, Methoden oder Variablennamen wie „reg_rq_fachver2_23“ ist wenig hilfreich. Darüber hinaus habe ich unglaublich redundanten Code gesehen, wie zum Beispiel:

int za_wwggw_w2(int p, int p_k, int p_key, int k, int key, char *pk) {
int _key;
struct pkey *pkey_p;
return za_wwggw_w3(p, p_k, p_key, k, key, pk, &_key, pkey_p);
}

Selbst nach mehrmaligem Lesen verstehe ich diesen Code nicht. Wenn ich mich durch etwa 50 Dateien mit mehreren zehntausend Zeilen Code kämpfen müsste, der genauso oder noch schlimmer aussieht, würde ich irgendwann aufgeben.

Es gab Software, bei der GUI-Komponenten mit Namen wie Edit1, Edit2, Edit3 bis Edit123 versehen waren. Diese Praxis führte zu immensen technischen Schulden, die ich nicht erklären kann. Schließlich ist allgemein bekannt, dass sauberer und lesbarer Quellcode die Notwendigkeit von Dokumentation minimiert (die oft ohnehin nicht vorhanden ist), das Bugfixing und die Wartbarkeit erhöht sowie die Einarbeitung anderer Menschen vereinfacht.

Betrachten wir das Projekt mit der Methode za_wwggw_w2. Es war offensichtlich, dass das Projekt unkontrolliert gewachsen war und eine Reimplementierung erforderlich war. Meiner Meinung nach war dies der einzige praktikable Ansatz, um das Projekt weiter auszubauen. Allerdings sah ich sofort mehrere Probleme:

  1. Die Reimplementierung wurde nicht als eine vollständige Neubegründung des Projekts verstanden, was angesichts seiner Komplexität eine gewaltige Aufgabe gewesen wäre. Stattdessen wurde sie als eine Portierung des alten Codes auf eine neue Programmiersprache wahrgenommen, mit minimalen Anpassungen, um die Besonderheiten der neuen Sprache zu berücksichtigen. Als ich den Code der anderen Programmierer untersuchte, stellte ich fest, dass er fast identisch mit dem alten Code war, mit nur wenigen geringfügigen Änderungen. Die zugrundeliegenden Probleme blieben bestehen, und ohne jemanden direkt verantwortlich machen zu wollen, würde ich behaupten, dass die meisten, die an dieser Umsetzung beteiligt waren, sie nicht vollständig verstanden.
  2. Darüber hinaus wurde im Prozess keine Architektur implementiert. Da die neue Programmiersprache eine einfache Integration von Code aus dem alten System ermöglichte, wurde diese Funktion häufig genutzt.
  3. Es wurde eine Masse an „gleichem“ Code automatisch generiert, anstatt auf Abstraktion, Ableitung und Konventionen zu setzen. Viele tausende Zeilen gleicher Code mit leicht unterschiedlicher Logik wurde generiert.

Leider wurden die gleichen Fehler gemacht wie bei dem Ein-Personen-Projekt, obwohl das Team aus erfahrenen Programmierern bestand, die jahrelange und jahrzehntelange Erfahrung mitbrachten. (Ich kam relativ spät dazu und wurde hauptsächlich als „Programmieräffchen“ eingestellt.)

Diese Erfahrung habe ich in zahlreichen Projekten gemacht. In einem Projekt, das stark auf eine Datenbank angewiesen war, deaktivierte mein Vorgänger die referentielle Integrität mit der Begründung, es würde die Leistung verbessern. Dies führte jedoch dazu, dass die Datenbank in einen fehlerhaften Zustand geraten konnte (was auch mehr als nur einmal passierte). Solche Probleme hätten leicht behoben werden können, indem beispielsweise ein zuverlässiges Datenbankmanagementsystem (DBMS) verwendet oder qualitativ hochwertiger Code geschrieben worden wäre. Trotzdem wurden diese Maßnahmen nicht ergriffen.

Meine Botschaft ist klar: Anstatt sich ausschließlich auf das Umfeld zu konzentrieren, würde es vielen Produkten zugutekommen, wenn ein stärkerer Fokus auf die Produkte selbst gelegt würde. Auch vorher Gedanken zu machen und Erfahrungen von Menschen einzubringen, die das seit Jahren/Jahrzehnten bereits machen, ist absolut sinnvoll.

Schreibe einen Kommentar