Warum sollen wir diese Ausnahmen nicht werfen?

111

Ich bin auf diese MSDN-Seite gestoßen , auf der es heißt:

Werfen Sie Exception , SystemException , NullReferenceException oder IndexOutOfRangeException nicht absichtlich aus Ihrem eigenen Quellcode.

Leider macht es sich nicht die Mühe zu erklären, warum. Ich kann die Gründe erraten, aber ich hoffe, dass jemand, der zu diesem Thema maßgeblicher ist, seine Einsicht bietet.

Die ersten beiden machen einen offensichtlichen Sinn, aber die beiden letzteren scheinen diejenigen zu sein, die Sie beschäftigen möchten (und tatsächlich habe ich).

Sind dies die einzigen Ausnahmen, die man vermeiden sollte? Wenn es andere gibt, was sind sie und warum sollten auch sie vermieden werden?

DonBoitnott
quelle
22
Von msdn.microsoft.com/en-us/library/ms182338.aspx : Wenn Sie einen allgemeinen Ausnahmetyp wie Exception oder SystemException in eine Bibliothek oder ein Framework auslösen, werden Verbraucher gezwungen, alle Ausnahmen abzufangen, einschließlich unbekannter Ausnahmen, die sie ausführen Ich weiß nicht, wie ich damit umgehen soll.
Henrik
3
Warum würden Sie eine NullReferenceException auslösen?
Rik
5
@ Rik: Ähnlich wie NullArgumentExceptionmanche Leute beide verwirren könnten.
Moslem Ben Dhaou
2
@ Rik Erweiterungsmethoden, gemäß meiner Antwort
Marc Gravell
3
Eine andere, die Sie nicht werfen sollten, istApplicationException
Matthew Watson

Antworten:

98

Exceptionist der Basistyp für alle Ausnahmen und als solcher furchtbar unspezifisch. Sie sollten diese Ausnahme niemals auslösen, da sie einfach keine nützlichen Informationen enthält. Das Aufrufen des Code-Catching für Ausnahmen konnte die absichtlich ausgelöste Ausnahme (aus Ihrer Logik) nicht von anderen Systemausnahmen unterscheiden, die völlig unerwünscht sind und auf echte Fehler hinweisen.

Der gleiche Grund gilt auch für SystemException. Wenn Sie sich die Liste der abgeleiteten Typen ansehen, sehen Sie eine Vielzahl anderer Ausnahmen mit sehr unterschiedlicher Semantik.

NullReferenceExceptionund IndexOutOfRangeExceptionsind von einer anderen Art. Nun, dies sind sehr spezifische Ausnahmen, daher könnte es in Ordnung sein , sie zu werfen . Sie werden diese jedoch immer noch nicht werfen wollen, da sie normalerweise bedeuten, dass Ihre Logik einige tatsächliche Fehler enthält. Die Nullreferenzausnahme bedeutet beispielsweise, dass Sie versuchen, auf ein Mitglied eines Objekts zuzugreifen null. Wenn dies in Ihrem Code möglich ist, sollten Sie nullstattdessen immer explizit nach einer nützlicheren Ausnahme suchen und diese auslösen (zum Beispiel ArgumentNullException). In ähnlicher Weise tritt IndexOutOfRangeExceptions auf, wenn Sie auf einen ungültigen Index zugreifen (für Arrays - nicht für Listen). Sie sollten immer sicherstellen, dass Sie dies überhaupt nicht tun, und zuerst die Grenzen eines Arrays überprüfen.

Es gibt einige andere Ausnahmen wie diese beiden, zum Beispiel InvalidCastExceptionoder DivideByZeroException, die für bestimmte Fehler in Ihrem Code ausgelöst werden und normalerweise bedeuten, dass Sie etwas falsch machen oder nicht zuerst nach ungültigen Werten suchen. Indem Sie sie wissentlich aus Ihrem Code entfernen, erschweren Sie es dem aufrufenden Code nur, festzustellen, ob sie aufgrund eines Fehlers im Code ausgelöst wurden, oder nur, weil Sie beschlossen haben, sie für etwas in Ihrer Implementierung wiederzuverwenden.

