Laden...

throw oder return false

Erstellt von hinrich vor 15 Jahren Letzter Beitrag vor 15 Jahren 2.607 Views
H
hinrich Themenstarter:in
116 Beiträge seit 2008
vor 15 Jahren
throw oder return false

An vielen Punkten in einem Projekt steht man an der Frage "Wie sag ich's meinem Boss, dass es schief ging?". Und an der Stelle hat der Programmierer einer Methode zwei grundsätzliche Möglichkeiten:*Er schmeisst wie ein wütendes Kind eine Exception, oder *er verlässt die Methode mit dem bool'schen Ergebnis false.

Meine Frage nun ist: Was nutzt Ihr? Exceptions oder Ifs? Exceptions (Ausnahmen) deuten ja sematisch auf eine außergewöhnliche Situation hin, so beispielsweise bei File.Delete(), wenn die Datei nicht da ist. Ifs implizieren imho mehr normale Fälle.

Exceptions haben aus meiner Sicht den Vorteil, dass ich den Code einfach hintereinander wegschreiben kann (getreu dem Motto: Halte den Code links). Es kann aber auch bedeuten, dass ich viele try-catch-Blöcke erstellen muss, wobei je nach Implementierung auch noch sehr viele catch-Blöcke auftreten.

Was nutzt Ihr und warum?

S
8.746 Beiträge seit 2005
vor 15 Jahren

Schau mal in den Foren-Suche. Die Diskussion hatten wir oft.

Aber ein kleines Stück Code zum Denken. Warum sind folgende Codeabschnitte NICHT funktional gleich? Frage 2: Ist es möglich Variante 2 durch Code ohne try/catch zu ersetzen?

if (File.Exist(x))
{
    File.Delete(x);
    return true;
}
else
  return false;
try
{
    File.Delete(x);
    return true;
}
catch(IOException ex)
{
   return false;
}

Ansonsten ist die Aussage

Ifs implizieren imho mehr normale Fälle.

uneingeschränkt richtig. "Normale Fälle" sollte man durchaus prüfen und explizit behandeln, insbesondere dann, wenn diese Fälle häufig sind, denn Exceptions sind sehr teuer in der Erzeugung.

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo hinrich,

neben den üblichen Überlegungen zu Normalfall und Ausnahmefall, die sicher richtig sind, mal folgender Gedanke: mit return false zwingst du den Aufrufer, den Rückgabewert (direkt an der Aufrufstelle) zu berücksichtigen (also abzufragen oder zumindest zwischenzuspeichern, um ihn später abzufragen). Bei throw kann die Behandlung auch (viele) Aufrufebenen weiter oben erfolgen.

Deshalb bin ich gar kein Freund von Exception-Sparsamkeit. Ich würde in svensons Beispiel immer Variante zwei nehmen, selbst wenn es häufig vorkommen kann, dass die Datei nicht vorhanden ist.

Auch bei falschen Benutzereingaben kann man sich ehrlich fragen, was das häufig angeführte Argument, dass Exceptions relativ teuer sind, soll. Klar sind sie teurer als ein return false, aber selbst wenn sie um Faktoren teuer sind, ist das absolut gesehen immer noch nicht wirklich teuer. Bei Benutzereingaben ist immer de Benutzer das limitierende Element. Wenn der Benutzer mehrere Sekunden für eine Eingabe braucht, dann kommt es auf die Mikrosekunden für die Exception auch nicht an, zumal dann nicht, wenn die Exception eh gleich angezeigt wird. Dann ist nämlich das Anzeigen wesentlich teurer als die Exception. Trotzdem wird das teuere Anzeigen keiner in Frage stellen, die Exception wird aber merkwürdigerweise in Frage gestellt.

Erst wenn man in einer Schleife alle Zeilen einer Datei durchgeht und prüft (eine Datei ist ja auch eine Form einer Benutzereingabe), kann kann es einen spürbaren Unterschied machen, ob man für jede falsche Zeile eine Exception wirft und fängt oder ein if verwendet.

