Wie führt eine Variable den Zustand ein?


11

Ich habe die "C ++ Coding Standards" gelesen und diese Zeile war da:

Variablen führen den Zustand ein, und Sie sollten mit so wenig Zustand wie möglich und mit möglichst kurzen Lebensdauern umgehen müssen.

Manipuliert nicht irgendetwas, das mutiert, irgendwann den Zustand? Was soll man mit möglichst wenig Staat zu tun haben ?

Ist State Management in einer unreinen Sprache wie C ++ nicht wirklich das, was Sie tun? Und was sind andere Möglichkeiten, um mit so wenig Zustand wie möglich umzugehen, als die variable Lebensdauer zu begrenzen?

Antworten:


16

Manipuliert nichts Veränderliches den Zustand wirklich?

Ja.

Und was bedeutet das "Sie sollten mit wenig Staat umgehen müssen"?

Es bedeutet, dass weniger Staat besser ist als mehr Staat. Mehr Staat führt tendenziell zu mehr Komplexität.

Ist State Management in einer unreinen Sprache wie C ++ nicht wirklich das, was Sie tun?

Ja.

Was sind andere Möglichkeiten, um mit "kleinen Zuständen umzugehen", als die variable Lebensdauer zu begrenzen?

Minimieren Sie die Anzahl der Variablen. Isolieren Sie Code, der einen bestimmten Status manipuliert, in eine eigenständige Einheit, damit andere Codeabschnitte ihn ignorieren können.


9

Manipuliert nichts Veränderliches den Zustand wirklich?

Ja. In C ++ sind die einzigen veränderlichen Dinge (Nicht- const) Variablen.

Und was bedeutet das "Sie sollten mit wenig Staat umgehen müssen"?

Je weniger Status ein Programm hat, desto leichter ist es zu verstehen, was es tut. Sie sollten also keinen Status einführen, der nicht benötigt wird, und Sie sollten ihn nicht behalten, wenn Sie ihn nicht mehr benötigen.

Ist State Management in einer unreinen Sprache wie C ++ nicht wirklich das, was Sie tun?

In einer Multi-Paradigmen-Sprache wie C ++ gibt es oft die Wahl zwischen einem "reinen" funktionalen oder einem zustandsgesteuerten Ansatz oder einer Art Hybrid. In der Vergangenheit war die Sprachunterstützung für die funktionale Programmierung im Vergleich zu einigen Sprachen relativ schwach, verbessert sich jedoch.

Was sind andere Möglichkeiten, um mit "kleinen Zuständen umzugehen", als die variable Lebensdauer zu begrenzen?

Beschränken Sie den Umfang sowie die Lebensdauer, um die Kopplung zwischen Objekten zu verringern. bevorzugen eher lokale als globale Variablen und eher private als öffentliche Objektmitglieder.


5

state bedeutet, dass etwas irgendwo gespeichert wird, damit Sie später darauf zurückgreifen können.

Durch das Erstellen einer Variablen wird Platz zum Speichern einiger Daten geschaffen. Diese Daten sind der Status Ihres Programms.

Sie verwenden es, um Dinge zu tun, zu ändern, damit zu rechnen usw.

Dies ist Zustand , während die Dinge, die Sie tun, nicht Zustand sind.

In einer funktionalen Sprache beschäftigen Sie sich meist nur mit Funktionen und geben Funktionen weiter, als wären sie Objekte. Obwohl diese Funktionen keinen Status haben und die Funktion weitergeben, wird kein Status eingeführt (außer möglicherweise innerhalb der Funktion selbst).

In C ++ können Sie Funktionsobjekte erstellen , die überladen sind structoder classsind operator()(). Diese Funktionsobjekte können einen lokalen Status haben, der jedoch nicht unbedingt von anderen Codes in Ihrem Programm gemeinsam genutzt wird. Funktoren (dh Funktionsobjekte) sind sehr einfach weiterzugeben. Dies ist ungefähr so ​​nah, wie Sie ein funktionales Paradigma in C ++ imitieren können. (SO VIEL ICH WEISS)