Natürlich gibt es einige Ausnahmen (hah) von diesen Regeln. Wenn Sie etwas erstellen, das eine Ausnahme verursachen kann, die genau mit einer vorhandenen übereinstimmt, können Sie diese verwenden, insbesondere wenn Sie versuchen, einem integrierten Verhalten zu entsprechen. Stellen Sie einfach sicher, dass Sie dann einen ganz bestimmten Ausnahmetyp auswählen.

Im Allgemeinen sollten Sie jedoch immer in Betracht ziehen, eigene Ausnahmetypen für bestimmte erwartete Ausnahmen zu erstellen, es sei denn, Sie finden eine (bestimmte) Ausnahme, die Ihren Anforderungen entspricht. Insbesondere wenn Sie Bibliothekscode schreiben, kann dies sehr nützlich sein, um die Ausnahmequellen zu trennen.

Sack
quelle
3
Der dritte Teil macht für mich wenig Sinn. Sicher, Sie sollten es lieber vermeiden, diese Fehler von Anfang an zu verursachen, aber wenn Sie z. B. eine IListImplementierung schreiben , liegt es nicht in Ihrer Macht, die angeforderten Indizes zu beeinflussen. Es ist der logische Fehler des Aufrufers, wenn ein Index ungültig ist, und Sie können sie nur informieren dieses logischen Fehlers durch Auslösen einer entsprechenden Ausnahme. Warum ist das IndexOutOfRangeExceptionnicht angebracht?
7
@delnan Wenn Sie implementieren IList, werfen Sie ein, ArgumentOutOfRangeExceptionwie in der Schnittstellendokumentation vorgeschlagen . IndexOutOfRangeExceptionist für Arrays, und soweit ich weiß, können Sie Arrays nicht erneut implementieren.
stupsen
2
Was auch etwas verwandt sein könnte, NullReferenceExceptionwird normalerweise intern als Sonderfall eines AccessViolationException(IIRC der Test ist so etwas wie cmp [addr], addr, dh es versucht, den Zeiger zu dereferenzieren und wenn es mit einer Zugriffsverletzung fehlschlägt, behandelt es den Unterschied zwischen NRE und AVE im resultierenden Interrupt-Handler). Abgesehen von semantischen Gründen gibt es also auch Betrug. Dies kann Sie auch davon abhalten, nullmanuell zu suchen, wenn dies nicht hilfreich ist. Wenn Sie trotzdem eine NRE auslösen möchten, lassen Sie .NET dies tun.
Luaan
In Bezug auf Ihre letzte Aussage zu benutzerdefinierten Ausnahmen: Ich hatte nie das Bedürfnis, dies zu tun. Vielleicht verpasse ich etwas. Unter welchen Bedingungen müsste ein benutzerdefinierter Ausnahmetyp anstelle von etwas aus dem Framework erstellt werden?
DonBoitnott
1
Für kleinere Anwendungen ist dies möglicherweise nicht erforderlich. Sobald Sie jedoch etwas Komplexeres erstellen, bei dem einzelne Teile als unabhängige „Komponenten“ arbeiten, ist es häufig sinnvoll, benutzerdefinierte Ausnahmen für benutzerdefinierte Fehlersituationen einzuführen. Wenn Sie beispielsweise über eine Zugriffssteuerungsschicht verfügen und versuchen, einen Dienst auszuführen, obwohl Sie nicht dazu berechtigt sind, können Sie eine benutzerdefinierte Ausnahme für "Zugriff verweigert" oder etwas anderes auslösen. Wenn Sie einen Parser haben, der eine Datei analysiert, haben Sie möglicherweise eigene Parserfehler, die Sie dem Benutzer melden müssen.
Poke
36

