[MELDEN] Von der Vision zum Code: Ein Leitfaden zur Ausrichtung der Geschäftsstrategie auf die Ziele der Softwareentwicklung ist veröffentlicht!
HOL ES DIR HIER

Das Tech-Schuldenrätsel lösen: Strategien, die das Geschäft ankurbeln

readtime
Last updated on
February 17, 2025

A QUICK SUMMARY – FOR THE BUSY ONES

Kontext: 

Eine Express-Anwendung, die von technischen Schulden, komplexen APIs, Leistungsengpässen und Skalierbarkeitsproblemen geplagt ist.

Vorgehensweise:

  • Durchführung eines technischen Audits, um die Probleme und Risiken aufzuzeigen und das Bewusstsein zu schärfen.
  • Verknüpfung der technischen Herausforderungen mit Geschäftsprioritäten.
  • Festlegung einer Teststrategie (Komponententests passten nicht, Testing Trophy war eine Lösung).
  • Wir verfolgen einen strukturierten Ansatz, der Tests, Refactoring und sorgfältige Planung nutzt.
  • Verkapselung von Legacy-Code in das bestehende System und Entwicklung neuer Funktionen im Nest-Framework.
  • Schrittweise Verbesserungen durch kleinere Überarbeitungen vornehmen.

Ergebnisse:

In 10 Monaten intensiver Arbeit haben wir die beschriebene Codebasis von 0% auf etwa 50% Codeabdeckung transformiert und den größten Teil des Backend-Codes auf die isolierten Nest-Module migriert.

TABLE OF CONTENTS

Das Tech-Schuldenrätsel lösen: Strategien, die das Geschäft ankurbeln

In der Welt der Softwareentwicklung ist es fast unvermeidlich, dass Sie auf Projekte stoßen, die mit technischen Schulden und schlecht geschriebenem Code zu kämpfen haben. Solche Projekte stellen oft eine ziemliche Herausforderung dar, erfordert einen strategischen Ansatz, um sie zu bergen und zu robusten, wartbaren Systemen weiterzuentwickeln.

Der Artikel befasst sich mit der Herausforderung, mit veraltetem Code und technischen Schulden umzugehen, und beschreibt die Lehren aus einem realen Projekt, an dem wir gearbeitet haben. Als Softwareingenieure wissen wir, dass die Arbeit mit Altsystemen ein integraler Bestandteil unseres Fachs ist, und es ist eine Reise, die sowohl sehr herausfordernd als auch lohnend sein kann.

In dem Artikel teile ich die Geschichte von ein Projekt, das von technischen Schulden, komplexen APIs, Leistungsengpässen und Skalierbarkeitsproblemen geplagt ist.

Wir werden einige Strategien, Taktiken und praktische Schritte untersuchen, die wir ergriffen haben, um dem Projekt neues Leben einzuhauchen und gleichzeitig weiterhin einen Mehrwert für das Unternehmen zu schaffen.

Beginnen wir mit den Herausforderungen, auf die wir gestoßen sind.

Fünf Hauptherausforderungen, auf die das Altsystem gestoßen ist

Nachdem wir uns das Altsystem angesehen und analysiert hatten, stellten wir fünf Hauptherausforderungen fest:

  1. Hohe Kopplung: Das Backend, eine wichtige Komponente unserer Anwendung, wurde hauptsächlich in nur zwei riesigen Quelldateien implementiert, die jeweils Tausende von Codezeilen enthielten. Im Frontend litt unsere Angular-Anwendung unter riesigen Komponenten voller komplexer Logik, was ihre Wartung zu einer großen Herausforderung machte.
  2. Testabdeckung: Das Projekt war voller Probleme im Bereich des Testens. Das Backend hatte nur einen einzigen Komponententest, und dem Frontend erging es nicht viel besser. Es gab mehrere Komponententests, die wenig Wert auf die Sicherstellung der Codequalität boten.
  3. Fehlende Einrichtung der lokalen Umgebung: Das Fehlen einer klar definierten lokalen Entwicklungsumgebung, die von den anderen Systemkomponenten isoliert war, war ein großes Problem, das es fast unmöglich machte, unabhängig vom Rest des Systems zu arbeiten.
  4. Keine Beobachtbarkeit und schlechter CI/CD: Dem Projekt fehlten grundlegende Verfahren zur Sicherstellung der Codequalität und der Effizienz der Bereitstellung. Es gab keine Beobachtbarkeit, um Einblicke in das Systemverhalten zu gewinnen, und es wurden keine CI/CD-Pipeline-Tests des Systems durchgeführt.
  5. Fehlende Projektdokumentation: Das Projekt litt unter einer schlechten Dokumentationsqualität. Das Fehlen einer umfassenden Dokumentation machte die Systemerkennung schwierig und zwang das Team, auf manuelle Tests und Code-Reverse-Engineering zurückzugreifen, um zu verstehen, wie die verschiedenen Systemkomponenten und Funktionen funktionierten.

