| 

.NET C# Java Javascript Exception

7
Hallo zusammen,

ich überlege gerade, wie ich "sauber" mit Enumerationen in switch-Anweisungen umgehe:
Im default-Zweig einer switch-Anweisung soll eine Exception geworfen werfen, um anzuzeigen, dass der aktuelle Enum-Wert nicht behandelt wird.

Welcher Exception-Typ wäre hierfür eurer Meinung nach am besten geeignet?

* InvalidOperationException erscheint mir durchaus logisch, würde ich aber nur verwenden, wenn sich die Ausnahme auf den Zustand eines Objekts bezieht. Die ausgewertete Variable könnte aber ein Parameter sein und nichts mit dem Zustand zu tun haben.

* ArgumentException erscheint auch logisch, zwingt mich aber zur Angabe des Namens des Parameters. Die ausgewertete Variable könnte aber kein Parameter sein sondern ein Member der Klasse und somit Bestandteil des Zustands.

* NotImplementedException erscheint ebenfalls logisch, da Code zur Behandlung des aktuellen Werts nicht implementiert ist.

* EnumValueNotHandledException abgeleitet von System.Exception wäre ebenfalls denkbar. Dieser könnte man auch gleich den Wert übergeben und automatisch eine nette Meldung daraus bauen.

Danke schonmal vorab ...
15.02.2011
Torsten Weber 691 1 8
Der eigene Exception-Typ kristallisiert sich als die beste Variante heraus, denn eine der bereits vorhandenen Exceptions sind denke ich zu ungenau und es müssten je nach Kontext andere verwendet werden. Ich würde mich dazu entscheiden, die Ausnahme von NotImplementedException abzuleiten, weil ich mich mal auf den Standpunkt stelle, dass Code für die Behandlung eines Falls nicht implementiert wurde. Außerdem könnte man beim Erzeugen der Exception den Typ der Enum und den nicht behandelten Wert mit zu übergeben und ggf. sogar automatisch eine passende Message dazu erzeugen.
Torsten Weber 15.02.2011
Die InvalidEnumArgumentException, die Andi unten ins Spiel gebracht hat, hat mich inzwischen denke ich zur "geschmeidigsten" Lösung inspiriert:
Wenn ein switch-Block einfach immer in einer eigenen Methode implementiert wird, handelt es sich beim Enum-Wert immer um ein Argument und somit kann dann auch immer diese Ausnahme verwendet werden. Dazu kommt noch, dass ein so gekapselter switch-Block unit-testbar wird und dokumentiert sich - einen ordentlichen Methodennamen vorausgesetzt - auch noch von selbst. Ha!
Torsten Weber 20.02.2011
5 Antworten
1
Ich finde, die Antwort hängt davon ab, was Dein Code an der Stelle tun soll.

a) Er soll mit allen gültigen Enum-Ausprägungen zurecht kommen, und die Exception soll signalisieren, dass da jemand bei einer nachträglichen Erweiterung vergessen hat, den Code nachzuziehen. Dann halte ich die NotImplementedException für passend. Eine ArgumentOutOfRangeException wäre, selbst wenn der switch-Parameter ein Methodenargument ist, imo nicht korrekt, denn der Aufrufer hat ja alles richtig gemacht. Das Argument ist "in range", der Code tut nur nicht, was er soll (nämlich alle Enums korrekt behandeln).
Ein prüfender Blick, ob ein Enum hier das passende Mittel ist, käme in einem solchen Fall übrigens auch in Betracht. Das Thema wird unter anderem in diesem Blog-Post behandelt.

