Flutter/Dart Code-Review Best Practices
Umfassende, bibliotheksunabhängige Checkliste für die Überprüfung von Flutter/Dart-Anwendungen. Diese Prinzipien gelten unabhängig davon, welche State-Management-Lösung, Routing-Bibliothek oder DI-Framework verwendet wird.
1. Allgemeine Projekthygiene
- Das Projekt folgt einer konsistenten Ordnerstruktur (Feature-first oder Layer-first)
- Korrekte Trennung der Belange: UI, Geschäftslogik, Datenebenen
- Keine Geschäftslogik in Widgets; Widgets sind rein präsentativ
-
pubspec.yamlist sauber – keine ungenutzten Abhängigkeiten, Versionen sind angemessen fixiert -
analysis_options.yamlenthält ein strenges Lint-Set mit aktivierten strengen Analyzer-Einstellungen - Keine
print()-Anweisungen im Produktionscode – verwenden Siedart:developerlog()oder ein Logging-Paket - Generierte Dateien (
.g.dart,.freezed.dart,.gr.dart) sind aktuell oder in.gitignore - Plattformspezifischer Code ist hinter Abstraktionen isoliert
2. Dart-Sprachfallen
- Implizites dynamic: Fehlende Typannotationen, die zu
dynamicführen – aktivieren Siestrict-casts,strict-inference,strict-raw-types - Nullsicherheitsmissbrauch: Übermäßiger
!(Bang-Operator) anstelle von korrekten Null-Prüfungen oder Dart 3 Pattern Matching (if (value case var v?)) - Typ-Promotion-Fehler: Verwendung von
this.field, wo die lokale Variablen-Promotion funktionieren würde - Zu breites Fangen:
catch (e)ohneon-Klausel; geben Sie immer Ausnahmetypen an - Fangen von
Error:Error-Subtypen weisen auf Fehler hin und sollten nicht gefangen werden - Unbenutztes
async: Funktionen, die mitasyncmarkiert sind, aber nieawaitverwenden – unnötiger Overhead -
late-Übernutzung:latewird verwendet, wo nullable oder Konstruktor-Initialisierung sicherer wäre; verzögert Fehler zur Laufzeit - String-Konkatenation in Schleifen: Verwenden Sie
StringBufferanstelle von+für iterative String-Erstellung - Veränderlicher Zustand in
const-Kontexten: Felder in Klassen mitconst-Konstruktor sollten nicht veränderlich sein - Ignorieren von
Future-Rückgabewerten: Verwenden Sieawaitoder rufen Sie explizitunawaited()auf, um die Absicht zu signalisieren -
varwofinalfunktioniert: Bevorzugen Siefinalfür lokale Variablen undconstfür Compile-Zeit-Konstanten - Relative Imports: Verwenden Sie
package:-Imports für Konsistenz - Offengelegte veränderliche Sammlungen: Öffentliche APIs sollten unveränderliche Ansichten zurückgeben, nicht rohe
List/Map - Fehlendes Dart 3 Pattern Matching: Bevorzugen Sie switch-Ausdrücke und
if-casegegenüber ausführlichenis-Prüfungen und manuellem Casting - Wegwerf-Klassen für mehrere Rückgabewerte: Verwenden Sie Dart 3 Records
(String, int)anstelle von einmaligen DTOs -
print()im Produktionscode: Verwenden Siedart:developerlog()oder das Logging-Paket des Projekts;print()hat keine Log-Level und kann nicht gefiltert werden
3. Widget-Best Practices
Widget-Zerlegung:
- Kein einzelnes Widget mit einer
build()-Methode, die ~80-100 Zeilen überschreitet - Widgets werden sowohl nach Kapselung als auch nach Änderungsverhalten (Rebuild-Grenzen) aufgeteilt
- Private
_build*()-Hilfsmethoden, die Widgets zurückgeben, werden in separate Widget-Klassen extrahiert (ermöglicht Element-Wiederverwendung, const-Weitergabe und Framework-Optimierungen) - Stateless Widgets werden gegenüber Stateful bevorzugt, wenn kein veränderlicher lokaler Zustand benötigt wird
- Extrahierte Widgets befinden sich in separaten Dateien, wenn sie wiederverwendbar sind
Const-Verwendung:
-
const-Konstruktoren werden wo immer möglich verwendet – verhindert unnötige Rebuilds -
const-Literale für Sammlungen, die sich nicht ändern (const [],const {}) - Der Konstruktor wird als
constdeklariert, wenn alle Felder final sind
Key-Verwendung:
-
ValueKeywird in Listen/Grids verwendet, um den Zustand bei Neuanordnungen zu erhalten -
GlobalKeywird sparsam verwendet – nur wenn der Zugriff auf den Zustand über den gesamten Baum wirklich notwendig ist -
UniqueKeywird inbuild()vermieden – es erzwingt einen Rebuild bei jedem Frame -
ObjectKeywird verwendet, wenn die Identität auf einem Datenobjekt basiert und nicht auf einem einzelnen Wert
Theming und Designsystem:
- Farben stammen aus
Theme.of(context).colorScheme– keine hartcodiertenColors.redoder Hex-Werte - Textstile stammen aus
Theme.of(context).textTheme– kein Inline-TextStylemit rohen Schriftgrößen - Dark-Mode-Kompatibilität wurde überprüft – keine Annahmen über hellen Hintergrund
- Abstände und Größen verwenden konsistente Design-Tokens oder Konstanten, keine magischen Zahlen
Build-Methoden-Komplexität:
- Keine Netzwerkaufrufe, Datei-I/O oder schwere Berechnungen in
build() - Kein
Future.then()oderasync-Arbeit inbuild() - Keine Erstellung von Abonnements (
.listen()) inbuild() -
setState()auf den kleinstmöglichen Teilbaum beschränkt
4. State-Management (Bibliotheksunabhängig)
Diese Prinzipien gelten für alle Flutter-State-Management-Lösungen (BLoC, Riverpod, Provider, GetX, MobX, Signals, ValueNotifier usw.).
Architektur:
- Geschäftslogik lebt außerhalb der Widget-Ebene – in einer State-Management-Komponente (BLoC, Notifier, Controller, Store, ViewModel usw.)
- State-Manager erhalten Abhängigkeiten per Injektion, nicht durch interne Konstruktion
- Eine Dienst- oder Repository-Ebene abstrahiert Datenquellen – Widgets und State-Manager sollten keine APIs oder Datenbanken direkt aufrufen
- State-Manager haben eine einzige Verantwortung – keine „Gott“-Manager, die nicht zusammenhängende Belange behandeln
- komponentenübergreifende Abhängigkeiten folgen den Konventionen der Lösung:
- Bei Riverpod: Anbieter, die von anderen Anbietern über
ref.watchabhängen, sind erwartet – kennzeichnen Sie nur zirkuläre oder übermäßig verworrene Ketten - Bei BLoC: Blöcke sollten nicht direkt von anderen Blöcken abhängen – bevorzugen Sie gemeinsam genutzte Repositories oder Koordination auf Präsentationsebene
- Bei anderen Lösungen: Befolgen Sie die dokumentierten Konventionen für die Kommunikation zwischen Komponenten
- Bei Riverpod: Anbieter, die von anderen Anbietern über
Unveränderlichkeit und Wertegleichheit (für Lösungen mit unveränderlichem Zustand: BLoC, Riverpod, Redux):
- Zustandsobjekte sind unveränderlich – neue Instanzen werden über
copyWith()oder Konstruktoren erstellt, nie direkt mutiert - Zustandsklassen implementieren
==undhashCodekorrekt (alle Felder in den Vergleich einbezogen) - Der Mechanismus ist im gesamten Projekt konsistent – manuelle Überschreibung,
Equatable,freezed, Dart Records oder andere - Sammlungen innerhalb von Zustandsobjekten werden nicht als rohe veränderliche
List/Mapoffengelegt
Reaktivitätsdisziplin (für reaktive Mutationslösungen: MobX, GetX, Signals):
- Zustand wird nur über die reaktive API der Lösung mutiert (
@actionin MobX,.valuebei Signalen,.obsin GetX) – direkte Feldmutation umgeht die Änderungsverfolgung - Abgeleitete Werte verwenden den berechneten Mechanismus der Lösung, anstatt redundant gespeichert zu werden
- Reaktionen und Entsorger werden ordnungsgemäß bereinigt (
ReactionDisposerin MobX, Effektbereinigung in Signals)
Zustandsform-Design:
- Sich gegenseitig ausschließende Zustände verwenden versiegelte Typen, Union-Varianten oder den integrierten asynchronen Zustandstyp der Lösung (z. B. Riverpods
AsyncValue) – keine booleschen Flags (isLoading,isError,hasData) - Jeder asynchrone Vorgang modelliert Laden, Erfolg und Fehler als unterschiedliche Zustände
- Alle Zustandsvarianten werden in der UI erschöpfend behandelt – keine stillschweigend ignorierten Fälle
- Fehlerzustände tragen Fehlerinformationen zur Anzeige; Ladezustände enthalten keine veralteten Daten
- Nullable-Daten werden nicht als Ladeindikator verwendet – Zustände sind explizit
// SCHLECHT – boolesche Flag-Suppe ermöglicht unmögliche Zustände
class UserState {
bool isLoading = false;
bool hasError = false; // isLoading && hasError ist darstellbar!
User? user;
}
// GUT (unveränderlicher Ansatz) – versiegelte Typen machen unmögliche Zustände undarstellbar
sealed class UserState {}
class UserInitial extends UserState {}
class UserLoading extends UserState {}
class UserLoaded extends UserState {
final User user;
const UserLoaded(this.user);
}
class UserError extends UserState {
final String message;
const UserError(this.message);
}
// GUT (reaktiver Ansatz) – beobachtbares Enum + Daten, Mutationen über die Reaktivitäts-API
// enum UserStatus { initial, loading, loaded, error }
// Verwenden Sie die Observable/Signal Ihrer Lösung, um Status und Daten getrennt zu umschließen
Rebuild-Optimierung:
- Zustandskonsumenten-Widgets (Builder, Consumer, Observer, Obx, Watch usw.) sind so eng wie möglich eingegrenzt
- Selektoren werden verwendet, um nur dann neu zu bauen, wenn sich bestimmte Felder ändern – nicht bei jeder Zustandsemission
-
const-Widgets werden verwendet, um die Rebuild-Ausbreitung durch den Baum zu stoppen - Berechneter/abgeleiteter Zustand wird reaktiv berechnet, nicht redundant gespeichert
Abonnements und Entsorgung:
- Alle manuellen Abonnements (
.listen()) werden indispose()/close()gekündigt - Stream-Controller werden geschlossen, wenn sie nicht mehr benötigt werden
- Timer werden im Entsorgungslebenszyklus abgebrochen
- Der vom Framework verwaltete Lebenszyklus wird gegenüber manuellem Abonnement bevorzugt (deklarative Builder gegenüber
.listen()) -
mounted-Prüfung vorsetStatein asynchronen Callbacks -
BuildContextwird nachawaitnicht ohne Prüfung voncontext.mountedverwendet (Flutter 3.7+) – veralteter Kontext führt zu Abstürzen - Keine Navigation, Dialoge oder Scaffold-Nachrichten nach asynchronen Lücken ohne Überprüfung, ob das Widget noch gemountet ist
-
BuildContextwird niemals in Singletons, State-Managern oder statischen Feldern gespeichert
Lokaler vs. globaler Zustand:
- Flüchtiger UI-Zustand (Checkbox, Slider, Animation) verwendet lokalen Zustand (
setState,ValueNotifier) - Gemeinsam genutzter Zustand wird nur so hoch wie nötig gehoben – nicht übermäßig globalisiert
- Funktionsbereichsbezogener Zustand wird ordnungsgemäß entsorgt, wenn die Funktion nicht mehr aktiv ist
5. Performance
Unnötige Rebuilds:
-
setState()wird nicht auf der Root-Widget-Ebene aufgerufen – lokalisieren Sie Zustandsänderungen -
const-Widgets werden verwendet, um die Rebuild-Ausbreitung zu stoppen -
RepaintBoundarywird um komplexe Teilbäume verwendet, die unabhängig neu zeichnen - Der
child-Parameter vonAnimatedBuilderwird für Teilbäume verwendet, die unabhängig von der Animation sind
Teure Operationen in build():
- Kein Sortieren, Filtern oder Abbilden großer Sammlungen in
build()– in der State-Management-Ebene berechnen - Keine Regex-Kompilierung in
build() - Die Verwendung von
MediaQuery.of(context)ist spezifisch (z. B.MediaQuery.sizeOf(context))
Bildoptimierung:
- Netzwerkbilder verwenden Caching (jede für das Projekt geeignete Caching-Lösung)
- Angemessene Bildauflösung für das Zielgerät (kein Laden von 4K-Bildern für Thumbnails)
-
Image.assetmitcacheWidth/cacheHeight, um in der Anzeigegröße zu dekodieren - Platzhalter- und Fehler-Widgets werden für Netzwerkbilder bereitgestellt
Lazy Loading:
-
ListView.builder/GridView.builderwird anstelle vonListView(children: [...])für große oder dynamische Listen verwendet (konkrete Konstruktoren sind für kleine, statische Listen in Ordnung) - Paginierung wird für große Datenmengen implementiert
- Verzögertes Laden (
deferred as) wird für schwere Bibliotheken in Web-Builds verwendet
Sonstiges:
- Das
Opacity-Widget wird bei Animationen vermieden – verwenden SieAnimatedOpacityoderFadeTransition - Clipping wird bei Animationen vermieden – Bilder vorab zuschneiden
-
operator ==wird nicht auf Widgets überschrieben – verwenden Sie stattdessenconst-Konstruktoren - Intrinsische Dimensionierungs-Widgets (
IntrinsicHeight,IntrinsicWidth) werden sparsam verwendet (zusätzlicher Layout-Durchlauf)
6. Testen
Testtypen und Erwartungen:
- Unit-Tests: Decken die gesamte Geschäftslogik ab (State-Manager, Repositories, Hilfsfunktionen)
- Widget-Tests: Decken einzelnes Widget-Verhalten, Interaktionen und visuelle Ausgabe ab
- Integrationstests: Decken kritische Benutzerabläufe von Anfang bis Ende ab
- Golden-Tests: Pixelgenaue Vergleiche für designkritische UI-Komponenten
Abdeckungsziele:
- Ziel: 80 %+ Zeilenabdeckung bei der Geschäftslogik
- Alle Zustandsübergänge haben entsprechende Tests (Laden → Erfolg, Laden → Fehler, Wiederholung usw.)
- Randfälle getestet: leere Zustände, Fehlerzustände, Ladezustände, Grenzwerte
Testisolierung:
- Externe Abhängigkeiten (API-Clients, Datenbanken, Dienste) werden gemockt oder gefälscht
- Jede Testdatei testet genau eine Klasse/Einheit
- Tests überprüfen das Verhalten, nicht die Implementierungsdetails
- Stubs definieren nur das Verhalten, das für jeden Test benötigt wird (minimales Stubbing)
- Kein gemeinsam genutzter veränderlicher Zustand zwischen Testfällen
Widget-Testqualität:
-
pumpWidgetundpumpwerden korrekt für asynchrone Operationen verwendet -
find.byType,find.text,find.byKeywerden angemessen verwendet - Keine flaky Tests, die von Timing abhängen – verwenden Sie
pumpAndSettleoder explizitespump(Duration) - Tests laufen in CI und Fehler blockieren Merges
7. Barrierefreiheit
Semantische Widgets:
- Das
Semantics-Widget wird verwendet, um Screenreader-Beschriftungen bereitzustellen, wo automatische Beschriftungen unzureichend sind -
ExcludeSemanticswird für rein dekorative Elemente verwendet -
MergeSemanticswird verwendet, um verwandte Widgets zu einem einzigen zugänglichen Element zu kombinieren - Bilder haben die Eigenschaft
semanticLabelgesetzt
Screenreader-Unterstützung:
- Alle interaktiven Elemente sind fokussierbar und haben sinnvolle Beschreibungen
- Die Fokusreihenfolge ist logisch (folgt der visuellen Lesereihenfolge)
Visuelle Barrierefreiheit:
- Kontrastverhältnis >= 4,5:1 für Text gegenüber Hintergrund
- Antippbare Ziele sind mindestens 48x48 Pixel groß
- Farbe ist nicht der einzige Indikator für den Zustand (Symbole/Text daneben verwenden)
- Text skaliert mit den Systemschriftgrößeneinstellungen
Interaktionsbarrierefreiheit:
- Keine No-Op-
onPressed-Callbacks – jeder Button tut etwas oder ist deaktiviert - Fehlerfelder schlagen Korrekturen vor
- Der Kontext ändert sich nicht unerwartet, während der Benutzer Daten eingibt
8. Plattformspezifische Belange
iOS/Android-Unterschiede:
- Plaatformadaptive Widgets werden verwendet, wo angemessen
- Die Rücknavigation wird korrekt behandelt (Android-Zurück-Button, iOS-Wisch-zum-Zurückgehen)
- Statusleiste und Safe Area werden über das
SafeArea-Widget behandelt - Plattformspezifische Berechtigungen werden in
AndroidManifest.xmlundInfo.plistdeklariert
Responsive Design:
-
LayoutBuilderoderMediaQuerywerden für responsive Layouts verwendet - Breakpoints werden konsistent definiert (Telefon, Tablet, Desktop)
- Text läuft auf kleinen Bildschirmen nicht über – verwenden Sie
Flexible,Expanded,FittedBox - Landscape-Orientierung wurde getestet oder explizit gesperrt
- Webspezifisch: Maus-/Tastaturinteraktionen werden unterstützt, Hover-Zustände sind vorhanden
9. Sicherheit
Sichere Speicherung:
- Sensitive Daten werden nicht in Klartext in
SharedPreferences, Datenbanken oder als Dateien gespeichert - Token und Geheimnisse werden mit
flutter_secure_storageoder der Schlüsselbund-/Kettenspeicher-API der Plattform gespeichert - Im App-Speicher zwischengespeicherte Daten werden bei der Abmeldung oder Sitzungszeitüberschreitung gelöscht
Netzwerksicherheit:
- Alle API-Anfragen verwenden HTTPS – keine HTTP-Anfragen in der Produktion
- SSL/TLS-Pinning wird implementiert, wo hohe Sicherheitsanforderungen bestehen
- API-Schlüssel sind nicht im Quellcode fest codiert – werden aus Umgebungsvariablen oder sicheren Konfigurationsdiensten gelesen
- Anfragen validieren Eingaben auf der Clientseite – vertrauen Sie niemals ausschließlich der serverseitigen Validierung
Eingabevalidierung:
- Alle Benutzereingaben werden validiert und bereinigt (Formularfelder, Deep Links, WebView-Eingaben)
- Keine direkte Ausgabe von unsicheren Benutzereingaben – HTML-Escape oder Markdown-Sanitisierung verwenden
- Deep Links werden gegen eine definierte Liste erwarteter Routen validiert
Autorisierung und Authentifizierung:
- Authentifizierungstoken werden sicher gespeichert und bei Zugriffsverweigerung gelöscht
- Die Sitzungsverwaltung hat angemessene Ablauf- und Erneuerungsmechanismen
- Der Zugriff auf bestimmte Bildschirme oder Funktionen wird in einer zentralen Auth-Schicht überprüft
- Daten aus APIs, die nicht autorisierte Inhalte enthalten, werden filtriert
10. Abhängigkeitsinjektion
DI-Konfiguration:
- Abhängigkeiten werden über einen zentralen DI-Container bereitgestellt (get_it, provider, riverpod, injectable usw.)
- DI-Einrichtung ist aspektorientiert – keine manuelle Konstruktorverdrahtung, die über die gesamte Codebasis verstreut ist
- Keine „Service-Locator“-Antimuster – registrierte Typen werden konstruiert, wo sie benötigt werden
- Singleton-Registrierungen werden nur für zustandslose Dienste oder gemeinsam genutzte Ressourcen verwendet
- Faktorien werden für vorübergehende Abhängigkeiten verwendet, die jedes Mal neu erstellt werden müssen
- Der DI-Container wird in Tests einfach durch Mocks ersetzt
Testbarkeit:
- Anbieter/Registrierungen können in Tests überschrieben werden
- Keine versteckten Abhängigkeiten – alle Abhängigkeiten werden über den DI-Container oder den Konstruktor weitergegeben
- Abhängigkeiten sind im Produktions- und Test-Code einheitlich
11. Fehlerbehandlung und Logging
Fehlerbehandlung:
- Alle try-catch-Blöcke behandeln spezifische Ausnahmetypen – kein breites
catch (Exception e)ohne Unterscheidung - Die Fehlerbehandlung erfolgt auf der geeigneten Abstraktionsebene – UI fängt Ausnahmen von Repositorys/Diensten
- Fehler im Zusammenhang mit der Geschäftslogik werden den Benutzern über Snackbars, Dialoge oder Inline-Fehlermeldungen angezeigt
- Ausnahmen werden nicht stillschweigend geschluckt – selbst ein Wiederholungsmechanismus protokolliert die ursprünglichen Fehler
- Kritische Fehler lösen einen Absturz aus oder zeigen eine Fehlerbildschirm-Widget an (Erase to gray / ErrorWidget)
Logging:
- Das Logging verwendet ein strukturiertes Logging-Framework (z. B.
logging,talker,loggy, benutzerdefinierte Logger) - Logs haben angemessene Ebenen (debug, info, warning, error)
- Keine sensiblen Daten in Logs (Passwörter, Token, persönliche Daten)
- Fehlerlogs enthalten aussagekräftige Fehlermeldungen und Stack-Traces
- Logs sind in Produktions-Builds nicht zugänglich (kompiliert oder anderweitig entfernt)
12. Internationalisierung und Lokalisierung (i18n/l10n)
Lokalisierungseinrichtung:
- Strings sind nicht hartcodiert – alle Textinhalte werden über die i18n-Lösung des Projekts referenziert (ARB-Dateien,
.i18n.yaml,l10n.yaml) - Anwendung unterstützt mindestens eine Sprache (normalerweise die Standardsprache) + Fallback
- RTL-Unterstützung wird getestet, wenn Arabisch, Hebräisch oder andere RTL-Sprachen in der Zielgruppe sind
- Textlayout wird mit RTL getestet (End-to-End-Ausrichtung, nicht nur einzelne Zeichen)
Übersetzungsqualität:
- Alle UI-Strings haben Übersetzungen – keine fehlenden Schlüssel in lokalen Builds
- Platzhalter in übersetzten Strings sind korrekt – keine fehlenden oder falsch platzierten Parameter
- Lokalisierte Strings berücksichtigen den Kontext (formell vs. informell, Pluralformen)
13. Routinge und Navigation
Routing-Architektur:
- Routing verwendet eine deklarative API (Navigator 2.0, go_router, auto_route, beamer) – keine versteckten
Navigator.push-Aufrufe, die tief in Widgets vergraben sind - Routen sind als benannte oder typisierte Routen definiert – keine rohen Zeichenfolgen, die Zielpfade darstellen, es sei denn, die Architektur des Projekts erfordert dies
- Tiefenverknüpfung wird unterstützt (wenn die App über eine externe URL geöffnet wird, navigiert sie zur richtigen Route)
- Die Navigation ist zustandsbehaftet – zurückgeschobene Seiten bewahren ihren Scroll-Zustand, es sei denn, sie werden explizit gelöscht
Navigationsmuster:
- Auth-Flow-Aufrufe sind in einer Auth-Route oder einem Auth-Guard zentralisiert – Navigation wird nicht über mehrere Widgets verteilt
- Modale Präsentation wird konsistent verwendet (BottomSheet, Dialog, Vollbild-Modal)
- Routenparameter werden über die Routing-Lösung übergeben – nicht über
Navigator.pushmit benutzerdefinierten Argumenten - Fehlerbehandlung wird für fehlgeschlagene Navigationen hinzugefügt (404-Seite, Fallback-Route)
14. Build-Konfiguration
Build-Varianten:
- Unterschiedliche Build-Varianten werden definiert (debug, profile, release) mit unterschiedlichen Konfigurationen für
main.dart,GoogleService-Info.plist,AndroidManifest.xml - Umgebungs-Enums oder -Konstanten werden verwendet, um die Konfiguration zu abstrahieren – keine magischen Strings für API-URLs oder Funktionen
- Die Konfiguration wird nach Umgebung getrennt (dev, staging, production)
Flutter-spezifische Build-Konfiguration:
- Splash-Screen wird für alle Plattformen konfiguriert –
flutter_native_splashoder benutzerdefinierte native Splash-Screens - App-Icon-Sets sind für alle erforderlichen Auflösungen vorhanden –
flutter_launcher_iconsoder benutzerdefinierte native Konfiguration - Der Build-Kanal ist korrekt eingerichtet – das Freigeben von Builds an den App Store und Play Store wird über CI/CD gesteuert
Checklisten-Nutzungshinweise
- Richtlinien, keine Regeln – verstehen Sie den Kontext, bevor Sie einen Punkt als Fehler markieren
- Fokus auf „Ob“ und „Warum“, nicht auf „Wie“ – die Checkliste ist implementierungsneutral
- Behandeln Sie Zustandsverwaltung als Prinzip, nicht als Produkt – die aufgeführten Prinzipien gelten für alle Lösungen
- Review in Schichten – analysieren Sie zuerst die Gesamtarchitektur, dann tiefer in die Implementierung
- Überspringen Sie Rubriken, die für den aktuellen Review nicht relevant sind – verwenden Sie das, was für Ihre Codebasis relevant ist
Bibliotheksspezifische Hinweise
Riverpod
- Verwenden Sie
ref.watchfür automatische Neuerstellung,ref.listenfür Nebenwirkungen AsyncValuemit.when()für erschöpfende Zustandsbehandlung- Anbieter sollten andere Anbieter niemals direkt verändern – respektieren Sie die unidirektionale Datenfluss
Provider.familywird für parametrisierte Abfragen verwendet
BLoC
- Verwenden Sie
BlocProviderfür die Bereitstellung undBlocBuilder/BlocSelectorfür den Konsum - Die
add-Methode ist der einzige Weg, um Ereignisse zu senden –emitsollte nur innerhalb vonmapEventToStateaufgerufen werden - Blöcke sollten keine anderen Blöcke direkt abhängig machen – verwenden Sie gemeinsam genutzte Repositories
- Verwenden Sie
BlocObserverfür das applikationsweite Debugging und Logging
Provider
- Verwenden Sie
ChangeNotifierProviderfür veränderlichen Zustand undProviderfür unveränderliche Werte ConsumerundSelectorfür granulare Rebuild-SteuerungProxyProviderist der Mechanismus für Provider, die von anderen abhängen – direkteProvider.of-Aufrufe zwischen ihnen vermeiden
GetX
- Verwenden Sie
GetX/Obxfür granulare Rebuilds – vermeiden SieGetBuilder, es sei denn, Sie benötigen die manuelle Steuerung - Controller sollten keine Widgets direkt verändern – Rückgabewerte von Methoden ändern die Ansicht über Observablen
Get.lazyPutwird für Controller mit verzögerter Initialisierung verwendet
MobX
- Verwenden Sie
@observablefür reaktive Felder und@actionfür Mutationen Observer-Widgets sollten eng um das spezifische Observable herum angeordnet sein, das sie lesen@computedwird für abgeleitete Werte verwendet – speichern Sie sie nicht manuell
Signals (z. B. signals, solidart)
- Verwenden Sie
Signalfür veränderliche Zustandsbehälter undComputedfür abgeleitete Werte effect()für Nebenwirkungen, die automatisch aufgeräumt werden- Signale in Widgets:
watch(ähnlich wieConsumer) für granulare Rebuilds - Vermeiden Sie die direkte Mutation von Signal-Wrapper-Objekten – aktualisieren Sie
.value