Ich vermute, die Absicht mit den letzten 2 ist es, Verwechslungen mit eingebauten Ausnahmen zu verhindern, die eine erwartete Bedeutung haben. Ich bin jedoch der Meinung, dass, wenn Sie die genaue Absicht der Ausnahme beibehalten : es die richtige ist throw. Wenn Sie beispielsweise eine benutzerdefinierte Sammlung schreiben, erscheint die Verwendung völlig vernünftig IndexOutOfRangeException- klarer und spezifischer, IMO, als ArgumentOutOfRangeException. Und während Sie List<T>sich für Letzteres entscheiden könnten, gibt es in der BCL mindestens 41 Stellen (mit freundlicher Genehmigung des Reflektors) (ohne Arrays), die maßgeschneidert sind IndexOutOfRangeException- keine davon ist "niedrig" genug, um eine besondere Ausnahme zu verdienen. Also ja, ich denke, Sie können zu Recht argumentieren, dass diese Richtlinie albern ist. Gleichfalls,NullReferenceException ist ein bisschen nützlich in Erweiterungsmethoden - wenn Sie die Semantik beibehalten möchten, dass:

obj.SomeMethod(); // this is actually an extension method

wirft ein NullReferenceExceptionwann objist null.

Marc Gravell
quelle
2
"Wenn Sie die genaue Absicht der Ausnahme beibehalten" - Wenn dies der Fall wäre, würde die Ausnahme sicher ausgelöst, ohne dass Sie sie zuerst testen müssten? Und wenn Sie es bereits getestet haben, ist es dann keine wirkliche Ausnahme?
PugFugly
5
@PugFugly Nehmen Sie sich 2 Sekunden Zeit, um das Beispiel für die Erweiterungsmethode anzusehen: Nein, das wird nicht ausgelöst, ohne dass Sie es testen müssen. Wenn SomeMethod()kein Mitgliederzugriff erforderlich ist, ist es falsch, ihn zu erzwingen. Ebenso: Nehmen Sie diesen Punkt mit den 41 Stellen in der BCL, die benutzerdefinierte erstellen IndexOutOfRangeException, und den 16 Stellen, die benutzerdefinierte erstellenNullReferenceException
Marc Gravell
16
Ich würde argumentieren, dass eine Erweiterungsmethode immer noch a ArgumentNullExceptionanstelle von a werfen sollte NullReferenceException. Selbst wenn der syntaktische Zucker aus Erweiterungsmethoden dieselbe Syntax wie der normale Mitgliederzugriff zulässt, funktioniert er dennoch sehr unterschiedlich. Und ein NRE von zu bekommen MyStaticHelpers.SomeMethod(obj)wäre einfach falsch.
stupsen
1
@PugFugly BCL ist eine „Basisklassenbibliothek“, im Grunde das Kernmaterial in .NET.
stupsen
1
@PugFugly: Es gibt viele Szenarien, in denen eine Ausnahme nicht zu einem "ungünstigen" Zeitpunkt ausgelöst wird, wenn eine Bedingung nicht präventiv erkannt wird. Wenn eine Operation nicht erfolgreich sein wird, ist es besser, eine Ausnahme frühzeitig auszulösen, als die Operation zu starten, die Hälfte zu durchlaufen und dann das resultierende teilweise verarbeitete Durcheinander zu bereinigen.
Supercat
5