b) Es geht bewußt nur um eine Teilmenge der Enum-Werte (das kann initial auch die unechte Teilmenge sein - wichtig ist nur, dass nicht automatisch vorausgesetzt werden kann, dass bei einer Erweiterung des Enums der Code nachgezogen werden muss).
b1) Wenn diese Teilmenge Teil des Vertrags der Methode ist (und der switch-Parameter direkt oder indirekt ein Argument der Methode), dann ist die ArgumentOutOfRangeException hier das passende. Sie sagt dem Aufrufer alles, was er wissen muss. Ein eigener Exception-Typ würde ihm keinen Mehrwert bieten. Insbesondere sollte es nicht erforderlich sein, speziell diese Exception zu fangen - ein solches catch wäre für mich ein deutlicher code smell.
b2) Wenn hingegen der eigene Code (das heißt Code, der - bewußt unscharf formuliert - "eng" mit dem switch zusammenhängt) den switch-Parameter produziert und dabei ein nicht erwarteter Wert herauskommt, dann ist das schlicht ein Programmierfehler. In Java würde ich hier eine assertion in Erwägung ziehen. In .NET kenne ich dazu kein wirkliches Äquivalent. Entweder definierst Du Dir dafür eine eigene Exception (die aber nicht explizit gefangen wird, sondern in einem allgemeinen "unhandled exception"-Block "weit oben"), oder Du greifst wieder zur NotImplementedException. Wichtiger als der Typ der Exception ist hier die Exception-Message, die klarmachen sollte, dass der Programmierer des switch davon ausgegangen ist, dass andere Werte hier nicht auftauchen können, dies dann aber doch passiert ist.
15.02.2011
Matthias Hlawatsch 13,2k 4 9
Zu a)
Eine NotImplementedException halt ich persönlich dafür unpassend. Die Implementierung ist ja gemacht, eben nur nicht vollständig oder wurde nach Änderungen nicht erweitert.
Zu b1) Für den Aufrufer hat ein eigener Exception-Typ keinen Mehrwehrt - wohl aber für den Programmierer. Wenn der Fehler bei einem Kunden passiert, ist anhand der spezifizierten Exception erkennbar in welcher Ecke der Fehler liegt. Klar, ich kann Messages verwenden. Das ist aber auch mit erheblichem Aufwand verbunden und setzt voraus, daß auch die richtigen Messages mitgegeben werden.
Joachim 15.02.2011
zu a) in Ermangelung einer "PartlyNotImplementedException" finde ich die NotImplementedException ausreichend aussagekräftig. Die NotSupportedException käme auch noch in Frage, noch mehr aber für b2).

Matthias Hlawatsch 15.02.2011
zu b1) Ich halte mich hier an die "Framework Design Guidelines" von K. Cwalina u. B. Abrams:
"Do not create new exception types to communicate usage errors. Throw one of the existing Framework exceptions instead. ... Usage errors need to be communicated to the developer calling the API and should not be handled directly in code. The exception type is generally less important to developers trying to fix their code than the message string. Therefore ... you should concentrate on designing a really good ... exception message and using one of the existing .NET Framework exception types"
Matthias Hlawatsch 15.02.2011
Schön. Für meinen Teil habe ich aber nicht vor, nur das zu tun, was andere mir vordenken. Wenn Cwalina und B.Abrams das so sehen, sollen sie es so tun. Ich versuche nur abzuwägen, was in meinen Fällen Sinn macht und was nicht. Und da jeder nunmal anders denkt, wird jeder unterschiedliche Wege beschreiten.
Joachim 15.02.2011
2
Man könnte die eigene Exception von der ArgumentOutOfRangeException oder der NotImplementedException ableiten. Je nachdem was gerade benötigt wird. Eine ordentliche Vererbungshierachie finde ich gerade bei Exceptions wichtig damit man sich beim behandeln für ein Detail-Level entscheiden kann.
Floyd 15.02.2011
@Floyd
Super Argument!
Joachim 15.02.2011
5
Kann nur Joachim beipflichten:
public class AutoTypNichtErmittelbarException : Exception { }


Ein switch-Statement brauchst du aber in den seltensten Fällen:
cars GetCarType(string car)
{
cars c;
bool result = System.Enum.TryParse<cars>(car, out c);
if (result == false) throw new AutoTypNichtErmittelbarException();
return c;
}
15.02.2011
Zorro 161 7
Der eigene Exception-Typ kristallisiert sich als beste Variante heraus. Allerdings nicht unbedingt abgeleitet von Exception und auch nicht unbedingt für jede Enum ein eigener Exception-Typ.

In Deinem Beispiel wird ein Wert in einen Enum-Wert konvertiert und darum geht es bei meiner Frage nicht. Ich möchte ja nicht Werte konvertieren, sondern auf Basis eines Enum-Werts eine Entscheidung treffen und dies tue ich normalerweise entweder mit "if" oder "switch".
Torsten Weber 15.02.2011
5
Hi,

für den Zweck ist die
ArgumentOutOfRangeException
gedacht.

MSDN
Die Ausnahme, die ausgelöst wird, wenn der Wert eines Arguments nicht in dem Wertebereich liegt, der durch die aufgerufene Methode als zulässig definiert ist.