Die eigentliche Herausforderung bestand jedoch darin, wo soll ich anfangen.

Technisches Audit: Aufzeigen der Probleme und Risiken

Unser Projekt war zweifellos voller technischer Probleme, aber die schwierigste Aufgabe, vor der wir standen, war die Entscheidung, wo wir mit der Verbesserung der Dinge beginnen sollten.
Die Lösung der oben genannten Probleme war ein wichtiger Schritt, aber der Versuch, sie alle auf einmal anzugehen, wäre unmöglich gewesen. Also, wo haben wir angefangen und wie haben wir dieses Labyrinth von Herausforderungen gemeistert?

Unser erster Schritt war führen Sie ein umfassendes technisches Audit des Projekts durch. Bei diesem Audit ging es nicht nur darum, die technischen Probleme zu identifizieren; es ging um den Kunden auf die drängenden Probleme der Technologieverschuldung aufmerksam machen und vermittelt seine breiteren Implikationen.

Das technische Audit ermöglichte es uns, die Probleme und Risiken aufzuzeigen, die in unserer Codebasis lauern.

Prioritäten setzen und mit der Bewusstseinsbildung beginnen

Wir haben diese Themen auf der Grundlage ihrer Bedeutung und ihrer potenziellen Auswirkungen auf den Projekterfolg priorisiert. Noch wichtiger ist, Wir nutzten dieses Audit als Plattform, um den Kunden in ein Gespräch über die Bedeutung der Bekämpfung von Technologieschulden einzubeziehen - wir haben vermittelt, dass es nicht nur um die Verbesserung der Codequalität geht, sondern um eine wichtige Investition in die Zukunft des Projekts. Wir konnten uns nicht zu viele technische Schulden leisten.

technical debt - tech audit

Es ist immer noch schwierig, eine Roadmap zu erstellen

Es ist jedoch erwähnenswert, dass das Tech-Audit zwar ein großartiges Instrument zur Sensibilisierung war, aber keine Wunderwaffe. Das Die während des Audits festgestellten Probleme waren zu umfangreich, um sie direkt in umsetzbare Aufgaben oder Epen umzuwandeln, und waren oft zu weit von den Geschäftsprioritäten getrennt, was sie schwer zu planen macht.

Die wichtigsten Erkenntnisse aus dieser Phase waren klar: Das technische Audit diente zwar als entscheidender Katalysator für Veränderungen, bot aber keinen unmittelbaren Fahrplan für die Lösung.

Lösung: Identifizierte Probleme mit den Geschäftszielen in Einklang bringen

Um signifikante Fortschritte zu erzielen, mussten wir diese technischen Herausforderungen mit dem tatsächlichen Geschäftswert verbinden.

Nur wenn wir unsere Bemühungen an den primären Geschäftszielen des Projekts ausrichten, konnten wir effektive Bewältigung von technischen Schulden innerhalb der Grenzen enger Projektfristen.

Festlegung einer Teststrategie

Wir sind uns alle einig, dass jedes gesunde Projekt über angemessene Tests verfügen sollte, auf die es sich verlassen kann. In unserer Mission, die technische Qualität des Projekts zu verbessern, mussten wir auch Wählen Sie die richtige Strategie und den richtigen Testansatz sodass wir uns in aller Ruhe auf die Weiterentwicklung des Systems konzentrieren konnten.

Komponententests funktionieren nicht

Traditionell predigt die Testpyramide die Bedeutung einer soliden Grundlage von Komponententests, gefolgt von Integrations- und End-to-End-Tests. In unserem speziellen Fall stellte die strikte Einhaltung der Prinzipien der Testpyramide jedoch eine einzigartige Herausforderung dar — unsere Codebasis hatte einen Zustand erreicht, in dem das Schreiben umfassender Komponententests mit dem Auftragen einer frischen Farbschicht auf eine bröckelnde Wand verglichen werden konnte. Es würde die Probleme lösen, anstatt sie zu lösen, und die Aufrechterhaltung solcher Tests würde zu einem Albtraum werden.

