Manipuliert nicht irgendetwas, das mutiert, irgendwann den Zustand?
Ja, aber wenn es sich um eine Mitgliedsfunktion einer kleinen Klasse handelt, die die einzige Entität im gesamten System ist, die ihren privaten Status manipulieren kann, hat dieser Status einen sehr engen Bereich.
Was soll man mit möglichst wenig Staat zu tun haben?
Vom Standpunkt der Variablen aus sollten möglichst wenige Codezeilen darauf zugreifen können. Grenzen Sie den Umfang der Variablen auf ein Minimum ein.
Vom Standpunkt der Codezeile aus sollten möglichst wenige Variablen von dieser Codezeile aus zugänglich sein. Grenzen Sie die Anzahl der Variablen ein, auf die die Codezeile möglicherweise zugreifen kann (es ist nicht einmal so wichtig, ob sie darauf zugreift , alles, was zählt, ist, ob dies möglich ist ).
Globale Variablen sind so schlecht, weil sie maximalen Umfang haben. Selbst wenn über zwei Codezeilen in einer Codebasis auf sie zugegriffen wird, kann über die POV der Codezeile immer auf eine globale Variable zugegriffen werden. Über die POV der Variablen kann auf jede globale Codezeile in der gesamten Codebasis (oder auf jede einzelne Codezeile, die den Header enthält) auf eine globale Variable mit externer Verknüpfung zugegriffen werden. Obwohl die globale Variable nur für 2 Codezeilen zugänglich ist und 400.000 Codezeilen sichtbar sind, enthält Ihre unmittelbare Liste der Verdächtigen, wenn Sie feststellen, dass sie in einen ungültigen Zustand versetzt wurde, 400.000 Einträge (möglicherweise schnell auf reduziert) 2 Einträge mit Tools, aber dennoch wird die unmittelbare Liste 400.000 Verdächtige haben und das ist kein ermutigender Ausgangspunkt.
Ebenso besteht die Möglichkeit, dass selbst wenn eine globale Variable anfängt, nur durch zwei Codezeilen in der gesamten Codebasis geändert zu werden, die unglückliche Tendenz der Codebasen, sich rückwärts zu entwickeln, dazu führt, dass diese Zahl drastisch zunimmt, einfach weil sie so viele erhöhen kann Entwickler, die sich verzweifelt darum bemühen, Termine einzuhalten, sehen diese globale Variable und erkennen, dass sie Verknüpfungen durch sie ziehen können.
Ist State Management in einer unreinen Sprache wie C ++ nicht wirklich das, was Sie tun?
Im Großen und Ganzen ja, es sei denn, Sie verwenden C ++ auf eine sehr exotische Art und Weise, bei der Sie sich mit maßgeschneiderten unveränderlichen Datenstrukturen und rein funktionaler Programmierung beschäftigen. Dies ist auch häufig die Ursache für die meisten Fehler, wenn die Statusverwaltung komplex wird und die Komplexität zunimmt oft eine Funktion der Sichtbarkeit / Belichtung dieses Zustands.
Und was sind andere Möglichkeiten, um mit so wenig Zustand wie möglich umzugehen, als die variable Lebensdauer zu begrenzen?
All dies dient dazu, den Umfang einer Variablen einzuschränken, aber es gibt viele Möglichkeiten, dies zu tun:
- Vermeiden Sie globale Rohvariablen wie die Pest. Selbst eine dumme globale Setter / Getter-Funktion schränkt die Sichtbarkeit dieser Variablen drastisch ein und ermöglicht zumindest eine Möglichkeit, Invarianten beizubehalten (z. B. wenn die globale Variable niemals einen negativen Wert haben darf, kann der Setter diese Invariante beibehalten). Natürlich ist selbst ein Setter / Getter-Design, das über eine ansonsten globale Variable hinausgeht, ein ziemlich schlechtes Design. Mein Punkt ist nur, dass es immer noch viel besser ist.
- Machen Sie Ihre Klassen wenn möglich kleiner. Eine Klasse mit Hunderten von Elementfunktionen, 20 Elementvariablen und 30.000 Codezeilen, die sie implementieren, hätte eher "globale" private Variablen, da alle diese Variablen für ihre Elementfunktionen zugänglich wären, die aus 30.000 Codezeilen bestehen. Man könnte sagen, dass die "Zustandskomplexität" in diesem Fall unter Abzinsung lokaler Variablen in jeder Mitgliedsfunktion ist
30,000*20=600,000
. Wenn darüber hinaus 10 globale Variablen verfügbar wären, könnte die Komplexität des Zustands ähnlich sein 30,000*(20+10)=900,000
. Eine gesunde "Zustandskomplexität" (meine persönliche Art der erfundenen Metrik) sollte für Klassen Tausende oder weniger betragen, nicht Zehntausende und definitiv nicht Hunderttausende. Für kostenlose Funktionen sagen wir Hunderte oder weniger, bevor wir ernsthafte Kopfschmerzen bei der Wartung bekommen.
- Implementieren Sie auf die gleiche Weise wie oben nichts als Member- oder Friend-Funktion, das ansonsten kein Mitglied sein kann, sondern nur über die öffentliche Schnittstelle der Klasse. Solche Funktionen können nicht auf die privaten Variablen der Klasse zugreifen und verringern somit das Fehlerpotential, indem sie den Umfang dieser privaten Variablen verringern.
- Vermeiden Sie das Deklarieren von Variablen, lange bevor sie tatsächlich in einer Funktion benötigt werden (dh vermeiden Sie den alten C-Stil, der alle Variablen am oberen Rand einer Funktion deklariert, auch wenn sie nur viele Zeilen darunter benötigt werden). Wenn Sie diesen Stil trotzdem verwenden, streben Sie zumindest nach kürzeren Funktionen.
Jenseits von Variablen: Nebenwirkungen
Viele dieser Richtlinien, die ich oben aufgeführt habe, befassen sich mit dem direkten Zugriff auf den unveränderlichen Rohzustand (Variablen). In einer ausreichend komplexen Codebasis reicht es jedoch nicht aus, nur den Umfang der Rohvariablen einzugrenzen, um leicht über die Richtigkeit nachzudenken.
Sie könnten beispielsweise eine zentrale Datenstruktur hinter einer völlig FESTEN, abstrakten Schnittstelle haben, die in der Lage ist, Invarianten perfekt zu verwalten, und aufgrund der weit verbreiteten Exposition dieses zentralen Zustands immer noch in große Trauer geraten. Ein Beispiel für einen zentralen Zustand, der nicht unbedingt global zugänglich, sondern nur allgemein zugänglich ist, ist das zentrale Szenendiagramm einer Spiel-Engine oder die Datenstruktur der zentralen Ebene von Photoshop.
In solchen Fällen geht die Idee des "Zustands" über Rohvariablen hinaus und nur auf Datenstrukturen und solche Dinge. Es hilft ebenfalls, ihren Umfang zu reduzieren (reduzieren Sie die Anzahl der Zeilen, die Funktionen aufrufen können, die sie indirekt mutieren).
Beachten Sie, wie ich hier sogar die Benutzeroberfläche absichtlich als rot markiert habe, da der Zugriff auf diese Benutzeroberfläche von der breiten, verkleinerten Architekturebene aus immer noch mutiert, wenn auch indirekt. Die Klasse kann als Ergebnis der Schnittstelle Invarianten beibehalten, aber das geht nur so weit, dass wir über Korrektheit nachdenken können.
In diesem Fall befindet sich die zentrale Datenstruktur hinter einer abstrakten Schnittstelle, auf die möglicherweise nicht einmal global zugegriffen werden kann. Es kann lediglich injiziert und dann indirekt (durch Mitgliedsfunktionen) aus einer Schiffsladung von Funktionen in Ihrer komplexen Codebasis mutiert werden.
In einem solchen Fall können, selbst wenn die Datenstruktur ihre eigenen Invarianten perfekt beibehält, seltsame Dinge auf einer breiteren Ebene passieren (z. B.: Ein Audio-Player kann alle Arten von Invarianten beibehalten, so dass der Lautstärkepegel niemals außerhalb des Bereichs von 0% bis liegt 100%, aber das schützt es nicht davor, dass der Benutzer die Wiedergabetaste drückt und einen anderen zufälligen Audioclip als den zuletzt geladenen als Ereignis startet, wodurch die Wiedergabeliste auf eine gültige Weise neu gemischt wird immer noch unerwünschtes, fehlerhaftes Verhalten aus der breiten Benutzerperspektive).
Der Weg, sich in diesen komplexen Szenarien zu schützen, besteht darin, die Stellen in der Codebasis zu "Engpässen", die Funktionen aufrufen können, die letztendlich externe Nebenwirkungen verursachen, selbst aus dieser Art einer breiteren Sicht auf das System, die über den Rohzustand und über Schnittstellen hinausgeht.
So seltsam dies auch aussieht, Sie können sehen, dass auf keinen "Status" (rot dargestellt, und dies bedeutet nicht "Rohvariable", sondern nur ein "Objekt" und möglicherweise sogar hinter einer abstrakten Schnittstelle) von zahlreichen Stellen zugegriffen wird . Funktionen haben jeweils Zugriff auf einen lokalen Status, auf den auch ein zentraler Updater zugreifen kann, und der zentrale Status ist nur für den zentralen Updater zugänglich (wodurch er nicht mehr zentral, sondern lokaler Natur ist).
Dies gilt nur für wirklich komplexe Codebasen, wie ein Spiel, das 10 Millionen Codezeilen umfasst. Es kann jedoch enorm hilfreich sein, um über die Richtigkeit Ihrer Software nachzudenken und festzustellen, dass Ihre Änderungen vorhersehbare Ergebnisse liefern, wenn Sie die Anzahl erheblich einschränken / einschränken von Orten, die kritische Zustände mutieren können, um die sich die gesamte Architektur dreht, um korrekt zu funktionieren.
Über Rohvariablen hinaus gibt es externe Nebenwirkungen, und externe Nebenwirkungen sind eine Fehlerquelle, selbst wenn sie auf eine Handvoll Mitgliedsfunktionen beschränkt sind. Wenn eine Schiffsladung von Funktionen diese Handvoll Mitgliedsfunktionen direkt aufrufen kann, gibt es eine Schiffsladung von Funktionen im System, die indirekt externe Nebenwirkungen verursachen können und die Komplexität erhöhen. Wenn es nur einen Ort in der Codebasis gibt, der Zugriff auf diese Mitgliedsfunktionen hat, und dieser eine Ausführungspfad nicht durch sporadische Ereignisse überall ausgelöst wird, sondern auf sehr kontrollierte, vorhersehbare Weise ausgeführt wird, verringert sich die Komplexität.
Zustandskomplexität
Auch die Komplexität des Staates ist ein wichtiger Faktor, der berücksichtigt werden muss. Eine einfache Struktur, die hinter einer abstrakten Oberfläche allgemein zugänglich ist, ist nicht so schwer durcheinander zu bringen.
Eine komplexe Diagrammdatenstruktur, die die logische Kerndarstellung einer komplexen Architektur darstellt, ist ziemlich leicht durcheinander zu bringen und auf eine Weise, die nicht einmal die Invarianten des Diagramms verletzt. Ein Graph ist um ein Vielfaches komplexer als eine einfache Struktur, und daher wird es in einem solchen Fall noch wichtiger, die wahrgenommene Komplexität der Codebasis zu reduzieren, um die Anzahl der Stellen, die Zugriff auf eine solche Graphstruktur haben, auf das absolute Minimum zu reduzieren. und wo sich diese Art von "zentraler Updater" -Strategie, die sich in ein Pull-Paradigma umkehrt, um sporadische, direkte Pushs von überall auf die Grafikdatenstruktur zu vermeiden, wirklich auszahlen kann.