Bei einer spezifischen Exception müsste für jeden Enumtype eine Exception erstellt werden,
imo zu großer unnötiger Aufwand.
Der Resharper verwendet diese Exception glaube ich auch.
15.02.2011
KHoffmann 939 6
Kommt auf die Situation an. Ein erhöhter Aufwand beim Erstellen spezifischer Exceptions ist u.U. schnell wieder aufgeholt. Alles mit vordefinierten Exceptions abzuwickeln ist auch nicht unbedingt die Lösung.
Joachim 15.02.2011
Wodurch habe ich denn den Erstellungsaufwand einer der Exceptions (pro Enumtype einen) wieder aufgeholt?
"Alles mit vordefinierten..." wie ist das gemeint?
Ich spreche mich nicht grundsätzlich gegen eigene Exceptions aus, aber für den Fall der Enums auf jeden Fall.
KHoffmann 15.02.2011
1
So wie ich es sehe, meinte Joachim nicht das Instanziieren der Exception, sondern die Erstellung der eigentlichen Klasse.
Mit vordefinierten Exception sind die im .NET-Framework mitgelieferten Exception-Typen gemeint. Soll heißen, sie passen nicht zu jedem Anwendungsfall. Man sollte nicht immer auf diese Typen zurückgreifen, nur wenn die Verwendung sinnvoll ist.
Andy Stumpp 15.02.2011
1
Nochmal: Es kommt auf die Situation an. Angenommen neben einer ArgumentOutOfRangeException durch ein enum, könnte eine weitere ArgumentOutOfRangeException auftreten, die mit dem enum nix zu tun hat. Durch das Werfen einer spezifischen Exception ist das Handling meines Erachtens einfacher. Wenn in der switch Anweisung wirklich nur bei falschem enum eine Exception geworfen werden soll, dann geb' ich Dir recht mit der Wahl von ArgumentOutOfRangeException
Joachim 15.02.2011
@Andy: Das ist mir schon klar, das nicht die Instanziierung der Ex gemeint ist.
_____________________________________________________
Nach Jochaims Erläuterung ist mir klar, wie er "... schnell wieder aufgeholt" meinte.
Ich kann mich jedoch an keine Situation erinnern, wo ich den Fall einer doppelten ArgumentOutOfRangeException hätte.
Aber belassen wir es dabei ;)
KHoffmann 15.02.2011
Na, dann verstehe ich aber nicht, warum Du zuerst so gar nicht verstehen wolltest, warum "schnell wieder aufgeholt" :)
Joachim 15.02.2011
3
Falls der enum-Wert als Parameter übergeben wird: Wie wäre es mit der InvalidEnumArgumentException ?
17.02.2011
Andi 151 5
Das gibt's ja gar nicht! Wie blind bin ich bitte? Diesen Exception-Typ gibt es seit .NET 1.1! Unlgaublich. Vielen Dank für Deinen Link!
Torsten Weber 17.02.2011
Ich muss da glaube ich noch mal drauf rum denken, denn diese Ausnahme scheint mir auch nicht "ausnahmslos" ;-) die Beste Wahl zu sein.
Torsten Weber 17.02.2011
Nee, ausnahmslos gut ist das Ding sicher nicht. Wie ich das sehe, ist InvalidEnumArgumentException mehr geeignet wenn der übergebene Enum-TYP(!) ungültig ist, während für einen ungültigen Enum-WERT(!) ArgumentOutOfRangeException irgendwie besser passt. Aber irgendwie ist das eine reine Geschmacksfrage :-)
Andi 20.02.2011
In der MSDN-Dokumentation (englische Version) zu dieser Ausnahme ist wohl auch irgendwas falsch. Die sprechen nämlich dort von "Enumerators" und es gibt einen Community-Kommentar, der darauf hinweist, dass der Hilfetext falsch ist.
Torsten Weber 20.02.2011
Du hast mich allerdings trotzdem inspiriert und ich habe einen Kommentar zu meiner Frage hinzugefügt. Dort entscheide ich mich nämlich für die InvalidEnumArgumentException.
Torsten Weber 20.02.2011
2
Leite Dir doch eine eigene Exception-Klasse von System.Exception ab und werfe diese.

public class MyEnumException: Exception
{
public MyEnumException()
{
}
public MyEnumException(string message)
: base(message)
{
}
...
}
15.02.2011
Joachim 3,1k 4 9
In meiner Antwort hätte ich besser schreiben sollen, das von Exception oder einer davon abgeleiteten Klasse abgeleitet werden soll. Aber das war ja auch nur ein Beispiel
Joachim 16.02.2011

Stelle deine .net-Frage jetzt!
TOP TECHNOLOGIES CONSULTING GmbH