Ich stimme dem Beitrag von Stephen Cleary zu, dass man versuchen sollte, seine Ausnahme zu kategorisieren und dann den Ausnahmetyp zu finden, der entsprechend geworfen wird. Ich finde die Kategorisierung von Eric Lippert sehr interessant und in vielerlei Hinsicht hat er recht, mais Ich habe über eine andere Art der Kategorisierung nachgedacht, die der Kategorisierung von Lippert ähnelt, mit dem Unterschied, dass ich mich mehr auf Code-Verträge . Was ich vorschlage, ist eine Kategorisierung Exceptions
in gescheiterten Vorbedingungen , Nachkonditionen und einfach Fehler .
Die Sache ist die: Jede Methode hat einen formalen Vertrag in dem Sinne, dass sie, wenn Sie alle Vorbedingungen erfüllen, verspricht, einige Nachbedingungen für Sie zu erfüllen. Im Allgemeinen können alle Vorbedingungen in drei Arten unterteilt werden:
- Sicherheitsrelevant (ist der Anrufer berechtigt, den Anruf zu tätigen?)
- Kontextbezogen (befindet sich das Objekt im richtigen Zustand, um den Anruf zu tätigen?)
- Eingabebezogen (hat der Aufrufer gültige Argumente angegeben?)
Im Prinzip sollte jede Methode diese Bedingungen der Reihe nach prüfen und eine der folgenden Ausnahmen auslösen (den genauen Typ oder die Ableitung):
SecurityException
InvalidOperationException
ArgumentException
Diese Ausnahmen teilen dem Anrufer mit, dass es "seine Schuld" war, dass etwas schief gelaufen ist und der Anruf nicht korrekt abgeschlossen werden konnte. Wenn jedoch alle Vorbedingungen gemäß der formalen Spezifikation der Methode erfüllt sind und die Methode feststellt, dass sie die angegebenen Nachbedingungen nicht erfüllen kann, sollte sie eine Exception
von einer Art, die klar kommuniziert was schiefgegangen. In der Regel sollten Sie für diese Situationen einen eigenen Ausnahmetyp definieren oder einen vorhandenen Ausnahmetyp wiederverwenden, sofern es sich nicht um einen der Typen handelt, die für Vorbedingungsfehler reserviert sind.
Und schließlich, Fehler sind Exceptions
die von der CLR ausgelöst werden, die praktisch überall und jederzeit auftreten können und praktisch nicht vorhersehbar sind. Sie werden nie explizit Teil Ihrer Methoden sein und sollten daher nie von Benutzercode ausgelöst (und auch nicht speziell behandelt) werden. In diesem Sinne sind sie fast eins zu eins mit den fatalen Ausnahmen von Lippert vergleichbar.
Was sollten Sie also meiner Meinung nach in dem von Slauma geschilderten Fall tun?
Nun, wie Sie sich vorstellen können, hängt dies ganz von den Verträgen der MyMethod(data)
, GetObject(data)
y objectOfAnotherClass.SomeProperty
. Was bedeutet die API von GetObject(data)
sagen, in welchen Situationen er zurückkehren wird null
(oder seine eigenen Ausnahmen auslösen)? Was ist die genaue Bedeutung und Spezifikation von SomeProperty
?
Angenommen, dass GetObject(data)
ist eine Methode, die ein aus einer Datenbank abgerufenes Objekt zurückgibt und die data
ist der Bezeichner des Objekts. Wenn der Vertrag von GetObject(data)
gibt an, dass er Folgendes zurückgeben wird null
wenn kein Objekt mit Kennung data
existiert, dann sollte Ihr Entwurf dies in seinem eigenen Vertrag berücksichtigen. Sie könnten den Aufrufer dazu zwingen, wenn das sinnvoll ist, immer einen Wert für data
tal que GetObject(data)
kehrt nicht zurück null
und wenn dies der Fall ist, können Sie eine ArgumentException
(oder Ableitung), um den Fehler auf der Seite des Anrufers anzuzeigen.
Andererseits, wenn GetObject(data)
festgelegt hat, dass es nur dann null zurückgibt, wenn der Aufrufer nicht genügend Rechte hat, um das Objekt abzurufen, können Sie eine SecurityException
(oder Ableitung), um dem Aufrufer von MyMethod(data)
.
Schließlich, wenn GetObject(data)
verspricht, dass er "niemals" zurückkehren wird null
und es trotzdem tut, können Sie Ihren Code mit einer NullReferenceException
(weil wir zu Recht davon ausgehen, dass es nie sein wird null
) oder behandeln Sie die Situation speziell, wenn Sie es mit sensiblem Code zu tun haben, indem Sie Ihren eigenen Exception-Typ auslösen (da eine Nachbedingung aus Sicht des Aufrufers von MyMethod fehlgeschlagen ist).
Der zweite Fall ist etwas komplizierter, lässt sich aber auf die gleiche Weise angehen. Angenommen, dass objectOfAnotherClass
eine Zeile oder Entität präsentiert, die aus einer Datenbank über einen Bezeichner abgerufen wurde data
y MyMethod(data)
eindeutig festgelegt, dass data
sollte einen Bezeichner eines Objekts angeben, bei dem objectOfAnotherClass.SomeProperty >= 0
dann würde in diesem Fall das Auslösen einer ArgumentException (oder eines Derivats) zu Ihrem Entwurf gehören.
Andererseits, wenn MyMethod(data)
arbeitet in einem Kontext, in dem der Aufrufer davon ausgehen kann, dass ein gültiger Bezeichner niemals ein Objekt zurückgeben wird, das objectOfAnotherClass.SomeProperty < 0
(oder besser: nicht einmal weiß, dass es ein solches Objekt gibt), dann ist ein solches Auftreten auf der Seite des Aufrufers wirklich unerwartet und MyMethod(data)
sollte den Fall entweder gar nicht explizit prüfen (weil man davon ausgeht, dass er nicht auftritt) oder, wenn man einen robusteren Code anstrebt, eine spezielle, benutzerdefinierte Exception auslösen, die das Problem anzeigt.
Zum Schluss: Ich denke, die Bestimmung der Art der Exception
zu werfen, hängt ausschließlich von den formalen Verträgen ab, die jede Methode mit ihren Aufrufern und Aufgerufenen hat. Wenn der Aufrufer eine Vorbedingung nicht erfüllt, sollte eine Methode dem Aufrufer immer mit dem Auslösen eines SecurityException
, InvalidOperationException
ou ArgumentException
. Wenn die Methode selbst ihre eigenen Nachbedingungen nicht erfüllt, kann sie entweder bei Ausnahmen, die von der CLR oder anderen Komponenten ausgelöst werden, abstürzen oder eine eigene, spezifischere Ausnahme auslösen, die das Problem angibt.