Trophy als Lösung testen

Stattdessen wir haben einen alternativen Ansatz gewählt, der heißt Test-Trophäe, und wandte seinen Hauptslogan an, der besagt:

Schreiben Sie Tests. Nicht zu viele. Hauptsächlich Integration

testing trophy

Unser Ansatz war einfach: Konzentrieren Sie sich darauf, die wichtigsten Endnutzerströme abzudecken.

Durch die Erstellung geeigneter High-Level-Tests mit Playwright (was die Aufgabe für uns sehr unterhaltsam machte), die sich hauptsächlich auf die E2Es und Integrationstests konzentrierten, stellten wir sicher, dass jede Änderung, die wir vorgenommen haben, dem Hauptzweck unserer Anwendung entsprach.

Diese Tests wurden zu Wächtern, die vor Regressionen und unerwartetem Verhalten schützten, und gaben uns in relativ kurzer Zeit einen sehr hohen Wert. Natürlich waren sie nicht die schnellsten und alles andere als perfekt, aber wir fühlten uns viel sicherer in dem, was wir damals taten.

Refactoring: Zweck vor Perfektion

Sobald wir durch Tests ein gewisses Maß an Sicherheit und Vertrauen gewährleistet hatten, konnten wir anfangen, über konkrete Änderungen nachzudenken.

Wenn man mit einer Codebasis konfrontiert wird, die voller technischer Schulden und suboptimaler Codequalität ist, ist es fast natürlich, vom Sirenenruf eines Neuanfangs in Versuchung geführt zu werden. Die Idee, alles zu verschrotten und von Grund auf neu aufzubauen, kann verlockend sein, aber es ist ein Weg voller Risiken und Herausforderungen. Refactoring ist immer mit einem Risiko verbunden und sollte immer von einem klaren Zweck und einer engen Abstimmung mit den Geschäftszielen geleitet werden.

Normalerweise ist ein Umschreiben nur dann praktisch, wenn es die einzige verbleibende Option ist.

Der Grund, warum Umschreibungen in der Praxis so riskant sind, liegt darin, dass das Ersetzen eines funktionierenden Systems durch ein anderes selten eine Änderung über Nacht ist. Wir verstehen selten, was das vorherige System bewirkt hat — viele seiner Eigenschaften sind zufälliger Natur, und Tests allein werden nicht garantieren können, dass einige Regressionen nicht doch eingeführt wurden.

Kleine Schritte zur Rettung!

Obwohl es aus technischer Sicht nach einem viel weniger attraktiven und aufregenden Weg klingt, in der Lage zu sein Das bestehende System einwandfrei zu verbessern, kann tatsächlich mit viel komplexeren und interessanteren Herausforderungen verbunden sein.

Denken Sie über die Geschäftsziele nach, um den Ausgangspunkt festzulegen

Wir haben diese wichtige Lektion gelernt: Refactoring sollte einen klar definierten Zweck haben und immer das Geschäft unterstützen. Die Ästhetik des Codes ist zwar unerlässlich, sollte aber niemals die einzige treibende Kraft hinter Refactoring-Maßnahmen sein. Stattdessen sollte Refactoring eine strategische Maßnahme sein, um spezifische Probleme zu lösen oder langfristige Vorteile zu erzielen.

Vier Schritte zur Identifizierung verbesserungsbedürftiger Bereiche

Vor diesem Hintergrund haben wir die folgenden Punkte identifiziert, die Ihnen helfen könnten, Verbesserungsbereiche in Ihrem Projekt zu identifizieren, die Sie möglicherweise in Betracht ziehen sollten:

  1. Achte auf die Roadmap - Die Identifizierung von Refactoring-Möglichkeiten kann sich eng an der Projektplanung orientieren und Codeverbesserungen strategisch an den sich ändernden Bedürfnissen und Prioritäten des Unternehmens ausrichten.
  2. Analysieren Sie die Bugs - vielleicht sind sie verwandt und kommen von derselben verworrenen Stelle in der Codebasis. Wenn das der Fall ist, könnte das ein Grund sein, eine bedeutendere Änderung in Betracht zu ziehen, die diesen Teil des Systems ersetzt.
  3. Definieren Sie den wichtigsten Teil der Anwendung - dadurch wird sichergestellt, dass die Refactoring-Maßnahmen auf Bereiche ausgerichtet werden, die sich am stärksten auf die Erreichung der Geschäftsziele auswirken.
  4. Kommunikation ist der Schlüssel - Eine offene und transparente Kommunikation innerhalb des Teams ist entscheidend. Es ist sehr wichtig, technische Herausforderungen frühzeitig zu erkennen, zu teilen und sie in die Planung einzubeziehen.

