Im Jahr 2023 habe ich auf der Konferenz code.talks über die Entwicklung einer von Clean Architecture inspirierten React-Anwendung mit MVVM gesprochen.
Ein Teil dieses Vortrags war die Vorstellung von unterschiedlichen Problemen, denen man während der Entwicklung einer Anwendung und ihres Lebenszyklus begegnen kann.
Der Vortrag ist als Video auf YouTube zu finden. Bei Interesse klicken Sie einfach auf den folgenden Link: https://www.youtube.com/watch?v=v3HkasWQppk
Probleme bei der Softwareentwicklung
Während der Entwicklung einer Anwendung oder später in ihrem Lebenszyklus, zu dem auch ihre Wartung gehört, können verschiedene Probleme auftreten. Wenn Sie sich dieser Probleme nicht bewusst sind und nicht entsprechende Maßnahmen zur Vorbeugung treffen, kann es zu Budgetüberschreitungen, Zeitverzögerungen, Qualitätseinbußen oder im schlimmsten Fall zu einem gescheiterten Softwareprojekt kommen.
In den folgenden Abschnitten gehen wir auf eine Reihe von Problemen ein, geben Beispiele dafür, wie sie auftreten können, und erörtern mögliche negative Auswirkungen. In der Schlussfolgerung geben wir schließlich einen Hinweis darauf, wie man diesen Problemen vorbeugen kann.
Codeduplikationen
Wenn eine Codesequenz mehr als einmal in einer Software vorkommt, sprechen wir von Codeduplikationen. Diese Duplikate können in verschiedenen Situationen auftreten. In der folgenden Liste sind einige Beispiele aufgeführt:
- Um Zeit zu sparen, könnte man Code in mehrere Teile der Anwendung kopieren und einfügen, anstatt eine wiederverwendbare Funktion zu erstellen.
- In einer großen und komplexen Codebasis schreiben Entwickler:innen möglicherweise unabhängig voneinander ähnliche Funktionen, was zu mehreren Versionen derselben Logik führt.
- Eine unzureichende Kommunikation innerhalb eines Teams kann auch dazu führen, dass verschiedene Entwickler:innen unabhängig voneinander denselben Code schreiben.
Duplikate haben einen negativen Einfluss auf ein Softwareprojekt, unter anderem:
- Erhöhte Wartungskosten, da Sie bei jeder Änderung Duplikate aufspüren und pflegen müssen, was teuer und schwierig ist.
- Höheres Risiko von Fehlern, da ein Fehler in Codeduplikaten eingeführt werden kann. Die Fehler in den Kopien können zunächst unbemerkt bleiben und später Probleme verursachen.
- Geringere Codequalität, da duplizierter Code weniger organisiert und wartbar ist als gut strukturierter Code.
- Geringere Produktivität der Entwicklerteams, da diese viel Zeit für die Pflege und das Debugging von Codeduplikaten aufwenden müssen.
Enge Kopplung
Enge Kopplung liegt vor, wenn verschiedene Komponenten einer Software in hohem Maße voneinander abhängig sind.
Beispiele für enge Kopplungen sind:
- In einem Softwaresystem haben wir eine Klasse
Klasse A
, die direkt von einer anderen KlasseKlasse B
abhängt. Wenn sichKlasse B
ändert, müssen wir auchKlasse A
ändern. - Ein bestimmtes Anliegen ist über mehrere Komponenten verteilt, anstatt eine eigene Komponente zu haben.
Ein eng gekoppeltes System kann mehrere Nachteile haben:
- Das Adaptieren von sich ändernden Anforderungen oder das Hinzufügen neuer Funktionen wird erschwert, da die Änderung einer Komponente erhebliche Auswirkungen auf andere Komponenten haben kann.
- Komponenten sind schlechter wiederverwendbar, wenn sie mit anderen gekoppelt sind.
- Eng gekoppelte Systeme sind komplex und können schwer zu verstehen sein, was das Risiko von Fehlern erhöht. Wenn eine Komponente ausfällt, können auch andere ausfallen, was zu einem Dominoeffekt führen kann, der das gesamte System zum Einsturz bringen kann.
- Das Testen wird komplexer, da die Komponenten nicht isoliert getestet werden können. Stattdessen muss man beim Testen auch die gekoppelten Komponenten berücksichtigen.
- Die Entwicklungskosten können steigen, da Änderungen an Kernkomponenten auch Aktualisierungen an anderen Komponenten erforderlich machen können.
Skalierbarkeit - eine Herausforderung
Ein skalierbares System ist ein Softwaresystem, das sich an eine erhöhte Last und einen erhöhten Datenverkehr anpassen kann, ohne an Leistung oder Zuverlässigkeit einzubüßen. Diese Systeme werden entwickelt, um sicherzustellen, dass sie anspruchsvolle Arbeitslasten bewältigen können, den Anforderungen wachsender Unternehmen gerecht werden und ihren Benutzer:innen zuverlässige und effiziente Dienste bieten. Solche Systeme sind dazu in der Lage, nach oben und unten zu skalieren. Es können also mehr Ressourcen genutzt werden, sobald die Last steigt. Sollte die Last sinken, können die Ressourcen auch wieder reduziert werden. Dies kann die Kosten senken.
Beispiele für Softwaresysteme, bei denen die Skalierbarkeit ein Problem darstellen könnte, sind:
- Eine E-Commerce-Website verzeichnet während eines Sonderverkaufs einen starken Anstieg des Datenverkehrs. Der Server, der nicht für eine Skalierung ausgelegt ist, ist überlastet, was zu langsamen Antwortzeiten oder sogar Ausfällen führt.
- Eine Social-Media-Plattform speichert alle Benutzerdaten in einer einzigen Datenbank. Je mehr Nutzer:innen die Plattform nutzen, desto mehr wird die Datenbank zu einem Engpass und verursacht Leistungsprobleme. Dies kann eine komplexe und kostspielige Migration auf eine skalierbarere Lösung erforderlich machen.
Die Entwicklung eines skalierbaren Systems kann eine Herausforderung darstellen, da ein solches System leicht skalierbar sein muss und Lastspitzen bewältigen muss, ohne abzustürzen oder langsamer zu werden. Die Sicherstellung der Datenkonsistenz in einem solchen System ist eine zusätzliche Herausforderung, da die über mehrere Server verteilten Daten konsistent bleiben müssen, was eine sorgfältige Koordination zwischen den Servern erfordert.
Je komplexer und verteilter ein Softwaresystem wird, desto anfälliger wird es für Sicherheitsangriffe oder -verletzungen. Die Umsetzung von Sicherheitsmaßnahmen zum Schutz des Systems und seiner Benutzer:innen ist von entscheidender Bedeutung, auch wenn dies nicht immer trivial ist.
Es mag schwierig sein, die genannten Herausforderungen zu meistern, aber ihre Bewältigung ist entscheidend für den Erfolg eines Softwaresystems.
Testkomplexität
Die Testkomplexität ist der Schwierigkeitsgrad, der mit dem Testen eines Softwaresystems verbunden ist. Verschiedene Faktoren wie die Größe und Komplexität des Softwaresystems, sowie die Verfügbarkeit von Testwerkzeugen und Ressourcen können diese Komplexität beeinflussen.
Beispiele, bei denen wir mit Testkomplexität konfrontiert werden, sind:
- Das Erreichen einer vollständigen Testabdeckung komplexer Funktionen mit vielen bedingten Verzweigungen erfordert eine große Anzahl von Testfällen. Das macht das Testen zeitaufwändig und anfällig für fehlende Randfälle.
- Das Schreiben von Unit-Tests für ein einzelnes Modul in einem eng gekoppelten System kann schwierig sein, da sie das Mocking aller anderen Modulabhängigkeiten erfordert. Dies ist komplex und zeitaufwändig und kann zu fragilen Tests führen, die bei Codeänderungen leicht fehlschlagen können.
Komplexe Systeme erfordern komplexe Tests. Das Testen dieser Systeme dauert länger, was zu höheren Kosten und verzögerten Veröffentlichungen führt. Der Code eines komplexen Systems ist schwer zu durchschauen, was zu fehlenden Testfällen führen kann und die Wahrscheinlichkeit erhöht, dass komplexe Systeme Bugs enthalten.
Wenn die Komplexität während des Softwareentwicklungsprozesses berücksichtigt wird, können weniger komplexe Systeme geschaffen werden. Solche Systeme sind leichter zu testen, und da sie leichter zu verstehen sind, ist es wahrscheinlicher, dass keine Testfälle übersehen werden. Dadurch ist es auch weniger wahrscheinlich, dass das System Fehler enthält, was zu Kosteneinsparungen, geringerem Risiko und besserer Qualität führen kann.
Inkonsistenzen
Inkonsistenzen in einem Softwaresystem können auftreten, wenn verschiedene Teile des Systems einander widersprechen.
Beispiele für Inkonsistenzen sind:
- Für verschiedene Teile der Codebasis werden unterschiedliche Benennungskonventionen und Programmierungsstile verwendet. Dies kann die Lesbarkeit und das Verständnis des Codes erschweren, die Fehlerquote erhöhen und die Codequalität verringern.
- Eine Software besteht aus mehreren Modulen, die Datum und Uhrzeit unterschiedlich handhaben. Einige Module verwenden die Ortszeit, andere die UTC-Zeit. Dies kann zu Fehlern führen, wenn Daten zwischen diesen Modulen ausgetauscht werden.
Es gibt mehrere Gründe, die zu Inkonsistenzen führen können, z. B:
- Unterschiedliche Konventionen und Programmierungsstile.
- Unterschiedliche Entwürfe von Implementierungen. Ein einfaches Beispiel wären zwei Methoden zum Auffinden eines Textes in einer Zeichenkette. Die eine Methode erwartet, dass Sie zuerst die Zeichenkette und dann den Suchbegriff übergeben, während die andere Methode erwartet, dass Sie zuerst den Suchbegriff übergeben.
- Unterschiedliche UI-Interaktionen. Ein Dialog erwartet zum Beispiel, dass der / die Benutzer:in die Eingabetaste drückt, während ein anderer erwartet, dass der / die Benutzer:in auf eine Schaltfläche klickt.
- Unterschiedliche UI-Designs. Zum Beispiel können verschiedene Komponenten der Benutzeroberfläche völlig unterschiedlich aussehen.
Inkonsistenzen können mehrere negative Auswirkungen auf die Softwareentwicklung haben, wie z. B.:
- Steigende Kosten aufgrund der Schwierigkeit, das System zu verstehen, zu pflegen und weiterzuentwickeln.
- Ein höheres Risiko von Fehlern, da deren Identifizierung und Behebung eine Herausforderung darstellen kann, insbesondere bei großen und komplexen Systemen.
- Unerwartete Verhaltensweisen und Fehler verringern die Qualität der Software.
Um Konsistenz zu gewährleisten, müssen die Teams richtig kommunizieren, einen einheitlichen Programmierungsstil finden und sich auf eine einheitliche Benutzeroberfläche und UX einigen.
Schwierigkeiten bei der Migration
Die Migration eines Softwaresystems auf eine neue Plattform, Umgebung, Technologie oder seiner Komponenten kann eine Herausforderung darstellen. Der Schwierigkeitsgrad hängt von verschiedenen Faktoren ab, z. B. von der Größe und Komplexität des Systems, der Qualität seines Designs, der Verfügbarkeit von Migrationswerkzeugen und -ressourcen und dem Grad der Kopplung zwischen seinen Komponenten.
Beispiele für mögliche Schwierigkeiten bei der Migration sind:
- Sie müssen ein Altsystem mit veralteter Technologie auf eine moderne Plattform migrieren. Das System ist eng gekoppelt, was die Isolierung und Migration einzelner Komponenten erschwert. Dies könnte zu einem langwierigen und fehleranfälligen Migrationsprozess führen.
- Die Migration eines Systems auf eine neue Datenbank kann erhebliche Änderungen am Code, eine Datentransformation und umfangreiche Tests zur Gewährleistung der Datenintegrität und Funktionalität erfordern.
Die Migration von komplexen Systemen:
- Ist im Vergleich zu einfachen Systemen teurer.
- Erfordert eine gründliche Planung, mehr Aufwand und mehr Ressourcen.
- Erhöht das Risiko von Ausfallzeiten, die den Geschäftsbetrieb stören und die Einnahmen beeinträchtigen können.
- Kann neue Fehler in das System einbringen, was dessen Qualität mindert und die Veröffentlichung neuer Funktionen und Updates verzögert.
Reduzierte Wartbarkeit
In diesem Blog-Post haben wir verschiedene Probleme behandelt, die während der Softwareentwicklung auftreten können, wie z. B. Codeduplikationen, enge Kopplungen und Inkonsistenzen. Diese Probleme können die Wartbarkeit eines Systems beeinträchtigen.
In diesen Fällen kann die Wartbarkeit zum Beispiel eingeschränkt sein:
- Eine Codebasis mit einer hohen Rate an Codeduplikationen. Dies würde bedeuten, dass wir Änderungen an mehreren Stellen vornehmen müssten, was das Risiko von Fehlern erhöht und es schwieriger macht, sicherzustellen, dass alle Instanzen des duplizierten Codes konsistent aktualisiert werden.
- Ein komplexes System mit stark voneinander abhängigen Modulen erfordert oft Änderungen an mehreren anderen Modulen, wenn Änderungen an einem Modul vorgenommen werden. Dies erhöht den Zeit- und Arbeitsaufwand für die Wartung und kann auch die Stabilität des Systems beeinträchtigen.
Eine geringere Wartbarkeit hat mehrere negative Auswirkungen auf ein Softwaresystem:
- Sie kann zu höheren Kosten führen, da ein System mehr Ressourcen für die Wartung benötigt.
- Es ist wahrscheinlicher, dass das System Fehler enthält, was das Erkennen und Beheben dieser Fehler noch schwieriger macht.
- Es kann zu Qualitätsproblemen kommen, da es schwierig sein kann, ein System zu verbessern und weiterzuentwickeln.
- Die Entwicklung und Veröffentlichung neuer Funktionen oder Aktualisierungen könnte sich aufgrund der Zeit, die für die Änderung des Systems benötigt wird, verzögern.
Mangelnde Flexibilität
Wenn wir von mangelnder Flexibilität eines Softwaresystems sprechen, meinen wir damit die Schwierigkeit, sich an neue Anforderungen anzupassen. Verschiedene Gründe können zu diesem Problem beitragen, z. B. enge Kopplungen, ein monolithisches Softwaredesign oder ein Mangel an Dokumentation.
Um ein paar Beispiele zu nennen, bei denen ein Mangel an Flexibilität auftreten kann:
- Ein eng gekoppeltes System, bei dem ein Teil der Geschäftslogik fest in die Benutzeroberfläche einkodiert ist. Eine Änderung der Geschäftslogik ist nur durch eine Modifizierung des UI-Codes möglich, was die Fähigkeit des Systems zur Anpassung an neue Anforderungen einschränkt.
- Eine Anwendung, die so konzipiert ist, dass sie nicht ohne weiteres skaliert werden kann, um eine höhere Last zu bewältigen. Das Hinzufügen neuer Funktionen oder die Verbesserung der Leistung kann erhebliche Änderungen am gesamten System erfordern. Dies macht das System weniger flexibel und komplexer in der Entwicklung.
Ein Mangel an Flexibilität hat mehrere negative Auswirkungen auf die Software:
- Es ist teuer, ein System zu ändern, weil Änderungen unbeabsichtigte Nebeneffekte haben können und die Umsetzung schwierig sein kann.
- Die Veröffentlichung neuer Funktionen und Aktualisierungen kann länger dauern, da die Änderungen sorgfältig geplant und getestet werden müssen, bevor sie veröffentlicht werden können.
- Bei einem unflexiblen System ist die Wahrscheinlichkeit größer, dass es zu Qualitätsproblemen kommt, da die Behebung von Fehlern und die Verbesserung der Software schwieriger ist.
- Unternehmen, die unflexible Software verwenden, haben möglicherweise Wettbewerbsnachteile. Sie sind möglicherweise nicht in der Lage, schnell auf Marktveränderungen zu reagieren oder die Bedürfnisse ihrer Kunden zu erfüllen.
Schlussfolgerung
Bei der Entwicklung von Software oder während ihres Lebenszyklus können verschiedene Probleme auftreten. Wenn diesen Problemen nicht richtig vorgebeugt wird, können sie zu erfolglosen Softwareprojekten führen, die durch überhöhte Budgets, verzögerte Zeitpläne und beeinträchtigte Qualität gekennzeichnet sind.
Codeduplikate können die Wartungskosten erhöhen und Bugs einführen, während enge Kopplungen die Anpassungsfähigkeit und Innovation negativ beeinflussen. Diese Probleme sind nicht nur eine technische Herausforderung, sondern können auch zu erheblichen finanziellen und betrieblichen Rückschlägen führen. Skalierbarkeitsprobleme, komplexe Tests, Inkonsistenzen, Migrationsschwierigkeiten, eingeschränkte Wartbarkeit und mangelnde Flexibilität verstärken diese Probleme noch.
Da wir uns dieser Probleme bewusst sind, können wir Maßnahmen ergreifen, um sie zu vermeiden. Ein guter Weg ist die Einführung eines Architekturmusters. Ein Architekturmuster ist eine Blaupause für die Entwicklung von Softwaresystemen, die gegen die in diesem Beitrag besprochenen Probleme resilient sind. Es gibt verschiedene Architekturmuster, aus denen man wählen kann. Ein gutes Beispiel ist das Clean-Architecture-Muster, das Prinzipien wie die “Separation of Concerns”, Modularität und Kapselung fördert. Es ermöglicht Entwicklern, Systeme zu erstellen, die einfacher zu warten, zu skalieren und weiterzuentwickeln sind.
Eine ausführliche Erläuterung von Clean Architecture finden Sie in unserem nächsten Blogbeitrag: Clean Architecture: Ein tiefes Eintauchen in strukturiertes Software-Design.
Insgesamt ist es wichtig, sich der in diesem Blog-Post genannten Probleme bewusst zu sein. Wenn Sie sie kennen, können Sie ihnen angemessen vorbeugen und übermäßig teure oder fehlgeschlagene Softwareprojekte vermeiden.