Wenn Sie nur einen geringen oder keinen Status haben, können Sie Ihr Programm problemlos für die parallele Ausführung optimieren, da nichts zwischen Threads oder CPUs geteilt werden kann, sodass keine Konflikte entstehen können und Sie sich nicht vor Datenrassen usw. schützen müssen.


2

Andere haben die ersten drei Fragen gut beantwortet.

Und was sind andere Möglichkeiten, um "mit so wenig Zustand wie möglich umzugehen", als die variable Lebensdauer zu begrenzen?

Die Schlüsselantwort auf Frage 1 lautet: Ja, alles, was mutiert, wirkt sich letztendlich auf den Zustand aus. Der Schlüssel ist dann, Dinge nicht zu mutieren. Unveränderliche Typen, die einen funktionalen Programmierstil verwenden, bei dem das Ergebnis einer Funktion direkt an die andere übergeben und nicht gespeichert wird, Nachrichten oder Ereignisse direkt übergeben, anstatt den Status zu speichern, Werte zu berechnen, anstatt sie zu speichern und zu aktualisieren ...

Andernfalls bleibt es Ihnen überlassen, die Auswirkungen des Staates zu begrenzen. entweder über Sichtbarkeit oder Lebensdauer.


1

Und was bedeutet das "Sie sollten mit wenig Staat umgehen müssen"?

Dies bedeutet, dass Ihre Klassen so klein wie möglich sein sollten und eine einzelne Abstraktion optimal darstellen sollten. Wenn Sie 10 Variablen in Ihre Klasse einfügen, machen Sie höchstwahrscheinlich etwas falsch und sollten sehen, wie Sie Ihre Klasse umgestalten können.


1

Um zu verstehen, wie ein Programm funktioniert, müssen Sie seine Statusänderungen verstehen. Je weniger Status Sie haben und je lokaler der Code ist, der ihn verwendet, desto einfacher wird dies.

Wenn Sie jemals mit einem Programm gearbeitet haben, das eine große Anzahl globaler Variablen enthält, würden Sie dies implizit verstehen.


1

Zustand ist einfach gespeicherte Daten. Jede Variable ist wirklich eine Art Status, aber wir verwenden normalerweise "Status", um auf Daten zu verweisen, die zwischen Operationen persistent sind. Als einfaches, sinnloses Beispiel können Sie eine Klasse haben, die intern eine intund hat increment()und decrement()Mitgliedsfunktionen speichert . Hier ist der interne Wert state, da er für das Leben der Instanz dieser Klasse bestehen bleibt. Mit anderen Worten, der Wert ist der Zustand des Objekts.

Idealerweise sollte der von einer Klasse definierte Status so klein wie möglich sein und eine minimale Redundanz aufweisen. Dies hilft Ihrer Klasse, das Prinzip der Einzelverantwortung zu erfüllen , verbessert die Kapselung und reduziert die Komplexität. Der Status eines Objekts sollte vollständig von der Schnittstelle zu diesem Objekt gekapselt werden. Dies bedeutet, dass das Ergebnis einer Operation an diesem Objekt angesichts der Semantik des Objekts vorhersehbar ist. Sie können die Kapselung weiter verbessern, indem Sie die Anzahl der Funktionen minimieren, die Zugriff auf den Status haben .

Dies ist einer der Hauptgründe für die Vermeidung des globalen Staates. Der globale Status kann eine Abhängigkeit für ein Objekt einführen, ohne dass die Schnittstelle dies ausdrückt, wodurch dieser Status für den Benutzer des Objekts verborgen bleibt. Das Aufrufen einer Operation für ein Objekt mit einer globalen Abhängigkeit kann zu unterschiedlichen und unvorhersehbaren Ergebnissen führen.


1

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).

Geben Sie hier die Bildbeschreibung ein

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.

Geben Sie hier die Bildbeschreibung ein

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.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.