Wie Sie bereits erwähnt haben, listet Microsoft im Artikel Erstellen und Auslösen von Ausnahmen (C # -Programmierhandbuch) unter dem Thema Zu vermeidende Dinge beim Auslösen von Ausnahmen tatsächlich System.IndexOutOfRangeExceptioneinen Ausnahmetyp auf, der nicht absichtlich aus Ihrem eigenen Quellcode ausgelöst werden sollte.

Im Gegensatz dazu scheint Microsoft im Artikelwurf (C # -Referenz) gegen seine eigenen Richtlinien zu verstoßen. Hier ist eine Methode, die Microsoft in sein Beispiel aufgenommen hat:

static int GetNumber(int index)
{
    int[] nums = { 300, 600, 900 };
    if (index > nums.Length)
    {
        throw new IndexOutOfRangeException();
    }
    return nums[index];
}

Microsoft selbst ist also nicht konsistent, da es das Werfen IndexOutOfRangeExceptionin seiner Dokumentation für throw!

Dies führt mich zu glauben , dass zumindest für den Fall IndexOutOfRangeException, kann es vorkommen , dass dieser Ausnahme - Typ kann durch den Programmierer geworfen werden und eine akzeptable Praxis berücksichtigt werden.

DavidRR
quelle
1

Als ich Ihre Frage las, fragte ich mich, unter welchen Bedingungen man die Ausnahmetypen auslösen möchte NullReferenceException, InvalidCastExceptionoder ArgumentOutOfRangeException.

Wenn ich auf einen dieser Ausnahmetypen stoße, bin ich (der Entwickler) meiner Meinung nach besorgt über die Warnung in dem Sinne, dass der Compiler mit mir spricht. Wenn Sie (der Entwickler) solche Ausnahmetypen auslösen können, entspricht dies (dem Compiler) dem Verkauf der Verantwortung. Dies legt beispielsweise nahe, dass der Compiler dem Entwickler nun erlauben sollte, zu entscheiden, ob es sich um ein Objekt handelt null. Aber eine solche Entscheidung zu treffen, sollte wirklich die Aufgabe des Compilers sein.

PS: Seit 2003 entwickle ich meine eigenen Ausnahmen, damit ich sie nach Belieben werfen kann. Ich denke, dies wird als bewährte Methode angesehen.

Bellash
quelle
Gute Argumente. Ich denke jedoch, es wäre genauer zu sagen, dass der Programmierer die .NET Framework-Laufzeit diese Art von Ausnahmen auslösen lassen sollte (und dass der Programmierer sie in angemessener Weise behandeln sollte).
DavidRR
0

Die Diskussion über NullReferenceExceptionund IndexOutOfBoundsExceptionbeiseite legen:

Was über den Fang und werfen System.Exception. Ich habe diese Art von Ausnahme oft in meinen Code geworfen und wurde nie davon verarscht. Ebenso fange ich sehr oft den unspezifischen ExceptionTyp und es hat auch ziemlich gut für mich funktioniert. Warum ist das so?

Normalerweise argumentieren Benutzer, dass sie in der Lage sein sollten, Fehlerursachen zu unterscheiden. Nach meiner Erfahrung gibt es nur sehr wenige Situationen, in denen Sie verschiedene Ausnahmetypen unterschiedlich behandeln möchten. In den Fällen, in denen Benutzer Fehler programmgesteuert behandeln sollen, sollten Sie einen spezifischeren Ausnahmetyp auslösen. In anderen Fällen bin ich von der allgemeinen Best-Practice-Richtlinie nicht überzeugt.

In Bezug auf das Werfen Exceptionsehe ich keinen Grund, dies in allen Fällen zu verbieten.

BEARBEITEN: auch von der MSDN-Seite:

Ausnahmen sollten nicht verwendet werden, um den Ablauf eines Programms im Rahmen der normalen Ausführung zu ändern. Ausnahmen sollten nur zum Melden und Behandeln von Fehlerzuständen verwendet werden.

Das Übertreiben von catch-Klauseln mit individueller Logik für verschiedene Ausnahmetypen ist ebenfalls keine bewährte Methode.

uebe
quelle
Die Ausnahmemeldung ist noch vorhanden, um anzuzeigen, was passiert ist. Ich kann mir nicht vorstellen, dass Sie für jeden Fehler einen neuen Ausnahmetyp erstellen, nur für den Fall, dass ein Verbraucher diese Fehler programmgesteuert unterscheiden möchte.
uebe
Was ist mit meinem vorherigen Kommentar passiert?
DonBoitnott