Best Practices: Ausnahmen von Eigenschaften auslösen

110

Wann ist es angebracht, eine Ausnahme aus einem Property Getter oder Setter heraus auszulösen? Wann ist es nicht angebracht? Warum? Links zu externen Dokumenten zu diesem Thema wären hilfreich ... Google ist überraschend wenig aufgetaucht.

Jon Seigel
quelle
Auch verwandt: stackoverflow.com/questions/1488322/…
Brian Rasmussen
1
Ich habe beide Fragen gelesen, aber beide haben diese Frage IMO nicht vollständig beantwortet.
Jon Seigel
Wann immer nötig. Sowohl die vorherigen Fragen als auch die Antworten zeigen, dass es erlaubt und wünschenswert ist, Ausnahmen von Getter oder Setter auszulösen, sodass Sie einfach "schlau sein" können.
Lex Li

Antworten:

134

Microsoft hat seine Empfehlungen zum Entwerfen von Eigenschaften unter http://msdn.microsoft.com/en-us/library/ms229006.aspx

Im Wesentlichen empfehlen sie, dass Property Getter leichte Accessoren sind, die immer sicher angerufen werden können. Sie empfehlen, Getter als Methoden neu zu gestalten, wenn Sie Ausnahmen auslösen müssen. Für Setter geben sie an, dass Ausnahmen eine angemessene und akzeptable Fehlerbehandlungsstrategie sind.

Für Indexer gibt Microsoft an, dass es sowohl für Getter als auch für Setter akzeptabel ist, Ausnahmen auszulösen. Tatsächlich tun dies viele Indexer in der .NET-Bibliothek. Die häufigste Ausnahme ist ArgumentOutOfRangeException.

Es gibt einige ziemlich gute Gründe, warum Sie keine Ausnahmen in Property Getter auslösen möchten:

  • Da Eigenschaften als Felder "erscheinen", ist es nicht immer offensichtlich, dass sie eine (beabsichtigte) Ausnahme auslösen können. Bei Methoden werden Programmierer darin geschult, zu erwarten und zu untersuchen, ob Ausnahmen eine erwartete Folge des Aufrufs der Methode sind.
  • Getter werden von vielen .NET-Infrastrukturen verwendet, wie z. B. Serializer und Datenbindung (z. B. in WinForms und WPF). Der Umgang mit Ausnahmen in solchen Kontexten kann schnell problematisch werden.
  • Property Getter werden von Debuggern automatisch ausgewertet, wenn Sie ein Objekt beobachten oder untersuchen. Eine Ausnahme hier kann verwirrend sein und Ihre Debugging-Bemühungen verlangsamen. Aus den gleichen Gründen ist es auch unerwünscht, andere teure Vorgänge in Eigenschaften durchzuführen (z. B. den Zugriff auf eine Datenbank).
  • Eigenschaften werden häufig in einer Verkettungskonvention verwendet: obj.PropA.AnotherProp.YetAnother- Bei dieser Art von Syntax wird es problematisch zu entscheiden, wo Ausnahme-Catch-Anweisungen eingefügt werden sollen.

Als Randnotiz sollte man sich bewusst sein, dass nur weil eine Eigenschaft nicht dafür ausgelegt ist , eine Ausnahme auszulösen, dies nicht bedeutet, dass dies nicht der Fall ist. Es könnte leicht sein, Code aufzurufen, der dies tut. Selbst das einfache Zuweisen eines neuen Objekts (wie einer Zeichenfolge) kann zu Ausnahmen führen. Sie sollten Ihren Code immer defensiv schreiben und Ausnahmen von allem erwarten, was Sie aufrufen.