herbivore

3.511 Beiträge seit 2005
vor 15 Jahren

Hallo,

Die meisten Methoden die ich schreibe liefern Resultcodes zurück. Um svensons Beispiel mal weiter auszudehnen.


public int DeleteFile(string fileName)
{
  if (File.Exists(fileName))
  {
    try
    {
      File.Delete(fileName);
      return 0; // OK
    }
    catch (SecurityException)
    {
      return -2; // Keine Berechtigung zum Löschen
    }
    catch
    {
      return -3; // Unbekannt
    }
  }
  else
    return -1; // Datei nicht gefunden
}

Das heißt, das ich versuche so häufig wie möglich Exceptions aus dem Weg zu gehen, aber an Stellen wo es wahrscheinlich "teurer" ist zu prüfen ob etwas geht, schreibe ich lieber ein try..catch. Und da sehe ich es wie herbivore. Auf diese Mikrosekunden kommt es dann wirklich nicht mehr drauf an.

"Jedes Ding hat drei Seiten, eine positive, eine negative und eine komische." (Karl Valentin)

1.200 Beiträge seit 2007
vor 15 Jahren

Ich persönlich kann diese Variante gar nicht leiden. Meines Erachtens ist diese Methode unnötig kompliziert geschrieben. Die Fehlerbehandlung sollte nicht über ErrorCodes im Caller gemacht werden, sondern direkt im try-catch Block. Viele Return-Statements machen den Code meines Erachtens schwer zu lesen und der Caller muss auch wissen, was die Magic Values die du da zurücklieferst eigentlich bedeuten.

Genaugenommen würde ich hier die meiste Fehlerbehandlung gar nicht in der Methode selbst machen, sondern eher im (geschäftslogischen) Kontext, in dem die Exception auftritt und in dieser Methode gar nicht abfangen.

Just my 2 cents.

Shift to the left, shift to the right!
Pop up, push down, byte, byte, byte!

YARRRRRR!

1.433 Beiträge seit 2006
vor 15 Jahren

Genaugenommen würde ich hier die meiste Fehlerbehandlung gar nicht in der Methode selbst machen, sondern eher im (geschäftslogischen) Kontext, in dem die Exception auftritt und in dieser Methode gar nicht abfangen. Man müsste sich überlegen wo in der Geschäftslogik, was für Ausnahmen auftreten können und dann für diese Fälle Ausnahmen schreiben, die dann im Fehlerfall ausgelöst werden können.

Grüsse
Daniel
Space Profile
Wer nicht fragt, der nicht gewinnt

T
574 Beiträge seit 2008
vor 15 Jahren

bin GMLOD's Meinung.
a) musst du dir Return-Codes überlegen die du
b) wieder richtig in Klartext ausgeben musst
c) wenn über sowas, dann mit konstanten, mit denen man auch gleich im Code, ohne ihn zu entschlüsseln, sieht, was da zurückgeliefert wird

Aber eigentlich finds ichs auch unnötig, weil man ja gleich die richtige Exception, mit einer klaren Message throwen kann. Ob der Caller nun den Return-Wert behandelt, oder verschiedene Exceptions (oder nur eine, mit unterschiedlichen Messages) ist eigentlich auch wieder egal und gleich viel arbeit (mit einer Ex-Klasse und versch. Messages sogar weniger). Der Code wird dadurch aber wesentlich lesbarer.

Gelöschter Account
vor 15 Jahren

zu errorcodes sag ich nur:

"Very little software really gets error handling right. Even many critical, backend server systems tend to break under heavy loads. And the vast majority of end-user applications handle errors gracefully only for the most well understood, commonly encountered conditions, but very poorly for most other conditions."

Quelle: http://www.kinlan.co.uk/2006/04/error-codes-vs-exceptions.html

