Wie sich kleine Entscheidungen in langlebigen React-Codebasen summieren
Fünfzehn Wartungsgewohnheiten für React-Anwendungen, die jahrelang funktionieren: lesbare Codebasis, fokussierte Komponenten, begrenzter Zustand, minimale Abhängigkeiten, Tests und Überwachung.
Das Starten eines React-Projekts ist der einfachere Teil. Die eigentliche Herausforderung tritt Monate oder Jahre später auf, wenn die App mehr Nutzer, mehr Funktionen, mehr Mitwirkende sowie mehr Abhängigkeiten hat und einige „temporäre“ Workarounds stillschweigend zu wesentlichen Bestandteilen werden. Zu diesem Zeitpunkt ist React selbst selten das Problem; das Problem besteht darin, die Codebasis verständlich zu halten, während sich alles um sie herum ständig ändert. Diese Anleitung führt durch fünfzehn Gewohnheiten, die nach den Bereichen geordnet sind, in denen sich tatsächlich Wartungskosten ansammeln, damit Sie die Entscheidungen erkennen können, die sich verschärfen, bevor aus der Codebasis etwas wird, was niemand mehr anfassen möchte.
Code, der für den nächsten Leser geschrieben wird
Ziehen Sie offensichtlichen Code vor klugen Code
Dichte Einzeilensätze, tiefe Abstraktionen sowie Hilfsfunktionen, die für Dutzende hypothetischer Fälle entwickelt wurden, erscheinen beim Schreiben produktiv. Sechs Monate später werden sie zu einem Rätsel – oft für die Person, die sie geschrieben hat, die sich nicht mehr daran erinnert, warum.
Die Alternative ist absichtlich einfacher Code. Man sollte beispielsweise eine Benutzeroberfläche gestalten, die sich im Ladezustand, bei einem Fehler oder fertig befinden kann. Frühzeitige Rückgabewerte machen jeden Zustand klar ersichtlich, jeweils für eine Bedingung auf einmal:
if (isLoading) {
return <LoadingState />;
}
Der Fehlerfall folgt derselben Struktur, und der erfolgreiche Ablauf kommt zuletzt:
if (error) {
return <ErrorState />;
}
return <Dashboard />;
Hier gibt es nichts Beeindruckendes – und genau das ist der Punkt: Jeder kann in Sekundenschnelle erkennen, welche Komponente wann angezeigt wird. In einer großen Anwendung ist es weitaus wichtiger, dass der nächste Entwickler Ihren Code schnell versteht, als den aktuellen zu beeindrucken.
Namen sind Dokumentation, die niemals veraltet
Beim Schreiben von Code erscheint das Benennen wie eine unbedeutende Kleinigkeit, wird aber halb Jahr später beim Debuggen äußerst wichtig. Vergleichen Sie das Suchen im Codebase nach diesem:
handleData()
mit dem Suchen nach diesem:
calculateMonthlyRevenue()
Der zweite Name verrät Ihnen bereits vor dem Öffnen der Datei, was die Funktion berechnet. Große Codebases sind im Wesentlichen Kommunikationskanäle zwischen Entwicklern; ein präziser Name macht die Absicht klar, während ein vager Name jeden Leser zu einem Detektiv macht.
Achten Sie auf generische Namen, die unter Zeitdruck entstehen:
data
item
temp
helper
value
thing
Ja, thing kommt tatsächlich in echten Codebases vor. Eine nützliche Faustregel: Wenn ein Name genauso gut in jede Datei des Projekts passen würde, sagt er wahrscheinlich nicht genug über diese spezifische Datei aus.
Komponenten- und Zustandsgrenzen
Überdimensionierte Komponenten werden teuer
Fast jedes langfristig betriebene React-Projekt entwickelt irgendwann ein Komponentenmodul mit bis zu vierstelliger Anzahl an Zeilen. So beginnt es selten – meist mit etwa 150 Zeilen –, danach kommen Modal-Fenster, Filterfunktionen, Datenabrufe, Berechtigungsprüfungen sowie ein weiteres Modal-Fenster. Schließlich öffnet man die Datei und findet etwas wie Folgendes:
Dashboard.tsx
1,247 lines
Komponenten dieser Größe sind schwer verständlich, zu testen, wiederverwendbar und zu debuggen. Zudem ist es riskant, sie zu ändern, da jede Änderung den Zustand beeinflussen kann, von dem andere Teile der Datei abhängen. Teilen Sie die Komponente früher auf, als es notwendig erscheint – anstelle einer einzigen Datei:
Dashboard.tsx
Teilen Sie den Bildschirm in Abschnitte auf, von denen jeder eine eigene Aufgabe hat:
DashboardHeader.tsx
DashboardStats.tsx
DashboardFilters.tsx
RecentActivity.tsx
DashboardTable.tsx
Jede Datei hat nun nur eine einzige Verantwortung und einen deutlich geringeren Einflussbereich. Ein praktischer Hinweis: Wenn Sie eine Komponente nicht in einem Satz ohne mehrere „und“-Verbindungen beschreiben können, teilen Sie sie auf.
Wiederholen Sie Muster, die Sie bereits gesehen haben – nicht solche, die Sie sich vorstellen
Wiederverwendbare Komponenten sind ein guter Instinkt, der jedoch leicht übertrieben werden kann. Ein häufiges Versagen besteht darin, dass ein einziger Button versucht, alle Buttons im Produkt zu ersetzen:
<UniversalButton
type="primary"
variant="rounded"
size="medium"
iconPosition="left"
loadingStyle="spinner"
/>
Jede neue Eigenschaft scheint unbedenklich zu sein, doch zusammen erzeugen sie einen kombinatorischen Raum an Varianten, der von niemandem vollständig getestet wird. Dadurch wird die „wiederverwendbare“ Komponente letztendlich schwieriger zu nutzen als drei kleine, spezifische Buttons. Wiederverwendung ist wertvoll – eine vorzeitige Generalisierung hingegen nicht.
Ziehen Sie eine gemeinsame Abstraktion erst dann ab, wenn Sie tatsächlich ein wiederkehrendes Muster erkennen, und nicht weil eine zukünftige Seite sie benötigen könnte. Sollte dieses Bedürfnis entstehen, entwerfen Sie die Abstraktion anhand echter Beispiele statt aufgrund von Vermutungen.
Ordnen Sie den Zustand danach, wohin er gehört
Die Zustandsverwaltung neigt dazu, stillschweigend zu versagen. Eine kleine Anwendung beginnt mit dem Zustand der Komponenten:
useState()
Dann wird über den Kontext ein gemeinsamer Zustand hinzugefügt:
useContext()
Letztendlich enthält das Projekt gleichzeitig Redux, mehrere Kontexte, lokalen Zustand, URL-Zustand und Serverzustand – ohne klare Angabe, welcher davon die Sidebar steuert. Jedes Werkzeug war bei seiner Hinzufügung sinnvoll; was fehlt, ist eine Regel dafür, was wohin gehört.
Eine einfache Möglichkeit, Ordnung wiederherzustellen, besteht darin, den Zustand nach seiner Art zu klassifizieren. Lokaler UI-Zustand umfasst Dinge wie diese:
modal open
dropdown selected
input value
Er sollte innerhalb des Komponenten bleiben, der ihn verwendet. Serverzustand sind Daten, die sich auf der Backend-Seite befinden und lediglich im Browser gekachtet werden:
users
products
analytics
Für diese Kategorie sollte man eine Bibliothek verwenden, die zum Abrufen, Cachen und Erneuern von Remote-Daten konzipiert ist, anstatt Antworten manuell in einen globalen Speicher zu kopieren. Falls Ihr Team über diesen Wechsel nachdenkt, werden die Vor- und Nachteile in unserer Vergleichsstudie zu React Query und Redux für Server-State erläutert. Tatsächlich gibt es nur eine begrenzte Anzahl an wirklich globalen Zuständen:
theme
authenticated user
app-wide preferences
Halten Sie diese letzte Gruppe möglichst klein: Jede Komponente kann den globalen Zustand lesen oder ändern, daher führt weniger davon zu weniger Fehlern, die scheinbar aus dem Nichts auftauchen. Filter, Tabellen und Paginierung gehören oft in die URL, wo sie bei Neu laden erhalten bleiben und geteilt werden können.
Struktur und Abhängigkeiten
Sorgen Sie für eine vorhersehbare Ordnerstruktur
Stellen Sie sich vor, Sie treten einem Projekt bei und finden Folgendes im Wurzelverzeichnis:
src/
components/
shared/
common/
helpers/
utils/
services/
core/
misc/
new/
new2/
Wohin gehört ein neues Benutzerprofilkomponente? Niemand kann das sagen, weshalb jeder Entwickler unterschiedlich entscheidet und die Struktur ständig verändert wird. Überschneidende Kategorien wie shared, common, helpers und utils zeigen, dass niemand entschieden hat, was jede davon bedeutet.
Ein auf Funktionen basierendes Layout beseitigt den größten Teil dieser Unklarheit, indem es den Code nach dem Teil des Produkts gruppiert, dem er dient:
features/
auth/
dashboard/
users/
billing/
Innerhalb jeder Funktion sorgt ein kleiner, wiederkehrender Satz an Unterordnern dafür, dass alles vertraut bleibt:
components/
hooks/
services/
types/
Nun weiß jeder, der an der Rechnungsstellung arbeitet, wo sich der Rechnungskod befindet, und beim Umschreiben einer Funktion wird nur ein Verzeichnis angepasst statt zehn. Andere Strukturen sind ebenfalls möglich (siehe unsere Vergleich der React-Verzeichnisstrukturen); wichtig ist, dass die Regel vorhersehbar ist und schriftlich festgehalten wird.
Betrachten Sie jede Abhängigkeit als zukünftige Wartungsaufgabe
Die Hinzufügung eines Pakets erfordert nur einen Befehl:
npm install something-cool
Die Kosten zeigen sich später: Pakete werden aufgegeben, sie bringen brisante Änderungen mit sich, führen zu Sicherheitsproblemen und vergrößern die Dateigröße des Bundles – Ihr Team muss weiterhin Code aktualisieren, den niemand im Team gelesen hat.
Bevor Sie es installieren, fragen Sie sich daher, ob Sie es wirklich benötigen. Ein Paket, das erhebliche Arbeit spart oder ein wirklich schwieriges Problem löst – wie beispielsweise die zeitzonegerechte Verarbeitung von Datumsangaben – verdient seinen Platz. Eines, das lediglich eine Zeichenkette formatiert, nicht.
Sicherheitsnetze, die mit der App wachsen
Testen Sie die Abläufe, bei denen ein Fehler am meisten Schaden anrichtet
In einer kleinen App reicht es vor der Veröffentlichung aus, die verschiedenen Bildschirme durchzuklicken. In einer großen App kann bereits die Bearbeitung einer einzigen Funktion dazu führen, dass die Abrechnung aus unerklärlichen Gründen fehlschlägt – genau dann lohnen sich automatisierte Tests: Sie ermöglichen es Ihnen, Code zu ändern, den Sie nicht selbst geschrieben haben, mit Sicherheit.
Ihre Tests müssen nicht jeden Implementierungsaspekt abdecken. Konzentrieren Sie sich auf die Benutzerabläufe, bei denen ein Fehler teure Folgen hat:
- Anmelden
- Der Bestellprozess
- Einreichen wichtiger Formulare
- Rechenschaftskontrollen
- Berechnungen, auf denen das Geschäft beruht
Tests können nicht beweisen, dass die App fehlerfrei ist; sie erkennen offensichtliche Rückschritte bereits vor den Nutzern. Tests des Verhaltens anstelle der internen Funktionsweise überstehen außerdem ein Refactoring weitaus besser.
Achten Sie auf eine langsame, schrittweise Verschlechterung der Leistung
Große React-Apps werden selten bereits in einer Veröffentlichung langsam. Die Leistung verschlechtert sich Schritt für Schritt durch jeweils eine vernünftige Entscheidung: eine schwere Abhängigkeit, ein riesiges Bild, vermeidbare Neurenderungen, zehn Anfragen auf einer Seite. Zusammen können sie dazu führen, dass eine Dashboard-Seite fünf Sekunden zum Laden braucht – achten Sie daher ständig auf:
- Neurenderungen von Komponenten, obwohl sich nichts auf deren Anzeige geändert hat
- einen zunehmenden Bundle-Größe mit jeder Veröffentlichung
- dieselbe Anfrage, die mehrmals pro Seite abgesendet wird
- Einzelfunktionen der Komponenten, die langsam rendern
- Lange Listen, die ohne Virtualisierung dargestellt werden
- Überdimensionierte Bilder
Kleine Erfolge summieren sich, und ein bereits beim Auftreten entdeckter Rückschritt ist weitaus günstiger als einer, der später aufgespürt werden muss.
Entwerfen Sie absichtlich den „unglücklichen“ Pfad
Die meiste Anstrengung geht in den „glücklichen“ Pfad, bei dem jede Anfrage erfolgreich ist. In der Produktion gehen Verbindungen verloren, APIs versagen, Berechtigungen ändern sich und der Backend liefert Datenformate, die niemand erwartet hat. Der Benutzer sollte stattdessen eine klare, behobbare Nachricht wie „Wir konnten Ihre Daten nicht laden. Versuchen Sie es erneut.“ sehen, anstatt eines rohen Laufzeitfehlers wie folgendem:
TypeError: Cannot read properties of undefined
In React-Begriffen bedeutet das explizite Fehlerzustände für das Datenabrufen, Error-Boundaries, damit ein fehlerhafter Komponentenblock die Seite nicht leer macht, sowie Wiederholungsversuche, wo sie sinnvoll sind.
Betrachten Sie die Überwachung in der Produktion als Teil der Entwicklung
Die Veröffentlichung ist noch nicht das Ende der Arbeit. In der Produktion treten Probleme auf, die bei der lokalen Entwicklung niemals auftauchen würden, und Sie benötigen Einblicke in:
- Laufzeitfehler und ihre Auftretensorte
- Anfragen, die langsamer sind als erwartet
- API-Aufrufe, die fehlschlagen
- Gesamtleistung der Seite und der Interaktionen
- Wie die Nutzer das Produkt tatsächlich nutzen
Ohne diese Funktion ist ein Fehlerbericht lediglich „Ein Benutzer sagte, gestern sei etwas kaputtgegangen“. Fehlerverfolgung und kontextbezogene Protokolle verwandeln das in einen Stack Trace sowie ein Zeitstempel, auf die man handeln kann.
Gewohnheiten, die ein Team zusammenhalten
Schreiben Sie Dokumentation für die bereits im Team befindlichen Personen
Dokumentation wird oft als lästige Übergabearbeit angesehen, doch sie hilft allen im Team heute – insbesondere bei komplexen Berechtigungen, ungewöhnlicher Architektur, Integrationen mit Drittanbietern und wichtigen Geschäftsregeln.
Es ist kein umfangreiches Handbuch erforderlich. Kurze Anmerkungen dazu, wie die Authentifizierung funktioniert, wie Daten fließen, wo sich die wichtigsten Funktionen befinden und warum bestimmte Entscheidungen getroffen wurden, sparen Stunden Zeit. Wenn der ursprüngliche Entwickler geht, ist es nicht mehr möglich, ihn um Hilfe zu bitten – und in langfristigen Projekten geht das immer so.
Refactoring ist Wartung, keine Anerkennung von Fehlern
Refactoring ist kein Beweis dafür, dass der ursprüngliche Code schlecht war. Anforderungen, Teams und Produkte ändern sich, und Code, der vor einem Jahr noch passte, entspricht möglicherweise nicht mehr dem jeweiligen Problem.
Die Gefahr besteht darin, zu argumentieren, dass etwas „schon funktioniert“. Auch ein dreibeiniger Stuhl funktioniert, bis jemand darauf sitzt. Kleine, regelmäßige Refaktorisierungen unter Verwendung von Tests halten die technische Verschuldung in Schach.
Konstanz ist wichtiger als persönlicher Geschmack
Ein Entwickler schreibt Identifikatoren so:
camelCase
Ein anderer bevorzugt es so:
snake_case
Und ein Dritter erfindet alle paar Wochen ein neues Komponentenmuster. Ein großes Team kann nicht effektiv arbeiten, wenn jede Datei dem Geschmack des jeweiligen Autors folgt – daher sollte die Konsistenz durch Folgendes automatisiert werden:
- einen Linter mit vereinbarten Regeln
- einen automatischen Formatierer
- dokumentierte Namenskonventionen
- gemeinsam überprüfte Muster für häufige Aufgaben
Die Codebasis sollte wie ein einziges Projekt wirken, nicht wie das Ergebnis von Diskussionen zwischen Dutzenden Entwicklern bezüglich der Dateistrukturen.
Wählen Sie die Architektur, die Ihr Team tatsächlich umsetzen kann
Architekturdebatten sind ein beliebtes Hobby: Microservices, Monorepos, Clean Architecture, domain-driven Design – und immer hat jemand ein Diagramm parat. Doch die ausgefeilteste Option ist nicht automatisch die beste. Eine gute Wahl erfüllt drei Kriterien:
- sie löst tatsächlich die von Ihnen bestehenden Probleme
- das gesamte Team kann sie erklären
Ein einfaches Design, das konsequent angewandt wird, ist immer besser als ein brillantes Design, das nur sein Erfinder versteht.
Warum all das nicht aufregend ist – und warum es funktioniert
Langefristige Wartung verändert die Bedeutung von „guter Entwicklung“: Die Schreibgeschwindigkeit ist weniger wichtig als Lesbarkeit, die Einfachheit zukünftiger Änderungen, die Bedürfnisse anderer Entwickler sowie das Verhalten in der Produktion. Als Arbeitscheckliste:
- Komponenten bleiben klein und einzigartig in ihrer Funktion
- Der globale Zustand besteht aus einer kurzen, überlegten Liste
- Namen beschreiben die Absicht
- Jedes neue Paket hat eine Begründung
- Die Ordnerstruktur folgt einer dokumentierten Regel
- Refactoring findet kontinuierlich statt
- Kritische Benutzerflüsse haben Tests
- Produktionsfehler sind sichtbar
- Die einfachere Option gewinnt bei Gleichstand
Diese Regeln sind nicht besonders attraktiv, was vermutlich der Grund dafür ist, dass sie Bestand haben. Zu strukturellen Vorgehensweisen auf der Architekturebene siehe zehn Architekturgewohnheiten, die Codebasen für die Frontend-Entwicklung jahrelang wartbar halten.
Kernpunkte
- Große React-Anwendungen leiden selten aufgrund von React selbst; sie leiden vielmehr darunter, dass sich kleine Abkürzungen ansammeln: ein riesiger Komponenten-Code, eine rätselhafte Hilfsfunktion, ein unnötiges Paket sowie eine Lösung, die ursprünglich nur vorübergehend gedacht war.
- Wartbarkeit kann nicht erst am Ende hinzugefügt werden. Sie entsteht aus der Summe vieler kleiner Entscheidungen, die nacheinander getroffen werden.
- Der günstigste Zeitpunkt, um diese Gewohnheiten anzuwenden, ist vor dem Auftreten von Problemen – wenn das Aufteilen einer Komponente oder die Ablehnung einer Abhängigkeit noch nur Minuten statt Wochen kostet.
Verwandte Artikel
- Zehn Architekturgewohnheiten, die Codebasen für das Frontend jahrelang wartbar halten – Erklärt strukturelle Gewohnheiten wie die Optimierung für Löschbarkeit, einen klaren Datenfluss sowie die Trennung der Geschäftslogik, die dabei helfen, dass Codebasen auch nach jahrelangen Änderungen weiterhin wartbar bleiben.
- Speichern von Ursachen, Ableiten von Konsequenzen: Design von minimaler React-State — Lernen Sie, überflüssigen React-State zu erkennen, Effekt-getriebene Synchronisierungsketten sowie boolesche Flags durch abgeleitete Werte und Status-Unionen zu ersetzen, und zu entscheiden, wo der State gespeichert werden sollte.