LBushkin
quelle
41
Wenn Sie auf eine schwerwiegende Ausnahme wie "Nicht genügend Speicher" stoßen, spielt es kaum eine Rolle, ob Sie die Ausnahme in einer Eigenschaft oder an einem anderen Ort erhalten. Wenn Sie es nicht in der Eigenschaft erhalten haben, erhalten Sie es nur ein paar Nanosekunden später von der nächsten Sache, die Speicher zuweist. Die Frage ist nicht "Kann eine Eigenschaft eine Ausnahme auslösen?" Fast jeder Code kann aufgrund eines schwerwiegenden Zustands eine Ausnahme auslösen. Die Frage ist, ob eine Immobilie im Rahmen ihres spezifischen Vertrags von Natur aus eine Ausnahme auslösen soll.
Eric Lippert
1
Ich bin mir nicht sicher, ob ich das Argument in dieser Antwort verstehe. Zum Beispiel in Bezug auf die Datenbindung - sowohl WinForms als auch WPF wurden speziell geschrieben, um von Eigenschaften ausgelöste Ausnahmen ordnungsgemäß zu behandeln und sie als Validierungsfehler zu behandeln -, was eine vollkommen gute (einige glauben sogar, dass dies die beste ist) Methode ist, um die Validierung von Domänenmodellen bereitzustellen .
Pavel Minaev
6
@Pavel - Während sowohl WinForms als auch WPF Ausnahmen in Eigenschaftenzugriffern ordnungsgemäß wiederherstellen können, ist es nicht immer einfach, solche Fehler zu identifizieren und zu beheben. In bestimmten Fällen (z. B. wenn in WPF ein Control Template Setter eine Ausnahme auslöst) wird die Ausnahme stillschweigend verschluckt. Dies kann zu schmerzhaften Debugging-Sitzungen führen, wenn Sie solche Fälle noch nie zuvor gesehen haben.
LBushkin
1
@Steven: Wie viel Nutzen hat Ihnen diese Klasse dann im Ausnahmefall gebracht? Wenn Sie dann Defensivcode schreiben mussten, um alle diese Ausnahmen aufgrund eines Fehlers zu behandeln, und vermutlich geeignete Standardeinstellungen angeben, warum nicht diese Standardeinstellungen in Ihrem Fang angeben? Alternativ, wenn die Eigenschaftsausnahmen an den Benutzer ausgelöst werden, warum nicht einfach die ursprüngliche "InvalidArgumentException" oder ähnliches auslösen, damit sie die fehlende Einstellungsdatei bereitstellen können?
Zhaph - Ben Duguid
6
Es gibt einen Grund, warum dies Richtlinien und keine Regeln sind. Keine Richtlinie deckt alle verrückten Randfälle ab. Ich hätte diese Methoden und nicht Eigenschaften selbst gemacht, aber es ist ein Urteilsspruch.
Eric Lippert
34

Es ist nichts Falsches daran, Ausnahmen von Setzern zu werfen. Wie kann man besser anzeigen, dass der Wert für eine bestimmte Eigenschaft nicht gültig ist?

