overfetch: Anfragen-Audit
Du erstellst einen Bericht, nach dem jemand handeln wird: eine priorisierte Liste der Arten, wie diese App mehr Anfragen stellt, als ihre Bildschirme benötigen, jeweils mit Beweis, Korrektur und der Anzahl, um die es sich bewegen soll. Der Bericht ist nur nützlich, wenn jeder Fund einen skeptischen Leser übersteht. Daher verwendet die folgende Methode den größten Teil ihrer Mühe auf die Verifizierung und darauf, klar zu sagen, was geprüft wurde und was nicht.
Was du lieferst
Ein Markdown-Bericht (Vorlage in references/report-template.md) mit:
- Umfang und Beweise: welcher Baum, welcher Commit, welche Captures.
- Abdeckung: jede Route, jeder Lazy-Chunk und jede geschützte Datenstelle, und welche davon nur im Code gelesen vs. in einem Capture ausgeführt wurden.
- Funde, priorisiert nach dem Wachstum der Anfragenanzahl, jeder mit
Datei:Zeileoder Capture-Beweis, Ursache, Korrekturmuster, erwarteter Differenz, Aufwand. - Nicht-Funde: Muster, die du geprüft und ausgeschlossen hast, mit Begründung. Diese verhindern, dass die nächste Person erneut nachforscht.
- Offene Produktentscheidungen und Backend-Anfragen: Dinge, die einen Menschen oder eine API-Änderung erfordern, keinen Client-Patch.
- Grenzen: Was das Audit nicht messen konnte.
Biete anschließend an, die Korrekturen zu implementieren, ein PR pro Fund, beginnend mit dem höchsten Schweregrad. Beginne nicht während des Audits mit Korrekturen.
Prinzipien
Ein Regex-Treffer ist ein Kandidat, kein Fund. scripts/static_scan.py liest Text. Es kann nicht zwischen einer 3-Zeilen-Registerkartenleiste und einer 3.000-Zeilen-Tabelle oder zwischen einer absichtlichen Stamm-Invalidierung nach einem Bulk-Import und einer faulen unterscheiden. Lies den Code hinter jedem Kandidaten, den du melden willst. Sage, was du gelesen hast.
Gemessen geht vor Behauptet. Ein Capture der laufenden App ist die Wahrheit über die Anfrageanzahl; der Code ist die Wahrheit über das Warum. Kommentare, Doku und alte Audit-Berichte sind Behauptungen. Wenn eine Doku sagt „60 Sekunden frisch“ und der Code sagt staleTime: Infinity, melde den Code, notiere den Widerspruch und halte den Commit-Hash fest, den du gelesen hast.
Kenntnis der tatsächlichen Semantik der Bibliothek, bevor du sie beschuldigst. Die Fehler, die am meisten kosten, sind selbstbewusste Behauptungen, die falsch sind: „der Cache dedupliziert dies“, wenn die Schlüssel unterschiedlich sind; „Pollen pausiert im Leerlauf“, wenn es nur pausiert, wenn versteckt; „drei Aufrufer, drei Abrufe pro Minute“, wenn die Bibliothek gleichschlüssige Timer neu synchronisiert und den Abruf teilt. references/library-facts.md enthält die Fakten für TanStack Query, SWR, Apollo und RTK Query, mit dem Quellpfad, gegen den jeder geprüft wurde. Lies den Abschnitt für die verwendete Bibliothek, bevor du einen Satz über Caching oder Pollen schreibst, und wenn eine Behauptung einen Fund entscheidet, öffne die Quelle der installierten Bibliothek und bestätige es.
Priorisierung nach Wachstum, nicht nach Anzahl. Ein Lesevorgang, der mit Zeilen, mit eingehängten Komponenten oder mit der Zeit skaliert, ist schlimmer als ein fester Duplikat. Die folgende Schweregrad-Rubrik handelt von der Wachstumsklasse.
Abdeckung hat einen Nenner. Ein Capture sieht nur den Code, der geladen wurde, während er lief: die Routen, die jemand besucht hat, die Dialoge und Tabs, die jemand geöffnet hat, die Flags, die aktiv waren, die Zustände, die das Backend zurückgegeben hat. Lazy Chunks, geschützte Bildschirme und Fehlerpfade sind genau die Stellen, an denen sich Anfragefehler verstecken, weil während der Entwicklung niemand darauf schaut. Daher beginnt jedes Audit mit der vollständigen Oberfläche (coverage_map.py), jedes „kein Problem“ ist auf das Abgedeckte beschränkt, und der Bericht sagt, welche Teile live ausgeführt und welche nur gelesen wurden.
Sage, was in Ordnung ist. Ein Hover-Prefetch, der ein deduplizierter Lesevorgang pro Zeile ist, ist ein Nicht-Fund. Schreibe ihn als einen nieder. Ein Bericht, der nur Probleme auflistet, lässt den Leser fragen, was sonst noch übersehen wurde.
Zahlen kommen von irgendwoher. Jede Anzahl im Bericht führt auf eine Capture-Datei oder einen Codepfad zurück. Erfinde keine „6+" oder „ungefähr 100" aus der Intuition; wenn du nicht messen kannst, sage „ungemessen" und gib die Formel an (Zeilen × Lesevorgänge pro Zeile).
Arbeitsablauf
Teile dem Benutzer von Anfang an mit, wie lange dies dauert. Ein rein statisches Audit einer mittelgroßen App dauert 15 bis 30 Minuten Lesezeit. Das Hinzufügen von Live-Captures dauert 15 bis 45 Minuten, je nachdem, wie viele Journeys relevant sind.
1. Umfang
- Finde das/die App-Root(s) und die Datenbibliothek aus
package.json(der Scan gibt beides aus). Monorepos: Scanne jede App separat. - Finde die Datenebene: Query-Key-Fabriken, Transport-/Servicemodule, die
QueryClient-Konstruktion (oder SWR-Konfiguration, Apollo-Client, RTKcreateApi). Lies zuerst die Standardwerte; sie legen die Basis fest, an der jeder Fund gemessen wird. - Liste die relevanten Bildschirme auf: normalerweise die Landeseite nach dem Anmelden, die am stärksten frequentierte Liste und jeder Bildschirm, der stundenlang offen bleibt (eine Shell, ein Monitor). Frage den Benutzer, wenn es nicht offensichtlich ist; ansonsten wähle diese drei aus und sage es.
- Notiere den Commit:
git rev-parse HEAD. Jede Berichtszeile bezieht sich auf diese Revision. - Entscheide, ob ein Live-Capture möglich ist: ein laufender Dev-Server plus ein Browser, den du steuern kannst, oder ein HAR, das der Benutzer bereits hat. Wenn beides nicht der Fall ist, ist das Audit rein statisch, und der Bericht muss dies in seinem ersten Absatz sagen.
2. Abdeckungskarte
python3 <skill-dir>/scripts/coverage_map.py <app-root> --format md --out coverage.md
Dies ist der Nenner. Es listet jede Route (dateibasierte Router für TanStack Router, Next.js, Remix, SvelteKit und SolidStart; code-definierte Router durch ihre Pfadliterale; ansonsten Einstiegsdateien), jede dynamische import()-Grenze mit den dahinterliegenden Lesevorgängen, jedes von einem Flag, einer Plattform, einer Berechtigung oder einer Umgebungsprüfung geschützte Datenmodul, jedes Endpunkt-Pfadliteral im Quellcode und die Datenmodule, die keine Route erreicht. Es läuft in ein paar Sekunden bei einer App mit 2.000 Dateien.
Verwende es auf drei Arten:
- Zur Planung. Der Capture-Plan am Ende benennt die Routen mit den meisten Lesestellen, die Lazy Chunks, die ausgelöst werden müssen, und die Gates, die umgeschaltet werden müssen. Verwende ihn, um die Journeys in Schritt 6 auszuwählen, anstatt zu raten.
- Zur Eingrenzung des Scans. Ein Fund in einem Modul, das keine Route erreicht, ist toter Code oder wird von etwas erreicht, das die Karte nicht auflösen kann. Prüfe welches, bevor du es als live bezeichnest.
- Zum Schließen des Kreislaufs. Nach dem Capture führe es erneut mit den Captures aus; es markiert, welche Routen besucht wurden, welche Module und Lazy Chunks geladen wurden (exakt, bei einem Dev-Server-Capture), welche geschützten Module ausgeführt wurden und welche Endpunktpfade erschienen sind:
python3 <skill-dir>/scripts/coverage_map.py <app-root> --har home.har --har library.har --visited home.har.visited.txt --format md
Wiederhole Captures, bis die Routen und Lazy Chunks mit den meisten Lesestellen abgedeckt sind, oder schreibe auf, warum nicht (keine Fixture-Daten, benötigt eine Rolle, die du nicht hast, Plattform, die du nicht ausführen kannst).
3. Statischer Scan
python3 <skill-dir>/scripts/static_scan.py <app-root> --format md --out scan.md
python3 <skill-dir>/scripts/static_scan.py <app-root> --format json --out scan.json
Der Scan ist schnell (etwa zehn Sekunden für 2.000 Dateien) und benötigt nur Python 3.9+. Er gibt Kandidaten aus mit einem Schweregrad, einer Konfidenz, dem ausgelösten Detektor, Datei:Zeile, warum es wichtig ist, dem Korrekturmuster und einer „Zuerst verifizieren"-Anweisung. Lies scan.md vollständig; das JSON ist für Scripting und für den Evaluierungsrahmen.
Füge --exclude für generierte Bäume hinzu, die die Standardeinstellungen übersehen, --min-confidence medium, wenn die Liste lang ist und du den ersten Durchlauf eng halten möchtest.
4. Kandidaten verifizieren
Arbeite die Kandidaten in dieser Reihenfolge durch: P0 nach Konfidenz, dann P1, dann der Rest. Für jeden von dir behaltenen musst du die Datei geöffnet haben und Folgendes beantworten können:
- Was ist der Multiplikator pro Zeile oder pro Beobachter, und was begrenzt ihn? (Ein
.mapüber ein festes Enum ist kein N+1. Eine Zeilenanzahl aus einer paginierten Liste schon.) - Absorbiert der Cache es bereits? (Gleicher Schlüssel, warmer Eintrag,
enabled: falseLesevorgänge, ein übergeordnetes Element, das das Objekt bereits abgerufen hat.) - Gibt es einen Batch- oder Projektions-Endpunkt, den der Client stattdessen verwenden könnte? Lese den API-Client oder die OpenAPI-Spezifikation. Wenn keiner existiert, ist die Korrektur eine Backend-Anfrage, und der Fund bleibt mit „Backend-blockiert" markiert.
- Wie lautet das Korrekturmuster, namentlich, aus
references/catalog.md?
Verwerfe Kandidaten, die diese Prüfungen nicht bestehen, und behalte die, die du verwirfst und die ein Leser plausiblerweise vorbringen würde: Sie kommen in die Tabelle der Nicht-Funde mit einer einzeiligen Begründung.
Der Katalogeintrag für jeden Detektor listet die spezifischen Bedingungen für Nicht-Funde auf. Lese ihn für jede Detektor-ID, die im Scan vorkommt; er ist nach denselben IDs organisiert.
5. Querschnittsprüfungen, die der Scanner nicht durchführen kann
Diese erfordern Lesen, nicht Grep. Führe alle durch; die meisten Audits finden mindestens eine.
- Schlüsselkanonizität. Wähle die fünf meistgelesenen Endpunkte und finde jeden Query-Key, der jeden davon liest. Zwei Schlüssel für einen Lesevorgang bedeuten zwei Cache-Einträge und zwei Abrufe. Achte besonders auf Listenlesungen unter verschiedenen Projektionen (vollständig vs. nur Namen) und auf Bereichsunterschiede (Org vs. Workspace), die nicht wirklich unterschiedliche Daten sind.
- Geteilte laufende Lesevorgänge. Zwei Komponenten, die im selben Frame mit demselben Schlüssel eingehängt werden, teilen sich eine Anfrage nur, wenn der Schlüssel in diesem Moment identisch ist. Schlüssel, die aus einem Zustand aufgebaut werden, der sich einen Tick später einpendelt (eine ID, die von einer anderen Abfrage kommt), erzeugen zwei Abrufe: einen mit
undefined, einen echten. Überprüfeenabled-Gates bei abhängigen Schlüsseln. - Frische pro Oberfläche. Erstelle eine Tabelle der
staleTimepro Datenfamilie (Identität, Listen, Details, Live-Status) und vergleiche sie mit der Geschwindigkeit, mit der sich jede tatsächlich ändert. Der übliche Fehler ist ein einzelner globaler Wert, der für nichts richtig ist. - Polling-Eigentümerschaft. Liste für jede Polling-Option in einem gemeinsamen Hook oder einer Optionsfabrik die Komponenten auf, die gleichzeitig eingehängt sein können, und den Schlüssel, den jede verwendet. In SWR und Apollo führt jede Instanz ihren eigenen Timer aus, und die Abrufe multiplizieren sich, es sei denn, sie landen in einem Deduplizierungsfenster. In TanStack Query werden die Timer bei jeder Query-Aktualisierung neu gestartet und teilen sich einen Abruf, daher kosten gleichschlüssige Beobachter einen Abruf pro kürzestem Intervall; Stapelung tritt über Schlüssel oder Clients hinweg auf, und der Fund ist die Takt-Eigentümerschaft. In RTK Query läuft ein Timer pro Cache-Eintrag mit dem kürzesten Intervall.
- Fokus und Sichtbarkeit. Was löst Fokus-Neuabrufe auf dieser Plattform aus? Eine Desktop-Shell (Tauri, Electron) verdrahtet oft den OS-Fensterfokus, der viel häufiger ausgelöst wird als die Tab-Sichtbarkeit. Welche Polls pausieren, wenn versteckt, und welche laufen weiter, während der Benutzer auf einem sichtbaren Bildschirm im Leerlauf ist?
- Parallelitäts-Obergrenze. Gibt es einen Begrenzer für Fan-Out-Lesevorgänge, und was ist das? Beachte, dass ein Begrenzer von 6 laufenden Anfragen keine Grenze von 6 Anfragen pro Sekunde ist; schreibe nicht das eine als das andere.
- Paginierungsvertrag. Stoppt der Client bei Sammlungen mit unbekannter Gesamtzahl, wenn eine Seite zu kurz zurückkommt, oder fragt er weiter, bis eine leere Seite erscheint? Überprüfe die Seitengrößen im Vergleich zu dem, was der Bildschirm anzeigen kann.
- Wiederholungen. Was macht der Client bei 403/404/422? Der Bibliotheksstandard wiederholt dreimal; ein N+1 mit 100 Zeilen und einem verbotenen Chunk wird zu 400 Anfragen.
- Persistenz. Wenn der Cache persistiert wird (IndexedDB, localStorage, ein nativer Store), was ist
maxAge, was bricht ihn beim Deployen um, und ist der Store pro Plattform? Ein persistierter Cache ändert, was „kalter Start" in einem Capture bedeutet; sage, welchen du gemessen hast. - Mutationen. Was wird bei den fünf häufigsten Schreibvorgängen invalidiert? Stamm-Invalidierungen nach einem kleinen Schreibvorgang sind der übliche Fund.
6. Live-Capture
Wenn du die App ausführen kannst oder der Benutzer dir einen Capture gegeben hat, hole Zahlen. references/capture.md enthält die Journeys, die ausgeführt werden sollen, die Zustandsmatrix (leer, befüllt, Fehler, langsam, offline, Leerlauf, versteckt, geschützt, kalter und warmer Cache), wie zu erfassen ist (DevTools HAR, Playwright, das gebündelte scripts/capture.mjs oder das eigene Anforderungsprotokoll der App) und wie Captures vergleichbar gehalten werden.
Wähle Journeys aus dem Plan der Abdeckungskarte, nicht aus Gewohnheit. Bevorzuge den Dev-Server, wenn du ihn ausführen kannst: Seine Captures benennen jedes geladene Modul, sodass die Karte genau sagen kann, welche Lazy Chunks und geschützten Module ausgeführt wurden. Führe dann die Karte erneut mit den Captures aus (Schritt 2) und erfasse entweder die Lücken oder liste sie unter Grenzen auf.
Ein Capture zeigt, was passiert ist, niemals warum. Hedging in einem Capture-Only-Audit jede kausale Klausel, wo sie steht („konsistent mit zwei Query-Keys", „wahrscheinlich ein erneutes Einhängen"), nicht einmal in einem Haftungsausschluss oben: Leser zitieren einzelne Sätze, und ein ungedämpftes „weil" reist ohne seinen Haftungsausschluss.
Wenn du nur einen Capture hast, den jemand anderes gemacht hat, kannst du nicht sehen, was ihm allein aus dem Capture heraus entgangen ist. Sage dies, liste auf, was eine einzelne Sitzung normalerweise übersieht (nicht besuchte Routen, nicht geöffnete Dialoge und Tabs, Flag- und Rollenvarianten, Fehler- und Leerzustände, Verhalten im Leerlauf und im versteckten Tab) und empfehle die Captures, die die Lücke schließen würden.
python3 <skill-dir>/scripts/har_analyze.py capture.har --format md --out capture.md
Es meldet API-Anfragen pro Seite, Bursts pro Zeile auf einer Pfadvorlage, Polling mit der Anzahl gestapelter Timer pro Schlüssel, Duplikate innerhalb eines Fensters (mit Body-Hash für POSTs), serielle Ketten, wiederholte Fehler und die größten Antworten. Statische Assets sind ausgeschlossen; verwende --ignore für Telemetrie-Hosts.
Zwei Dinge, die du beim Lesen auseinanderhalten solltest:
- Ein POST auf dieselbe URL zweimal ist nur dann ein Duplikat, wenn der Body-Hash übereinstimmt. Wenn Bodies nicht erfasst wurden, sagt das Tool „unverifiziert"; melde es so.
- Eine serielle Kette ist nur dann ein Wasserfall, wenn die spätere Anfrage nichts von der früheren benötigte. Das Tool findet die Form; du bestätigst die Abhängigkeit im Code.
7. Abgleichen
Ordne jeden statischen Fund einem Capture-Beweis zu, wo ein Capture diesen Bildschirm abdeckt. Wenn sie sich widersprechen, gewinnt der Capture für die Anzahlen und der Code für die Ursache. Wenn ein Capture eine Verstärkung zeigt, die kein Kandidat erklärt, lies den Code, bis er es tut; das ist normalerweise ein Problem mit Schlüsselkanonizität oder abhängigen Schlüsseln aus Schritt 4.
Wenn das Repository ein früheres Audit oder Leistungsdokument enthält, überprüfe jede seiner Behauptungen, die deine Funde betreffen, am Code dieses Commits und liste die Widersprüche im Bericht auf. Veraltete Behauptungen sind der Grund, warum derselbe Fehler zweimal „behoben" wird.
8. Einstufen und schreiben
Verwende die Rubrik:
| Schweregrad | Wachstumsklasse | Typische Formen |
|---|---|---|
| P0 | Wächst ohne Obergrenze: mit Zeilen, mit eingehängten Beobachtern oder mit der Zeit | Pro-Zeilen-Abfrage auf einer paginierten Liste, Pro-Instanz-Poll-Timer in einem gemeinsamen Hook (SWR, Apollo, handgemacht), Invalidierung bei jedem Tastendruck, nicht gelöschtes Intervall |
| P1 | Fester Multiplikator auf einem heißen Pfad | staleTime 0 auf einer Liste, die jeder Bildschirm liest, Neuabruf bei jedem Fokus, effektgesteuerter Abruf ohne Cache, nicht verwendete erneute Abrufe aus Totem |