vor allem das catch->unbekannt ist eigendlich ein fass ohne boden. diese errorcodes können auch zum antipattern "MagicValues" führen. in c# ist das nciht mehr notwendig. so unterbindest du loggingmechnismen auf höherer ebene und eine vernünftige reaktion auf spezifische ursachen.

generell denken noch immer viele entwickler, das exceptions sehr teure sachen sind. dieses denken kommt noch von c++ wo eine exception extrem teuer war. in c# allerdings ist es nichts mehr als eine erzeugung eines kleinen objektes (ja ok der stacktrace wird noch ausgelesen aber das wars dannauch schon) und ein sprung. quasi als hättest du als rückgabewert irgendein kleineres objekt, das du vorher instanziieren musst.

generell besagen die c# guidelines, das du nur exception fangen sollst, die du auch behandeln kannst. verschlucken ist allerdings nie eine lösung (siehe fxcop regel "Do not catch general Exceptions").

wie ich es mache?
ich benutze gern exceptions (natürlich nciht um den normalen logischen ablauf zu formulieren). ich versuche sie an sinnvoller stelle zu fangen und sinnvoll zu behandeln. so bekomme ich auch vom systemtest ab und zu noch eine exception mitgeteilt, die bei n-bedingungen aufgetreten ist, die ich noch nciht gefangen habe (und woran ich auch nciht gedacht habe) und kann so noch ein vernünftiges handling implementieren (evtl nur loggen oder loggen und den benutzer informieren oder sogar ganz was anderes wie z.b. einen anmeldedialog anzubieten um das löschen mit anderen rechten nochmals zu versuchen usw...)

generell fange ich nur an einer oder zwei stellen alle exceptions und das auch nur, damit die anwendung nicht böse wegkracht.

mann jetzt ist während des schreibens so viel gepostet worden... 😃

0
767 Beiträge seit 2005
vor 15 Jahren
  
public int DeleteFile(string fileName)  
{  
  if (File.Exists(fileName)) // X  
  {  
    try  
    {  
      File.Delete(fileName); // Y  
      return 0; // OK  
    }  
    catch (SecurityException)  
    {  
      return -2; // Keine Berechtigung zum Löschen  
    }  
    catch  
    {  
      return -3; // Unbekannt  
    }  
  }  
  else  
    return -1; // Datei nicht gefunden  
}  
  

Ein gutes Beispiel, warum Exceptions besser sind. Im Fall dass das File an der Stelle X gelöscht wurde wird -1 zurückgegeben. Wenn das File aber an Y zwischenzeitlich gelöscht wurde (auch wenns unwahrscheinlich ist) wird -3 zurückgegeben. Zwei verschiedene Fehler mit der gleichen Ursache.

Man müsste alle auftretenden Exceptions explizit catchen und entsprechend zurückgeben, auch die von denen man glaubt, man hätte sie ausgeschlossen. Und dann kanns immer noch passieren, dass neuere Versionen weitere Exceptions werfen.

Ausserdem geht die innere Information komplett verloren. Für die gleiche Exception kann die Message komplett unterschiedlich sein, aber auch für Exceptions mit der gleichen Message könnte es unterschiedliche InnerException also unterschiedliche Ursachen geben.

Niemand wird je erfahren was los war.

loop:
btst #6,$bfe001
bne.s loop
rts

3.511 Beiträge seit 2005
vor 15 Jahren

Ich persönlich kann diese Variante gar nicht leiden.

Wenn ich ehrlich bin ich auch nicht unbedingt 😃. Klingt jetzt ein bisschen komisch, aber es kommt halt drauf an, wo man sowas benötigt. Sowas verwende ich hauptsächlich, in den ganzen WCF Services die ich habe. Ich will vermeiden, das Exceptions über den WCF Channel laufen. Und da hab ich mir das so angewöhnt. Nur so als warum 😃