Für Getter ist es im Allgemeinen verpönt, und das kann ziemlich einfach erklärt werden: Ein Getter von Eigenschaften meldet im Allgemeinen den aktuellen Zustand eines Objekts; Daher ist der einzige Fall, in dem es für einen Getter sinnvoll ist, zu werfen, wenn der Zustand ungültig ist. Es wird jedoch auch allgemein als eine gute Idee angesehen, Ihre Klassen so zu gestalten, dass es einfach nicht möglich ist, ein ungültiges Objekt anfangs abzurufen oder es mit normalen Mitteln in einen ungültigen Zustand zu versetzen (dh immer eine vollständige Initialisierung in Konstruktoren sicherzustellen und Versuchen Sie, Methoden in Bezug auf Zustandsgültigkeit und Klasseninvarianten ausnahmesicher zu machen. Solange Sie sich an diese Regel halten, sollten Ihre Immobilienbeschaffer niemals in eine Situation geraten, in der sie einen ungültigen Zustand melden müssen, und daher niemals werfen.

Es gibt eine Ausnahme, die ich kenne, und es ist tatsächlich eine ziemlich große: jedes Objekt, das implementiert wird IDisposable. Disposeist speziell dazu gedacht, Objekte in einen ungültigen Zustand zu versetzen, und es gibt sogar eine spezielle Ausnahmeklasse, ObjectDisposedExceptiondie in diesem Fall verwendet werden kann. Es ist völlig normal, ObjectDisposedExceptionvon jedem Klassenmitglied, einschließlich Property Getter (und ohne sich Disposeselbst), zu werfen , nachdem das Objekt entsorgt wurde.

Pavel Minaev
quelle
4
Danke Pavel. Diese Antwort lautet "Warum", anstatt einfach noch einmal zu sagen, dass es keine gute Idee ist, eine Ausnahme von den Eigenschaften auszulösen.
SolutionYogi
1
Ich mag die Vorstellung nicht, dass absolut alle Mitglieder von a IDisposablenach a unbrauchbar gemacht werden sollten Dispose. Wenn das Aufrufen eines Mitglieds die Verwendung einer Ressource erfordern würde, die Disposenicht verfügbar geworden ist (z. B. würde das Mitglied Daten aus einem geschlossenen Stream lesen), sollte das Mitglied werfen, ObjectDisposedExceptionanstatt zu verlieren, z. B. ArgumentExceptionwenn eines ein Formular mit Eigenschaften hat, die das darstellen Werte in bestimmten Feldern, scheint es weitaus hilfreicher zu sein, solche Eigenschaften nach der Entsorgung lesen zu lassen (was die zuletzt eingegebenen Werte ergibt), als ...
supercat
1
... die Disposeverschoben werden, bis alle diese Eigenschaften gelesen wurden. In einigen Fällen, in denen ein Thread möglicherweise blockierende Lesevorgänge für ein Objekt verwendet, während ein anderer es schließt, und in denen Daten zu einem beliebigen Zeitpunkt zuvor eintreffen Dispose, kann es hilfreich sein Dispose, eingehende Daten abgeschnitten zu haben , aber zuvor gelesene Daten lesen zu lassen. Man sollte keine künstliche Unterscheidung zwischen Closeund Disposein Situationen erzwingen, in denen sonst keine existieren müsste.
Supercat
Wenn Sie den Grund für die Regel verstehen, wissen Sie, wann Sie gegen die Regel verstoßen müssen (Raymond Chen). In diesem Fall können wir sehen, dass Sie einen nicht behebbaren Fehler jeglichen Typs nicht im Getter ausblenden sollten, da die Anwendung in solchen Fällen so schnell wie möglich beendet werden muss.
Ben
Der Punkt, den ich ansprechen wollte, ist, dass Ihre Property Getter im Allgemeinen keine Logik enthalten sollten, die nicht behebbare Fehler zulässt. Wenn dies der Fall ist, ist es möglicherweise besser als Get...Methode. Eine Ausnahme ist hier, wenn Sie eine vorhandene Schnittstelle implementieren müssen, für die Sie eine Eigenschaft angeben müssen.
Pavel Minaev
24

Es ist fast nie für einen Getter geeignet und manchmal auch für einen Setter.

Die beste Quelle für diese Art von Fragen sind "Framework Design Guidelines" von Cwalina und Abrams; Es ist als gebundenes Buch erhältlich und große Teile davon sind auch online erhältlich.

Ab Abschnitt 5.2: Eigenschaftsgestaltung

Vermeiden Sie es, Ausnahmen von Immobilien-Gettern zu werfen. Property Getter sollten einfache Operationen sein und keine Voraussetzungen haben. Wenn ein Getter eine Ausnahme auslösen kann, sollte sie wahrscheinlich als Methode neu gestaltet werden. Beachten Sie, dass diese Regel nicht für Indexer gilt, bei denen wir aufgrund der Validierung der Argumente Ausnahmen erwarten.

Beachten Sie, dass diese Richtlinie nur für Getter gilt. Es ist in Ordnung, eine Ausnahme in einem Eigenschaftssetter auszulösen.

Eric Lippert
quelle
2
Obwohl ich (im Allgemeinen) solchen Richtlinien zustimme, halte ich es für nützlich, zusätzliche Einblicke zu geben, warum sie befolgt werden sollten - und welche Konsequenzen sich daraus ergeben können, wenn sie ignoriert werden.
LBushkin
3
Wie hängt dies mit Einwegobjekten und der Anleitung zusammen, die Sie werfen sollten, ObjectDisposedExceptionwenn das Objekt Dispose()aufgerufen wurde und anschließend etwas nach einem Eigenschaftswert fragt? Es scheint, als sollte die Anleitung lauten: "Vermeiden Sie das Auslösen von Ausnahmen von Eigenschaftsträgern, es sei denn, das Objekt wurde entsorgt. In diesem Fall sollten Sie in Betracht ziehen, eine ObjectDisposedExcpetion auszulösen."
Scott Dorman
4
Design ist die Kunst und Wissenschaft, angesichts widersprüchlicher Anforderungen vernünftige Kompromisse zu finden. So oder so scheint ein vernünftiger Kompromiss zu sein; Es würde mich nicht wundern, wenn ein entsorgter Gegenstand auf ein Grundstück geworfen würde. Ich wäre auch nicht überrascht, wenn es nicht so wäre. Da die Verwendung eines entsorgten Objekts eine schreckliche Programmierpraxis ist, wäre es unklug, irgendwelche Erwartungen zu haben.
Eric Lippert
1
Ein weiteres Szenario, in dem es völlig gültig ist, Ausnahmen aus Gettern heraus auszulösen, besteht darin, dass ein Objekt Klasseninvarianten verwendet, um seinen internen Status zu überprüfen. Diese müssen überprüft werden, wenn ein öffentlicher Zugriff erfolgt, unabhängig davon, ob es sich um eine Methode oder eine Eigenschaft handelt
Trap
2

Ein guter Ansatz für Ausnahmen besteht darin, sie zu verwenden, um Code für sich und andere Entwickler wie folgt zu dokumentieren:

Ausnahmen sollten für außergewöhnliche Programmzustände gelten. Das heißt, es ist in Ordnung, sie zu schreiben, wo immer Sie wollen!

Ein Grund, warum Sie sie in Getter einfügen möchten, ist die Dokumentation der API einer Klasse. Wenn die Software eine Ausnahme auslöst, sobald ein Programmierer versucht, sie falsch zu verwenden, wird sie nicht falsch verwendet! Wenn Sie beispielsweise während eines Datenlesevorgangs eine Validierung haben, ist es möglicherweise nicht sinnvoll, fortzufahren und auf die Ergebnisse des Prozesses zuzugreifen, wenn schwerwiegende Fehler in den Daten aufgetreten sind. In diesem Fall möchten Sie möglicherweise den Ausgabewurf abrufen, wenn Fehler aufgetreten sind, um sicherzustellen, dass ein anderer Programmierer nach dieser Bedingung sucht.

Sie sind eine Möglichkeit, die Annahmen und Grenzen eines Subsystems / einer Methode / was auch immer zu dokumentieren. Im allgemeinen Fall sollten sie nicht gefangen werden! Dies liegt auch daran, dass sie niemals ausgelöst werden, wenn das System wie erwartet zusammenarbeitet: Wenn eine Ausnahme auftritt, zeigt dies, dass die Annahmen eines Codeteils nicht erfüllt sind - z. B. dass es nicht auf die Art und Weise mit der Welt um es herum interagiert es war ursprünglich beabsichtigt. Wenn Sie eine Ausnahme abfangen, die zu diesem Zweck geschrieben wurde, bedeutet dies wahrscheinlich, dass das System in einen unvorhersehbaren / inkonsistenten Zustand eingetreten ist. Dies kann letztendlich zu einem Absturz oder einer Beschädigung von Daten oder Ähnlichem führen, was wahrscheinlich viel schwieriger zu erkennen / zu debuggen ist.

Ausnahmemeldungen sind eine sehr grobe Methode, um Fehler zu melden. Sie können nicht massenweise erfasst werden und enthalten nur eine Zeichenfolge. Dies macht sie ungeeignet für die Meldung von Problemen in Eingabedaten. Im Normalbetrieb sollte das System selbst keinen Fehlerzustand aufweisen. Aus diesem Grund sollten die darin enthaltenen Nachrichten für Programmierer und nicht für Benutzer konzipiert sein. Fehlerhafte Eingabedaten können erkannt und in geeigneteren (benutzerdefinierten) Formaten an Benutzer weitergeleitet werden.

Die Ausnahme (haha!) Von dieser Regel sind Dinge wie IO, bei denen Ausnahmen nicht unter Ihrer Kontrolle stehen und nicht im Voraus überprüft werden können.

JonnyRaa
quelle
2
Wie wurde diese gültige und relevante Antwort herabgestuft? In StackOverflow sollte es keine Politik geben, und wenn diese Antwort das Ziel zu verfehlen schien, fügen Sie einen entsprechenden Kommentar hinzu. Downvoting ist für Antworten, die irrelevant oder falsch sind.
Debater
1

Dies ist alles in MSDN dokumentiert (wie in anderen Antworten verlinkt), aber hier ist eine allgemeine Faustregel ...

Im Setter, wenn Ihre Eigenschaft über den Typ hinaus validiert werden soll. Beispielsweise sollte eine Eigenschaft namens PhoneNumber wahrscheinlich eine Regex-Validierung haben und einen Fehler auslösen, wenn das Format nicht gültig ist.

Für Getter, möglicherweise wenn der Wert null ist, aber höchstwahrscheinlich ist dies etwas, das Sie für den aufrufenden Code (gemäß den Entwurfsrichtlinien) behandeln möchten.

David Stratton
quelle
0

Dies ist eine sehr komplexe Frage und Antwort hängt davon ab, wie Ihr Objekt verwendet wird. Als Faustregel gilt, dass Property Getter und Setter, bei denen es sich um "späte Bindung" handelt, keine Ausnahmen auslösen sollten, während Eigenschaften mit ausschließlich "früher Bindung" bei Bedarf Ausnahmen auslösen sollten. Übrigens definiert das Code-Analyse-Tool von Microsoft die Verwendung von Eigenschaften meiner Meinung nach zu eng.

"späte Bindung" bedeutet, dass Eigenschaften durch Reflexion gefunden werden. Zum Beispiel wird das Attribut "Serialisierbar" verwendet, um ein Objekt über seine Eigenschaften zu serialisieren / deserialisieren. Das Auslösen einer Ausnahme in einer solchen Situation führt zu einer katastrophalen Unterbrechung und ist keine gute Möglichkeit, Ausnahmen zu verwenden, um robusteren Code zu erstellen.

"Early Binding" bedeutet, dass eine Eigenschaftsverwendung vom Compiler im Code gebunden wird. Zum Beispiel, wenn ein von Ihnen geschriebener Code auf einen Eigenschafts-Getter verweist. In diesem Fall ist es in Ordnung, Ausnahmen auszulösen, wenn sie sinnvoll sind.

Ein Objekt mit internen Attributen hat einen Status, der durch die Werte dieser Attribute bestimmt wird. Eigenschaften, die Attribute ausdrücken, die den internen Status des Objekts kennen und darauf reagieren, sollten nicht für die späte Bindung verwendet werden. Angenommen, Sie haben ein Objekt, das geöffnet, aufgerufen und dann geschlossen werden muss. In diesem Fall sollte der Zugriff auf die Eigenschaften, ohne zuerst open aufzurufen, zu einer Ausnahme führen. Nehmen wir in diesem Fall an, wir lösen keine Ausnahme aus und erlauben dem Code den Zugriff auf einen Wert, ohne eine Ausnahme auszulösen. Der Code wird glücklich erscheinen, obwohl er einen Wert von einem Getter erhalten hat, der nicht sinnvoll ist. Jetzt haben wir den Code, der den Getter aufgerufen hat, in eine schlechte Situation gebracht, da er wissen muss, wie der Wert überprüft wird, um festzustellen, ob er nicht sinnvoll ist. Dies bedeutet, dass der Code Annahmen über den Wert treffen muss, den er vom Property Getter erhalten hat, um ihn zu validieren. So wird schlechter Code geschrieben.

Jack D Menendez
quelle
0

Ich hatte diesen Code, bei dem ich mir nicht sicher war, welche Ausnahme ich auslösen sollte.

public Person
{
    public string Name { get; set; }
    public boolean HasPets { get; set; }
}

public void Foo(Person person)
{
    if (person.Name == null) {
        throw new Exception("Name of person is null.");
        // I was unsure of which exception to throw here.
    }

    Console.WriteLine("Name is: " + person.Name);
}

Ich habe verhindert, dass das Modell die Eigenschaft überhaupt null hat, indem ich es als Argument im Konstruktor erzwungen habe.

public Person
{
    public Person(string name)
    {
        if (name == null) {
            throw new ArgumentNullException(nameof(name));
        }
        Name = name;
    }

    public string Name { get; private set; }
    public boolean HasPets { get; set; }
}

public void Foo(Person person)
{
    Console.WriteLine("Name is: " + person.Name);
}
Fred
quelle