Sollten Eigenschaften Nebenwirkungen haben

19

Sollten Eigenschaften in C # neben der Benachrichtigung über eine Änderung des Status auch Nebenwirkungen haben?

Ich habe Eigenschaften gesehen, die auf verschiedene Arten verwendet wurden. Von Eigenschaften, die den Wert beim ersten Zugriff auf sie laden, zu Eigenschaften, die massive Nebenwirkungen haben, z. B. das Verursachen einer Weiterleitung auf eine andere Seite.

Erin
quelle
1
Warum sollte ich unter den Ergebnissen von @ Job die Verwendung von Eigenschaften in C # vermeiden? Die Meinung von aka Jeffrey Richter ist für diese Frage sehr relevant.
Rwong
@rwong Relevant ja, aber völlig sinnlos - muss trotzdem gelesen werden (um sich der von ihm hervorgehobenen Probleme bewusst zu sein). Zumindest in dieser Angelegenheit bin ich im Skeet-Lager.
Apoorv Khurasia
Dies betrifft zwar Konstruktoren, ist aber auch relevant: programmers.stackexchange.com/a/164255/1996
Jim G.

Antworten:

40

Ich nehme an, Sie sprechen von Nur - Lese- Eigenschaften oder zumindest Eigenschaft Getter , da eine Eigenschaft Setter ist, in fast allen Fällen gehen Nebenwirkungen haben. Ansonsten ist es nicht sehr nützlich.

Im Allgemeinen folgt ein gutes Design dem Prinzip der geringsten Überraschung . Tun Sie keine Dinge, von denen Anrufer nicht erwarten, dass Sie sie tun, insbesondere wenn diese Dinge die zukünftigen Ergebnisse verändern könnten .

Im Allgemeinen bedeutet dies, dass Property Getter keine Nebenwirkungen haben sollten.

Seien wir jedoch vorsichtig mit dem, was wir unter "Nebeneffekt" verstehen .

Ein Nebeneffekt ist technisch jede Zustandsänderung. Das könnte ein öffentlich zugänglicher Staat sein, oder ... es könnte ein völlig privater Staat sein.

Lazy / Deferred Loader sind ein Beispiel für einen fast ausschließlich privaten Staat. Solange es nicht die Verantwortung des Anrufers ist, diese Ressource freizugeben, reduzieren Sie tatsächlich die Überraschung und die Komplexität im Allgemeinen, indem Sie eine verzögerte Initialisierung verwenden. Ein Aufrufer erwartet normalerweise nicht, explizit die Initialisierung einer internen Struktur signalisieren zu müssen . Eine verzögerte Initialisierung verstößt also nicht gegen das obige Prinzip.

Ein weiteres Beispiel ist eine synchronisierte Eigenschaft. Damit eine Methode threadsicher ist, muss sie häufig durch einen kritischen Abschnitt oder Mutex geschützt werden. Die Eingabe eines kritischen Abschnitts oder der Erwerb eines Mutex ist ein Nebeneffekt. Sie ändern den Status, normalerweise den globalen Status. Dieser Nebeneffekt ist jedoch erforderlich , um eine viel schlimmere Art von Überraschung zu verhindern - die Überraschung, dass Daten von einem anderen Thread geändert (oder teilweise geändert) werden.

Daher würde ich die Einschränkung ein wenig auf Folgendes lockern: Eigenschaftslesevorgänge sollten keine sichtbaren Nebenwirkungen oder Nebenwirkungen haben, die ihre Semantik ändern .

Wenn es keine Möglichkeit gibt, dass ein Anrufer jemals von einer Nebenwirkung betroffen ist oder diese überhaupt wahrnimmt , ist diese Nebenwirkung nicht schädlich. Wenn es für Sie unmöglich ist, einen Test zu schreiben, um das Vorhandensein einer bestimmten Nebenwirkung zu überprüfen, ist dieser so lokalisiert, dass er als privates Implementierungsdetail und somit für die Außenwelt nicht als legitim anzusehen ist.

Aber sei vorsichtig; als Faustregel gilt : Sie sollten versuchen , Nebenwirkungen zu vermeiden, weil oft das, was Sie vielleicht denken, unerwartet auslaufen und öffentlich zu einer privaten Detail - Implementierung zu sein.

Aaronaught
quelle
2
+1 - eine schöne vollständige Antwort einschließlich der komplizierteren Fälle, die ein "soll" in ein "sollte ... außer wenn ..."
verwandeln
Ein weiteres Beispiel für eine akzeptable Nebenwirkung auf einen Getter: Eine Protokollierungsfunktion.
Loren Pechtel
5

Ich beantworte Ihre Frage mit einer Frage: Was passiert, wenn Sie die Width-Eigenschaft eines Formulars ändern?

Direkt von meinem Kopf aus wird dies:

  • Ändern Sie den Wert des Hintergrundfelds.
  • Ein Ereignis auslösen.
  • Ändern Sie die Dimensionen des Formulars, melden Sie die Änderung an den Fenstermanager und fordern Sie ein Neulackieren an.
  • Benachrichtigen Sie die Steuerelemente im Formular über die Änderung, damit alle Steuerelemente, deren Größe oder Position geändert werden muss, wenn die Größe des Formulars geändert wird, dies tun können.
  • Verursachen Sie, dass alle Steuerelemente, die geändert wurden, Ereignisse auslösen, und teilen Sie dem Fenstermanager mit, dass sie neu gezeichnet werden müssen.
  • Wahrscheinlich auch andere Sachen.