BTW: Das ich Konstanten benutze ist klar, ist ja nur ein runtergetipptes Codebeispiel 😃

"Jedes Ding hat drei Seiten, eine positive, eine negative und eine komische." (Karl Valentin)

H
hinrich Themenstarter:in
116 Beiträge seit 2008
vor 15 Jahren

Danke erstmal für die Antworten.

(...) Bei throw kann die Behandlung auch (viele) Aufrufebenen weiter oben erfolgen. (...)

(...) Genaugenommen würde ich hier die meiste Fehlerbehandlung gar nicht in der Methode selbst machen, sondern eher im (geschäftslogischen) Kontext, in dem die Exception auftritt und in dieser Methode gar nicht abfangen.

Das sind für mich eigentlich die ausschlaggebenden Punkte. So meine ich, dass z. B. der Datenlayer gar nicht wissen kann, was die Geschäftslogik aufgrund eines wie auch immer gearteten Fehlers machen wird (Programm abbrechen, Fehler protokollieren oder sonst was).

Um nochmal auf das Beispiel von Khalid zu kommen: Dass in den unteren Schichten die Frage, warum ein Löschen fehlgeschlagen ist, durchaus interessant sein kann, mag sein. Aber die höheren Schichten wird der Grund kaum noch interessieren. Von daher ist die Rückgabe eines numerischen Fehlercodes (in Deinem Beispiel als Signed Int, wobei die alte Diskussion angeschoben werden könnte, ob Signed oder Unsigned performanter ist ;-Q) zumindest bei mir unwichtig. Ein bool'scher Wert reicht, denn er sagt der Logik oder gar dem UI, ob gelöscht wurde oder eben nicht. Da kann man dann eine Fehlermeldung erzeugen, die man vielleicht dem Anwender zeigen mag, muss man aber nicht. Wesentlicher wäre es aber eventuell vorhandene Listen zu aktualisieren.

Und wenn die Fehlermeldung so wichtig ist (was ich nicht glaube, denn die meisten Anwender werden von detaillierten Fehlermeldungen nur verwirrt und rufen in der IT an), kann man sie eigentlich eleganter mit einem Event transportieren.

Hinzu kommt der Einwand mit den unterschiedlichen Codes bei identischen Ursachen, aber diesen Design-Fehler schiebe ich mal a conto On-The-Fly in der Eingabemaske programmiert...

Auch wenn es noch etwas mehr Aufwand bedeutet, das Erzeugen auch einer eigenen Hierarchie von Exceptions die dann auch in einfachen Fällen flexibler einen Zustand transportieren können.

Das Thema Performance klammere ich für meinen Fall einmal aus. Es geht bei mir um ein Datenbank gestütztes Verwaltungsprogramm, wobei die MySQL-Datenbank im ungünstigsten Fall über eine 10MBit-, im günstigsten Fall über eine 1GBit-Schnittstelle angebunden ist. Da sind die limitierenden Faktoren zu suchen, und natürlich auch im Bediener der UI. Und für echte Performance kann man sich auch eine DLL aus Assembler stricken...

S
8.746 Beiträge seit 2005
vor 15 Jahren

Vielleicht mal noch eine Anmerkung zu meinem Code. Ich hätte ihn so wohl nicht geschrieben, auch nicht Variante 2. Im Prinzip wollte ich nur zwei Sachen aufzeigen. Eine davon hat Khalid gezeigt, nämlich dass es noch mehr Gründe geben kann warum eine Datei nicht löschbar ist. Der zweite Fall ist ihm entgangen: Zwischen Exist-Prüfung und Delete vergeht Zeit und damit kann eine andere Anwendung das File löschen. Damit ist klar: Erst nach dem Löschversuch kann man wissen ob es klappt oder nicht.

Exceptions durch Prüfungen zu vermeiden ist in .NET oft nicht möglich oder - wie in diesem Fall - nur auf den ersten Blick.

