01 · Problem
Wofür braucht man das?
Direktes Hinzufügen oder Entfernen während forEach kann Elemente überspringen, doppelt besuchen oder die Schleifenlogik schwer vorhersagbar machen.
02 · Denkmodell
Das mentale Modell
Die Iteration sieht einen Snapshot. Mutation landet in Queues und wird nur an klaren Flush-Grenzen atomar übernommen.
Verarbeitet gepufferte Mutationen ohne wiederholte Adds oder geteilte Konstruktor-Arrays. Entfernen erfolgt wertbasiert für alle Treffer. Nebenläufige Threads und Isolation verschachtelter Iterationen werden nicht zugesagt.
03 · Kontrollfluss
Was passiert in welcher Reihenfolge?
- Add und Remove in getrennten Pending-Strukturen sammeln.
- Vor Iteration einen Flush durchführen und beide Queues leeren.
- Über einen stabilen Snapshot iterieren.
- Währenddessen gesammelte Änderungen erst nach der Schleife übernehmen.
04 · Bauteile
Die entscheidenden Verträge
SafeArray.add / remove / forEach / filterPuffert Änderungen und iteriert mit definierten Mutationsgrenzen.length / isEmptyLiest effektive Inhalte einschließlich gepufferter Änderungen.05 · Selbst bauen
Selbst implementieren
Definiere zuerst, ob Änderungen aus Callback N noch im selben oder erst im nächsten Durchlauf sichtbar werden.
- Konstruktorinput kopieren und Pending-Adds sowie Remove-Set anlegen.
- Flush übernimmt jede Queue genau einmal und leert sie sofort.
- Iteration arbeitet auf einer Kopie oder einem unveränderten Basispuffer.
class DeferredList<T> {
private values: T[] = [];
private adds: T[] = [];
private removes = new Set<T>();
add(value: T) { this.adds.push(value); }
remove(value: T) { this.removes.add(value); }
flush() {
this.values = this.values.filter(value => !this.removes.has(value));
this.values.push(...this.adds);
this.removes.clear();
this.adds = [];
}
}06 · Verifizieren
Was solltest du testen?
- Ein Add erscheint nach beliebig vielen Flushes genau einmal.
- Remove während Iteration folgt der dokumentierten Sichtbarkeitsregel.
- Add und Remove desselben Objekts in einem Zyklus haben eine festgelegte Reihenfolge.
Selbst ausprobieren
Formuliere die erste Prüfaussage als fehlschlagenden Test, implementiere die kleinste Korrektur und ergänze einen Grenzfall.
Ein Add erscheint nach beliebig vielen Flushes genau einmal.
Ansatz vergleichen
Dein Test sollte beim gezielten Verletzen der Invariante fehlschlagen und nach der Korrektur wieder bestehen. Halte den Aufbau deterministisch und prüfe ein beobachtbares Ergebnis.
07 · Grenzen
Kompromisse und Stolperfallen
- Snapshots sind klar und erzeugen O(n)-Kopien.
- Referenzgleichheit genügt für Objekte nur bei stabiler Identität.
- Deferred Mutation schützt Iterationen, verzögert aber sichtbare Zustandsänderungen.
Entfernen erfolgt wertbasiert für alle Treffer. Nebenläufige Threads und Isolation verschachtelter Iterationen werden nicht zugesagt.
08 · Weiterdenken
Quellcode und verwandte Ideen
yarn tsx packages/safe-array/examples/basic.ts