Fallstudie: Lassen Sie uns versuchen, es in die Praxis umzusetzen

Wie bereits erwähnt, hatten wir mit einigen Problemen zu kämpfen. Um uns in diesem Labyrinth von Problemen effektiv zurecht zu finden, haben wir einen strukturierten Ansatz gewählt, der folgende Vorteile nutzte Testen, Refactoring und sorgfältige Planung.

Am Beispiel unseres Backends wir begannen allmählich mit dem Übergang zum Nest - ein robustes und modulares Framework für die Erstellung skalierbarer Anwendungen.

Dieser Übergang ermöglichte es uns modernisieren Sie unsere Codebasis schrittweise, ohne die bestehende Funktionalität zu stören indem Sie es neben einer vorhandenen Express-Anwendung einrichten, was sich als so einfach herausstellte, als nur ein paar Codezeilen hinzuzufügen:

import { app as ExpressApp } from './legacy-app';
import { AppModule } from './app.module';

const bootstrap = async() => {
  const port = process.env.PORT || 3000;

  const nest = await NestFactory.create(
    AppModule,
    new ExpressAdapter(ExpressApp),
  );

  await nest.init();
  http.createServer(ExpressApp).listen(port);
}

Hauptpunkte der Migration

Obwohl das Framework nur als Tool zur Unterstützung unserer Änderungen gedacht war, berücksichtigten unser primäres Ziel und unser Plan für die weitere Migration die folgenden Hauptpunkte:

Isolierung der technischen Schulden

Durch die Kapselung von Legacy-Code in das bestehende System und die Entwicklung neuer Funktionen im Nest-Framework haben wir effektiv hat den Legacy-Code isoliert und verhindert, dass er die neue Codebasis kontaminiert.

Einführung der v2-API

Wir haben eine neue Version unserer API eingeführt, die sich durch eine sauberere, intuitivere Struktur auszeichnet. Die Implementierung folgt den Spezifikationen und umfasst entsprechende Tests. Es hat uns erlaubt neue Funktionen und Verbesserungen hinzufügen, ohne die bestehende API zu ändern, das als Rückgrat unserer Anwendung diente, während unsere API-Clients langsam auf die neueste Version umgestellt wurden.

Dokumentation der Geschäftsanforderungen

Zu gewinnen Klarheit und Ausrichtung Gemeinsam mit dem Entwicklungsteam und den Stakeholdern investierten wir Zeit in die umfassende Dokumentation der Geschäftsanforderungen, indem wir eine detaillierte Spezifikation, die als Blaupause für die Entwicklung und weitere Tests diente.

Inkrementelle Verbesserungen durch kleinere Überarbeitungen

Eines der Grundprinzipien unseres Ansatzes war es, monumentale Neufassungen zu vermeiden. Stattdessen entschieden wir uns für kleine, überschaubare kleine Schritte, die sich nahtlos in unsere laufenden Geschäftsaufgaben integrieren ließen.

Dieser Ansatz stellte sicher, dass wir nur das abbissen, was wir kauen konnten, und ermöglichte es uns, die Kernfunktionalitäten zu verbessern und gleichzeitig kontinuierlich neue Funktionen bereitzustellen.

Beispiel: Aktualisierung des Benutzerregistrierungsprozesses

Ich habe bereits viel über die Theorie gesagt. Schauen wir uns nun ein bestimmtes Beispiel genauer an und setzen alles in die Praxis um.

Irgendwann wurden wir gebeten, im Rahmen der Benutzerregistrierung eine E-Mail-Funktion zur Kontoverifizierung hinzuzufügen.
Das Die aktuelle Registrierungslogik ist zu einem komplexen Code geworden Im Laufe der Zeit, da es die Benutzererstellung, Datenbankinteraktionen, Authentifizierung und mehr verwaltet — alles innerhalb von Hunderten von Codezeilen, was sich als ziemliche Herausforderung herausstellte.

