Mutationen verhindern
Was bei DC und Marvel ziemlich cool ist, kann im Entwickler-Alltag leicht zu Frust und Zeitverschwendung führen: Mutability von Objekten.
Was haben denn bitte Comics mit Programmieren zu tun?! Ganz einfach, Quantenphysik.
⚛️ Objekte und und ihre Pointer sind ein bisschen wie verschränkte Teilchen. Ändert man ein Objekt an der einen Stelle im Code, ändert es sich auch an der anderen. Damit kennen wir dessen Zustand nicht mehr, ohne genau hinzuschauen - wir haben also erfolgreich Schrödingers Katze implementiert 🙀 Sprich das Gesetz der Dekohärenz ist umgangen und das Objekt befindet sich in Superposition. Will keiner, denn die nächste Messung wird erst stattfinden, wenn uns die nächste Fehlermeldung um die Ohren fliegt.
🤨 What...
In Redux ist es verboten und in NgRx schlich nicht möglich Objekte im Store zu verändern.
Manchmal ist es aber ziemlich übertrieben ein State Management einzuführen. Objekte, trotz fehlenden Zwangs, als immutable zu behandeln kann aber trotzdem eine ziemlich gute Idee sein.
Also getreu dem Reactive Programming Paradigma: "Create new, don't alter"
// Irgendein Objekt wird über den Setter in die Klasse reingegeben und als Property gespeichert
setLocalState(someState): Observable<any> {
someState.someProperty = someState.someProperty || [];
this.localState = someState;
return Observable.emtpy();
}
⚠️ Zonk! Problem: Das verschränkte Objekt (ja ok, es ist dasselbe!) hält sich auch noch außerhalb der Methode auf. Wird es nach dem Aufruf von setLocalState()
verändert, ändert sich auch die Property localState
in unserer Klasse
Also besser so:
setLocalState(someState): Observable<any> {
this.localState = Object.assign(
{}, // neues, leeres Objekt
someState, // wird mit übergebenem Objekt und wahlweise weiteren gemergt
{ someProperty: someState.someProperty || [] }
);
}
☝️🧐 So haben wir schnell ein neues Objekt erzeugt und unschönen Seiteneffekten vorgebeugt.
Ich weiß, die Beispiele sind komplett an den Haaren herbeigezogen. Hat aber halt Spaß gemacht, den Artikel so zu schreiben... 🤷♂️
Instead of mutating existing objects in memory, rather creating new objects.
Verhindert bestimmte Arten von Bugs, wie zB, dass Werte unerwartet von anderen Codestellen geändert werden.
Hilft auch bei bestimmten Arten von ChangeDetection
// nicht so gut
this.currentUser.classes.push(classesId);
// ändert classes-Array durch Hinzufügen eines Objekts
// ändert currentUser-Objekt durch Ändern der Property classes
// besser
this.currentUser = Object.assign({}, this.currentUser,
{classes: this.currentUser.classes.concat([classID])}
)
saveUser(user): Observable<any> {
user.classes = user.classes || [];
this.currentUser = user;
// this.currentUser zeigt auf den user, der von der aufrufenden Methode kommt
// ändert letztere ihr user-Objekt, wird auch this.currentUser geändert!
return Observable.empty().delay(1000);
}
// Bug schwer zu finden, wenn man zB ChangeDetection.onPush() verwendet
// besser
saveUser(user): Observable<any> {
this.currentUser = Object.assign({}, user, {classes: user.classes || []});
// erzeugt ein neues Objekt und kopiert alle Properties vom übergebenen user
return Observable.empty().delay(1000);
}