(Haftungsausschluss: Ich bin ein Delphi-Entwickler und kein .NET-Entwickler. Für C # ist dies möglicherweise nicht 100% genau, aber ich wette, es ist ziemlich nah, besonders wenn man bedenkt, wie viel des ursprünglichen .NET-Frameworks auf Delphi basierte.)

Alle diese "Nebenwirkungen" treten auf, wenn Sie die Width-Eigenschaft in einem Formular ändern. Scheint einer von ihnen unangemessen oder irgendwie falsch?

Mason Wheeler
quelle
solange es nicht in eine Endlosschleife gerät, die sich selbst aufruft. Um dies zu vermeiden, wird jede "Änderung" mindestens drei Ereignissen zugeordnet: einem, der vor der Änderung ausgelöst wurde, einem, der während der Änderung ausgelöst wurde, und einem, der nach deren Abschluss ausgelöst wurde.
Rwong
5

Schauen Sie sich die Microsoft Design Rules an , insbesondere eine davon:

CA1024: Verwenden Sie gegebenenfalls Eigenschaften

... Eine Methode ist ein guter Kandidat, um eine Eigenschaft zu werden, wenn eine der folgenden Bedingungen vorliegt:

  • Nimmt keine Argumente und gibt die Statusinformationen eines Objekts zurück.
  • Akzeptiert ein einzelnes Argument, um einen Teil des Status eines Objekts festzulegen.

Eigenschaften sollten sich so verhalten, als wären sie Felder. Wenn die Methode dies nicht kann, sollte sie nicht in eine Eigenschaft geändert werden. In den folgenden Situationen sind Methoden besser als Eigenschaften:

  • Das Verfahren führt eine zeitaufwendige Operation durch. Die Methode ist merklich langsamer als die Zeit, die erforderlich ist, um den Wert eines Felds festzulegen oder abzurufen.
  • Die Methode führt eine Konvertierung durch. Beim Zugriff auf ein Feld wird keine konvertierte Version der darin gespeicherten Daten zurückgegeben.
  • Die Get-Methode hat einen beobachtbaren Nebeneffekt. Das Abrufen des Feldwerts hat keine Nebenwirkungen.
  • Die Reihenfolge der Ausführung ist wichtig. Das Festlegen des Werts eines Felds hängt nicht vom Auftreten anderer Vorgänge ab.
  • Wenn Sie die Methode zweimal hintereinander aufrufen, werden unterschiedliche Ergebnisse erzielt.
  • Die Methode ist statisch, gibt jedoch ein Objekt zurück, das vom Aufrufer geändert werden kann. Durch das Abrufen des Werts eines Felds kann der Anrufer die im Feld gespeicherten Daten nicht ändern.
  • Die Methode gibt ein Array zurück ...
Guru Meditation
quelle
2

Ich glaube, Eigenschaften sollten keine Nebenwirkungen haben. Es gibt jedoch eine Ausnahme von dieser Regel: verzögertes Laden. Wenn Sie in einer Eigenschaft etwas faul laden möchten, ist das in Ordnung, da der Client nicht sagen kann, dass das faulen Laden stattfindet, und es im Allgemeinen egal ist.

EDIT : Oh, noch eine Sache - Auslösen eines Ereignisses mit der Aufschrift "PropertyXYZ Changed!" (zB INotifyPropertyChanged) - ist gut für Setter.

Billy ONeal
quelle
2

Zusätzlich zu dem zuvor erwähnten verzögerten Laden gibt es auch den Fall von Protokollierungsfunktionen. Dies wäre selten, aber nicht ausgeschlossen.

Loren Pechtel
quelle
2

Es gibt kaum Absolutes in der Programmierung. Um Ihre Frage zu beantworten, müssen wir einige wünschenswerte Eigenschaften von Objekten untersuchen und prüfen, ob dies für unseren Fall zutreffend ist

  1. Wir möchten, dass der Unterricht einfach zu testen ist.
  2. Wir möchten, dass Klassen Parallelität unterstützen
  3. Wir möchten, dass der Unterricht für Dritte leicht verständlich ist

Wenn wir einige dieser Fragen mit Ja beantworten, ist es wahrscheinlich eine gute Idee, über Eigenschaften und deren Nebenwirkungen sehr sorgfältig nachzudenken. Ich gehe davon aus, dass Sie mit Nebenwirkungen sekundäre Nebenwirkungen meinen, da die Aktualisierung des Werts eine Nebenwirkung für sich ist.

Je mehr Nebenwirkungen im Allgemeinen auftreten, desto schwieriger wird es sein, eine Klasse zu testen. Je mehr sekundäre Nebeneffekte auftreten, desto schwieriger wird es, die Parallelität zu bewältigen, aber das ist ein schwieriges Problem, das wahrscheinlich ohnehin einige grundlegende Konstruktionsüberlegungen erfordert.

Das große Problem ist wahrscheinlich für Leute, die Ihre Klasse als "Black Box" behandeln. Es wäre nicht ohne weiteres möglich, dass sich ein Zustand geändert hat, nur weil sie eine Eigenschaft gelesen haben, oder dass sich eine andere Eigenschaft ändert, weil sich eine andere ändert (was dazu führen könnte) zu kaskadierenden Updates).

Nein, im Allgemeinen möchten wir, dass Eigenschaften so einfach wie möglich und verständlich sind. Dies ist in der Entwurfsentscheidung impliziert, andernfalls handelt es sich um eine Methode. Ein guter Programmierer kann immer gegen die Regeln verstoßen, aber sei dir sicher, dass du einen wirklich guten Grund hast :)

Homde
quelle