Auch dort wo man es kann, sollte man sich überlegen, ob man nicht doch ein paar Exceptions vorsieht. Ich verstehe z.B. nicht wirklich, warum TryParse() im .NET-Framework keine ArgumentNullException bei einem Null-String wirft. Das ist definitiv eine "Ausnahme". Hier einfach false zurückzugeben ist m.E. nur zu rechtfertigen, weil man hier explizit eine exceptionfreie Routine schaffen wollte. Hier ist man über das Ziel hinausgeschossen, nämlich den _Regelfall _exceptionsfrei zu machen.

Zusammengefaßt mein Rat: NIEMALS Error-Codes in einer .NET-Anwendung verwenden. true/false im Kontext von Is....(), Try....(), etc.! Also Regelfall-Ergebnisse.

Zur Schichten-Debatte: Auch an Schichten-Grenzen sollte man Exceptions verwenden. Exceptions tragen einfach mehr Informationen als nur true/false. Jede Schicht hat ggf. eigenes Logging und braucht daher passable Informationen. Natürlich kann und soll eine generische Datenhaltungsschicht nicht explizit IOException, ADO.NET-Exceptions, usw. nach oben melden. Hier müssen Adapter zwischen der Datenhaltung und der konkreten Implementierung die konkreten Exceptions z.B. in generische StorageFailureExceptions umwandeln (die InnerException darf dann die IOException sein). Ansonsten gilt aber: Solange man innerhalb der logischen und physikalischen Einheit (dazu gehört auch der Thread) bleibt sollte man Exceptions möglichst nicht behandeln oder umwandeln.

Die Ausnahme ist natürlich klar: Wenn Fehler innerhalb einer Funktion vom Rest der Anwendung ignoriert werden sollen, muss die natürlich alles wegfangen. Alles heisst natürlich: Die erwarteten Fehler. Ein catch(Exception) sollte man hier auf jeden Fall vermeiden.

Das ist einen Blick wert: http://blogs.msdn.com/kcwalina/archive/2005/03/16/396787.aspx

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo hinrich,

was ich nicht glaube, denn die meisten Anwender werden von detaillierten Fehlermeldungen nur verwirrt ...

anderseits sind die meisten Anwender auch genervt, wenn eine Fehlermeldung vom Schlage "Es ist ein allgemeiner Fehler aufgetreten" kommt. Ich glaube nicht, dass die Detailliertheit das Problem ist, sondern Anwender dann mit Fehlermeldungen gut klar kommen, wenn sie in der Meldung klar gesagt bekommen, was sie tun müssen, um den Fehler zu beheben. Sich einfach darauf zu verlassen, dass die Meldung die Ursache des Fehlers so genau beschreibt, dass "schon jeder wissen wird, was zu tun ist", funktioniert leider meistens nicht.

... und rufen in der IT an

Spätestens der IT-Support braucht dann aber sowieso wieder die Details. Man kann also aus dem von dir zum Thema Verwirrung Gesagten nicht schließen, dass man auf detaillierte Fehlermeldungen verzichten kann. Natürlich kann man darüber nachdenken, die Details hinter einen "Details-Button" zu legen und nicht sofort anzuzeigen.

Und dann sind ja da doch immer noch die erfahrenen Anwender, die die Details benötigen. Fehlermeldungen sollten also schon möglichst detailliert sein oder andersherum zumindest nicht absichtlich allgemein.

und wenn die Fehlermeldung so wichtig ist [...]kann man sie eigentlich eleganter mit einem Event transportieren.

Nein, Events sollte man nicht dafür missbrauchen. Um Fehler zu transportieren gibt es Exceptions und nur Exceptions.

herbivore

3.971 Beiträge seit 2006
vor 15 Jahren

Hier noch ein interessanter Link:
HowToCode: ErrorCodes, Exceptions, den User informieren, wenn etwas schief läuft - wie gehts?

Besonders die Kommentare sind dabei Interessant.

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...