Um dieses Problem zu lösen, könnten wir die neue Funktion auf eine neue Nest-Art implementieren, aber der schwierige Teil wäre immer noch, sie in den alten Endpunkt zu integrieren. Der einfachste Weg, voranzukommen, wäre, auch den genannten Endpunkt neu zu schreiben und alle genannten Probleme gleichzeitig zu lösen was ein großes Risiko mit sich bringt und viel Arbeit investiert bevor wir überhaupt anfangen können, am primären Ziel der Aufgabe zu arbeiten.

Um all das zu vermeiden, wir haben uns für die Einführung eines ereignisgesteuerten Ansatzes mit dem Namen entschieden Broker-Topologie.

In der Broker-Topologie senden Sie Ereignisse an einen zentralen Broker (Event-Bus), und alle Broker-Abonnenten empfangen und verarbeiten Ereignisse asynchron.

broker topology

Bei diesem Ansatz Immer wenn der Event-Bus ein Ereignis von Event-Erstellern empfängt, leitet er das Event an alle Abonnenten weiter, die die Daten dann mit ihrer eigenen Logik verarbeiten können. In diesem Fall ist der Event Bus die einzige Abhängigkeit von Publishern und Abonnenten, wodurch die Kopplung erheblich reduziert wird.

Im Folgenden finden Sie ein anschauliches Beispiel für die Implementierung des ereignisgesteuerten Ansatzes zur Bewältigung der beschriebenen Probleme. Die einzige Änderung, die auf der alten Seite vorgenommen werden musste, bestand darin, den Event-Bus über das Ereignis der neuen Benutzerregistrierung zu informieren; das ist alles:

await EventBus.getInstance().dispatch(
  new NewUserRegisteredEvent(payload),
);

Was dann von allen interessierten Abonnenten erledigt werden könnte, die dafür verantwortlich sind, es auf ihre spezifische Weise zu handhaben:

export class AccountVerificationService {
  constructor(
    @Inject(EVENT_BUS_PROVIDER) private readonly eventBus: EventBus,
    // ...
  ) {
    this.eventBus.registerHandler(
      ApplicationEvents.NewUserRegisteredEvent,
      (eventPayload: NewUserRegisteredEventPayload) => 
           this.processUserAccountVerification(eventPayload);
    );
  }

  // ...
}

Durch die Nutzung des Eventbusses und die Nutzung der Modularität haben wir unser Ziel erreicht Hinzufügen neuer Funktionen auf getestete und wartbare Weise bei gleichzeitiger Minimierung der Unterbrechung des bestehenden Systems.

Zusammenfassung

Der Umgang mit technischen Schulden kann eine Herausforderung sein und eine lange Reise sein, die Geduld und strategisches Denken erfordert.

Nach etwa zehn Monaten engagierter Bemühungen wir haben die beschriebene Codebasis von 0% auf etwa 50% Codeabdeckung transformiert und den größten Teil des Backend-Codes auf die isolierten Nest-Module migriert.

Außerdem haben wir erfolgreich implementierte Integrations- und Ende-zu-Ende-Tests, um Robustheit und Stabilität zu gewährleisten, unser System nach jeder Änderung gründlich zu überprüfen und trotzdem flexibel genug zu bleiben, um neue Funktionen hinzuzufügen und den sich ändernden Geschäftsanforderungen gerecht zu werden.

Es gibt noch viel zu tun, aber das Projekt befindet sich an einem anderen Ort und ist bereit für die weitere Entwicklung.

Frequently Asked Questions

No items found.

Our promise

Every year, Brainhub helps 750,000+ founders, leaders and software engineers make smart tech decisions. We earn that trust by openly sharing our insights based on practical software engineering experience.

Authors

Michał Szopa
github
JavaScript-Softwareentwickler

JavaScript- und AWS-Spezialist, Cloud-Enthusiast, mit 7 Jahren Berufserfahrung in der Softwareentwicklung.

Michał Szopa
github
JavaScript-Softwareentwickler

JavaScript- und AWS-Spezialist, Cloud-Enthusiast, mit 7 Jahren Berufserfahrung in der Softwareentwicklung.

Read next

No items found...