Um alle Standardantworten zu vermeiden, auf die ich hätte googeln können, werde ich ein Beispiel geben, das Sie alle nach Belieben angreifen können.
C # und Java (und zu viele andere) haben mit vielen Typen ein Überlaufverhalten, das ich überhaupt nicht mag (zB type.MaxValue + type.SmallestValue == type.MinValue
zum Beispiel :) int.MaxValue + 1 == int.MinValue
.
Aber angesichts meiner bösartigen Natur werde ich diese Verletzung etwas beleidigen, indem ich dieses Verhalten auf einen überschriebenen DateTime
Typ ausdehne . (Ich weiß, dass DateTime
in .NET versiegelt ist, aber für dieses Beispiel verwende ich eine Pseudosprache, die genau wie C # ist, mit Ausnahme der Tatsache, dass DateTime nicht versiegelt ist.)
Die überschriebene Add
Methode:
/// <summary>
/// Increments this date with a timespan, but loops when
/// the maximum value for datetime is exceeded.
/// </summary>
/// <param name="ts">The timespan to (try to) add</param>
/// <returns>The Date, incremented with the given timespan.
/// If DateTime.MaxValue is exceeded, the sum wil 'overflow' and
/// continue from DateTime.MinValue.
/// </returns>
public DateTime override Add(TimeSpan ts)
{
try
{
return base.Add(ts);
}
catch (ArgumentOutOfRangeException nb)
{
// calculate how much the MaxValue is exceeded
// regular program flow
TimeSpan saldo = ts - (base.MaxValue - this);
return DateTime.MinValue.Add(saldo)
}
catch(Exception anyOther)
{
// 'real' exception handling.
}
}
Natürlich könnte ein Wenn dies genauso einfach lösen, aber die Tatsache bleibt, dass ich nicht verstehe, warum Sie keine Ausnahmen verwenden konnten (logischerweise kann ich sehen, dass Ausnahmen in bestimmten Fällen vermieden werden sollten, wenn Leistung ein Problem ist ).
Ich denke, in vielen Fällen sind sie klarer als Wenn-Strukturen und brechen keinen Vertrag, den die Methode macht.
IMHO ist die Reaktion „Niemals für den regulären Programmfluss verwenden“, die jeder zu haben scheint, nicht so gut unterbaut, wie es die Stärke dieser Reaktion rechtfertigen kann.
Oder irre ich mich?
Ich habe andere Beiträge gelesen, die sich mit allen Arten von Sonderfällen befassen, aber mein Punkt ist, dass daran nichts falsch ist, wenn Sie beide sind:
- klar
- Halten Sie den Vertrag Ihrer Methode ein
Erschieß mich.
quelle
if
Aussage überlegen ist . Sie werden das sehr schwer finden. Mit anderen Worten: Ihre Prämisse ist fehlerhaft, und die Schlussfolgerungen, die Sie daraus ziehen, sind daher falsch.Antworten:
Haben Sie jemals versucht, ein Programm zu debuggen, das im normalen Betriebsverlauf fünf Ausnahmen pro Sekunde auslöst?
Ich habe.
Das Programm war ziemlich komplex (es war ein verteilter Berechnungsserver), und eine geringfügige Änderung an einer Seite des Programms konnte leicht etwas an einer völlig anderen Stelle beschädigen.
Ich wünschte, ich hätte das Programm einfach starten und auf Ausnahmen warten können, aber es gab während des Starts im normalen Betriebsverlauf rund 200 Ausnahmen
Mein Punkt: Wenn Sie Ausnahmen für normale Situationen verwenden, wie finden Sie ungewöhnliche (dh außergewöhnliche ) Situationen?
Natürlich gibt es andere starke Gründe, Ausnahmen nicht zu häufig zu verwenden, insbesondere in Bezug auf die Leistung
quelle
Ausnahmen sind grundsätzlich nicht lokale
goto
Aussagen mit allen Konsequenzen der letzteren. Die Verwendung von Ausnahmen für die Flusskontrolle verstößt gegen das Prinzip des geringsten Erstaunens . Machen Sie Programme schwer lesbar (denken Sie daran, dass Programme zuerst für Programmierer geschrieben werden).Darüber hinaus erwarten Compiler-Anbieter dies nicht. Sie erwarten, dass Ausnahmen selten ausgelöst werden, und lassen den
throw
Code normalerweise recht ineffizient sein. Das Auslösen von Ausnahmen ist eine der teuersten Operationen in .NET.Einige Sprachen (insbesondere Python) verwenden jedoch Ausnahmen als Flusssteuerungskonstrukte. Beispielsweise lösen Iteratoren eine
StopIteration
Ausnahme aus, wenn keine weiteren Elemente vorhanden sind. Sogar Standardsprachenkonstrukte (wiefor
) stützen sich darauf.quelle
goto
natürlich interagieren Ausnahmen korrekt mit Ihrem Aufrufstapel und dem lexikalischen Gültigkeitsbereich und lassen den Stapel oder die Bereiche nicht durcheinander.Meine Faustregel lautet:
Das Problem, das ich mit Ausnahmen sehe, ist aus rein syntaktischer Sicht (ich bin mir ziemlich sicher, dass der Leistungsaufwand minimal ist). Ich mag keine Try-Blocks überall.
Nehmen Sie dieses Beispiel:
Ein anderes Beispiel könnte sein, wenn Sie einem Handle mithilfe einer Factory etwas zuweisen müssen und diese Factory eine Ausnahme auslösen könnte:
Ich persönlich denke, Sie sollten Ausnahmen für seltene Fehlerzustände (nicht genügend Speicher usw.) beibehalten und stattdessen Rückgabewerte (Werteklassen, Strukturen oder Aufzählungen) verwenden, um Ihre Fehlerprüfung durchzuführen.
Hoffe ich habe deine Frage richtig verstanden :)
quelle
Eine erste Reaktion auf viele Antworten:
Natürlich! Aber ein Wenn ist einfach nicht immer klarer.
Es sollte nicht erstaunlich sein, zB: dividieren (1 / x) Fang (DivisionByZero) ist klarer als jeder andere, wenn für mich (bei Conrad und anderen). Die Tatsache, dass diese Art der Programmierung nicht erwartet wird, ist rein konventionell und in der Tat immer noch relevant. Vielleicht wäre in meinem Beispiel ein Wenn klarer.
Aber DivisionByZero und FileNotFound sind klarer als wenn.
Natürlich, wenn es weniger performant ist und eine Zillion Zeit pro Sekunde benötigt, sollten Sie es natürlich vermeiden, aber ich habe immer noch keinen guten Grund gelesen, das Gesamtdesign zu vermeiden.
Was das Prinzip des geringsten Erstaunens angeht: Hier besteht die Gefahr von Zirkelschluss: Angenommen, eine ganze Community verwendet ein schlechtes Design, dieses Design wird erwartet! Daher kann das Prinzip kein Gral sein und sollte sorgfältig überlegt werden.
In vielen Reaktionen etw. so scheint durch. Fang sie einfach, nein? Ihre Methode sollte klar und gut dokumentiert sein und den Vertrag erfüllen. Ich verstehe diese Frage nicht, die ich zugeben muss.
Debuggen aller Ausnahmen: Das Gleiche gilt manchmal nur, weil das Design, keine Ausnahmen zu verwenden, üblich ist. Meine Frage war: Warum ist das überhaupt üblich?
quelle
x
bevor Sie anrufen1/x
? 2) Wickeln Sie jede Divisionsoperation in einen Try-Catch-Block, um sie zu fangenDivideByZeroException
? 3) Welche Logik setzen Sie in den Catch-Block, um sich zu erholenDivideByZeroException
?openConfigFile();
kann von einer abgefangenen FileNotFound gefolgt werden, wobei die{ createDefaultConfigFile(); setFirstAppRun(); }
FileNotFound-Ausnahme ordnungsgemäß behandelt wird; Kein Absturz, machen wir die Erfahrung des Endbenutzers besser, nicht schlechter. Sie können sagen "Aber was ist, wenn dies nicht wirklich der erste Lauf war und sie das jedes Mal bekommen?" Zumindest läuft die App jedes Mal und stürzt nicht bei jedem Start ab! Auf einem 1-zu-10 "das ist schrecklich": "x
bevor Sie anrufen1/x
, da dies normalerweise in Ordnung ist. Der Ausnahmefall ist der Fall, in dem es nicht in Ordnung ist. Wir sprechen hier nicht vonx
erderschütterndem Material , aber zum Beispiel würde bei einer zufälligen Ganzzahl, die zufällig angegeben wird , nur 1 von 4294967296 die Division nicht durchführen. Das ist außergewöhnlich und Ausnahmen sind ein guter Weg, um damit umzugehen. Sie könnten jedoch Ausnahmen verwenden, um das Äquivalent einerswitch
Anweisung zu implementieren , aber es wäre ziemlich dumm.Vor Ausnahmen gab es in C
setjmp
undlongjmp
das konnte verwendet werden, um ein ähnliches Abrollen des Stapelrahmens zu erreichen.Dann wurde dem gleichen Konstrukt ein Name gegeben: "Ausnahme". Und die meisten Antworten stützen sich auf die Bedeutung dieses Namens, um über seine Verwendung zu streiten, und behaupten, dass Ausnahmen unter außergewöhnlichen Bedingungen verwendet werden sollen. Das war im Original nie die Absicht
longjmp
. Es gab nur Situationen, in denen Sie den Kontrollfluss über viele Stapelrahmen hinweg unterbrechen mussten.Ausnahmen sind etwas allgemeiner, da Sie sie auch innerhalb desselben Stapelrahmens verwenden können. Dies wirft Analogien auf
goto
, von denen ich glaube, dass sie falsch sind. Gotos sind ein eng gekoppeltes Paar (und so sindsetjmp
undlongjmp
). Ausnahmen folgen einem lose gekoppelten Publish / Subscribe, das viel sauberer ist! Daher ist die Verwendung innerhalb desselben Stapelrahmens kaum dasselbe wie die Verwendung vongoto
s.Die dritte Quelle der Verwirrung betrifft, ob es sich um aktivierte oder nicht aktivierte Ausnahmen handelt. Natürlich scheinen ungeprüfte Ausnahmen besonders schrecklich für den Kontrollfluss und vielleicht viele andere Dinge zu sein.
Überprüfte Ausnahmen eignen sich jedoch hervorragend für den Kontrollfluss, sobald Sie alle viktorianischen Probleme überwunden haben und ein wenig leben.
Meine Lieblingsverwendung ist eine Sequenz
throw new Success()
in einem langen Codefragment, die eine Sache nach der anderen versucht, bis sie findet, wonach sie sucht. Jedes Ding - jedes Stück Logik - kann eine willkürliche Verschachtelung haben, so dassbreak
es auch keine Art von Bedingungstests gibt. Dasif-else
Muster ist spröde. Wenn ich eine herausschneideelse
Syntax oder auf andere Weise durcheinander bringe, liegt ein haariger Fehler vor.Die Verwendung von
throw new Success()
linearisiert den Codefluss. Ich verwende lokal definierteSuccess
Klassen - natürlich aktiviert - damit der Code nicht kompiliert wird, wenn ich vergesse, ihn abzufangen. Und ich fange keine anderen MethodenSuccess
.Manchmal prüft mein Code nach einer Sache nach der anderen und ist nur dann erfolgreich, wenn alles in Ordnung ist. In diesem Fall habe ich eine ähnliche Linearisierung mit
throw new Failure()
.Die Verwendung einer separaten Funktion beeinträchtigt den natürlichen Grad der Unterteilung. Die
return
Lösung ist also nicht optimal. Aus kognitiven Gründen bevorzuge ich ein oder zwei Seiten Code an einem Ort. Ich glaube nicht an ultrafeinen Code.Was JVMs oder Compiler tun, ist für mich weniger relevant, es sei denn, es gibt einen Hotspot. Ich kann nicht glauben, dass es einen fundamentalen Grund für Compiler gibt, lokal ausgelöste und abgefangene Ausnahmen nicht zu erkennen und sie einfach als sehr effizient
goto
auf der Ebene des Maschinencodes zu behandeln.In Bezug auf die funktionsübergreifende Verwendung für den Kontrollfluss - dh für häufige und nicht für außergewöhnliche Fälle - kann ich nicht erkennen, wie sie weniger effizient sind als mehrere Unterbrechungen, Bedingungstests und die Rückkehr zum Durchlaufen von drei Stapelrahmen im Gegensatz zur einfachen Wiederherstellung der Stapelzeiger.
Ich persönlich verwende das Muster nicht für Stapelrahmen und ich kann sehen, wie es Design-Raffinesse erfordern würde, um dies elegant zu tun. Aber sparsam eingesetzt sollte es in Ordnung sein.
In Bezug auf überraschende jungfräuliche Programmierer ist dies kein zwingender Grund. Wenn Sie sie sanft in die Praxis einführen, werden sie lernen, sie zu lieben. Ich erinnere mich, dass C ++ C-Programmierer überrascht und erschreckt hat.
quelle
return
Muster-Alternative würde zwei Funktionen für jede solche Funktion erfordern. Eine äußere, um die Servlet-Antwort oder andere solche Aktionen vorzubereiten, und eine innere, um die Berechnung durchzuführen. PS: Ein englischer Professor würde wahrscheinlich vorschlagen, dass ich im letzten Absatz "erstaunlich" statt "überraschend" verwende :-)Die Standardantwort lautet, dass Ausnahmen nicht regelmäßig sind und in Ausnahmefällen verwendet werden sollten.
Ein für mich wichtiger Grund ist, dass ich beim Lesen einer
try-catch
Kontrollstruktur in einer von mir verwalteten oder debuggenden Software versuche herauszufinden, warum der ursprüngliche Codierer eine Ausnahmebehandlung anstelle einerif-else
Struktur verwendet hat. Und ich erwarte eine gute Antwort.Denken Sie daran, dass Sie Code nicht nur für den Computer, sondern auch für andere Codierer schreiben. Einem Ausnahmebehandler ist eine Semantik zugeordnet, die Sie nicht wegwerfen können, nur weil es der Maschine nichts ausmacht.
quelle
Wie wäre es mit Leistung? Während des Lasttests einer .NET-Webanwendung haben wir 100 simulierte Benutzer pro Webserver erreicht, bis wir eine häufig auftretende Ausnahme behoben haben und diese Anzahl auf 500 Benutzer gestiegen ist.
quelle
Josh Bloch beschäftigt sich ausführlich mit diesem Thema in Effective Java. Seine Vorschläge sind aufschlussreich und sollten auch für .Net gelten (mit Ausnahme der Details).
Insbesondere sollten Ausnahmen für außergewöhnliche Umstände verwendet werden. Die Gründe hierfür liegen hauptsächlich in der Benutzerfreundlichkeit. Damit eine bestimmte Methode maximal verwendbar ist, sollten ihre Eingabe- und Ausgabebedingungen maximal eingeschränkt sein.
Beispielsweise ist die zweite Methode einfacher zu verwenden als die erste:
In beiden Fällen müssen Sie überprüfen, ob der Anrufer Ihre API ordnungsgemäß verwendet. Aber im zweiten Fall benötigen Sie es (implizit). Die weichen Ausnahmen werden weiterhin ausgelöst, wenn der Benutzer das Javadoc nicht gelesen hat, aber:
Der bodennahe Punkt ist, dass Ausnahmen nicht sollten als Rückkehrcodes verwendet werden sollten, hauptsächlich weil Sie nicht nur IHRE API, sondern auch die API des Anrufers kompliziert haben.
Das Richtige zu tun ist natürlich mit Kosten verbunden. Die Kosten sind, dass jeder verstehen muss, dass er die Dokumentation lesen und befolgen muss. Hoffentlich ist das sowieso der Fall.
quelle
Ich denke, dass Sie Ausnahmen für die Flusskontrolle verwenden können. Es gibt jedoch eine Kehrseite dieser Technik. Das Erstellen von Ausnahmen ist eine kostspielige Sache, da sie einen Stack-Trace erstellen müssen. Wenn Sie also Ausnahmen häufiger verwenden möchten, als nur eine Ausnahmesituation zu signalisieren, müssen Sie sicherstellen, dass das Erstellen der Stapelspuren Ihre Leistung nicht negativ beeinflusst.
Der beste Weg, um die Kosten für das Erstellen von Ausnahmen zu senken, besteht darin, die fillInStackTrace () -Methode wie folgt zu überschreiben:
In einer solchen Ausnahme werden keine Stacktraces ausgefüllt.
quelle
Ich sehe nicht wirklich, wie Sie den Programmfluss in dem von Ihnen zitierten Code steuern. Neben der ArgumentOutOfRange-Ausnahme wird keine weitere Ausnahme angezeigt. (Ihre zweite Fangklausel wird also niemals getroffen). Sie verwenden lediglich einen äußerst kostspieligen Wurf, um eine if-Anweisung nachzuahmen.
Außerdem führen Sie nicht die unheimlicheren Operationen aus, bei denen Sie nur eine Ausnahme auslösen, damit sie an einer anderen Stelle abgefangen wird, um die Flusskontrolle durchzuführen. Sie bearbeiten tatsächlich einen Ausnahmefall.
quelle
Hier sind die Best Practices, die ich in meinem Blogbeitrag beschrieben habe :
quelle
Da der Code schwer zu lesen ist, können Probleme beim Debuggen auftreten. Sie werden nach langer Zeit neue Fehler beim Beheben von Fehlern einführen. Er ist in Bezug auf Ressourcen und Zeit teurer und nervt Sie, wenn Sie Ihren Code und debuggen der Debugger stoppt beim Auftreten jeder Ausnahme;)
quelle
Abgesehen von den genannten Gründen besteht ein Grund, keine Ausnahmen für die Flusskontrolle zu verwenden, darin, dass dies den Debugging-Prozess erheblich erschweren kann.
Wenn ich beispielsweise versuche, einen Fehler in VS aufzuspüren, aktiviere ich normalerweise "Alle Ausnahmen unterbrechen". Wenn Sie Ausnahmen für die Flusskontrolle verwenden, werde ich den Debugger regelmäßig einschalten und diese nicht außergewöhnlichen Ausnahmen so lange ignorieren müssen, bis ich zum eigentlichen Problem komme. Dies wird wahrscheinlich jemanden verrückt machen !!
quelle
Nehmen wir an, Sie haben eine Methode, die einige Berechnungen durchführt. Es gibt viele Eingabeparameter, die validiert werden müssen, um eine Zahl größer als 0 zurückzugeben.
Die Verwendung von Rückgabewerten zur Signalisierung des Validierungsfehlers ist einfach: Wenn die Methode eine Zahl kleiner als 0 zurückgibt, ist ein Fehler aufgetreten. Wie kann man dann feststellen, welcher Parameter nicht validiert wurde?
Ich erinnere mich an meine C-Tage, als viele Funktionen folgende Fehlercodes zurückgaben:
etc.
Ist es wirklich weniger lesbar als eine Ausnahme zu werfen und zu fangen?
quelle
Sie können eine Hammerklaue verwenden, um eine Schraube zu drehen, genauso wie Sie Ausnahmen für den Kontrollfluss verwenden können. Dies bedeutet nicht, dass es sich um die beabsichtigte Verwendung der Funktion handelt. Die
if
Anweisung drückt Bedingungen aus, deren beabsichtigte Verwendung die Steuerung des Flusses ist.Wenn Sie eine Funktion unbeabsichtigt verwenden, während Sie sich dafür entscheiden, die für diesen Zweck vorgesehene Funktion nicht zu verwenden, fallen Kosten an. In diesem Fall leiden Klarheit und Leistung unter keinem wirklichen Mehrwert. Was bringt Ihnen die Verwendung von Ausnahmen gegenüber der allgemein akzeptierten
if
Aussage?Anders gesagt: Nur weil du kannst, heißt das nicht, dass du es solltest .
quelle
if
für den normalen Gebrauch oder die Verwendung von Ausführungen nicht beabsichtigt sind, weil dies nicht beabsichtigt ist (Zirkelargument)?public class ExitHelper{ public static void cleanExit() { cleanup(); System.exit(1); } }
Rufen Sie das einfach auf, anstatt zu werfen:ExitHelper.cleanExit();
Wenn Ihr Argument stichhaltig wäre, wäre dies der bevorzugte Ansatz und es gäbe keine Ausnahmen. Sie sagen im Grunde: "Der einzige Grund für Ausnahmen ist ein anderer Absturz."if
.Wie andere bereits zahlreich erwähnt haben, verbietet das Prinzip des geringsten Erstaunens , dass Sie Ausnahmen übermäßig nur für Kontrollflusszwecke verwenden. Andererseits ist keine Regel zu 100% korrekt, und es gibt immer Fälle, in denen eine Ausnahme "genau das richtige Werkzeug" ist -
goto
übrigens ähnlich wie sie selbst, die in Form vonbreak
undcontinue
in Sprachen wie Java geliefert wird sind oft der perfekte Weg, um aus stark verschachtelten Schleifen zu springen, die nicht immer vermeidbar sind.Der folgende Blog-Beitrag erklärt einen ziemlich komplexen, aber auch ziemlich interessanten Anwendungsfall für einen Nicht-Lokalen
ControlFlowException
:Es wird erklärt, wie innerhalb von jOOQ (einer SQL-Abstraktionsbibliothek für Java) solche Ausnahmen gelegentlich verwendet werden, um den SQL-Rendering-Prozess frühzeitig abzubrechen, wenn eine "seltene" Bedingung erfüllt ist.
Beispiele für solche Bedingungen sind:
Es sind zu viele Bindungswerte aufgetreten. Einige Datenbanken unterstützen keine beliebige Anzahl von Bindungswerten in ihren SQL-Anweisungen (SQLite: 999, Ingres 10.1.0: 1024, Sybase ASE 15.5: 2000, SQL Server 2008: 2100). In diesen Fällen bricht jOOQ die SQL-Rendering-Phase ab und rendert die SQL-Anweisung mit eingebetteten Bindungswerten neu. Beispiel:
Wenn wir die Bindungswerte explizit aus dem Abfrage-AST extrahieren, um sie jedes Mal zu zählen, würden wir wertvolle CPU-Zyklen für die 99,9% der Abfragen verschwenden, die nicht unter diesem Problem leiden.
Einige Logikfunktionen sind nur indirekt über eine API verfügbar, die nur "teilweise" ausgeführt werden soll. Die
UpdatableRecord.store()
Methode generiert abhängig von den internen Flags eine AnweisungINSERT
oder . Von außen wissen wir nicht, in welcher Art von Logik enthalten ist (z. B. optimistisches Sperren, Behandlung von Ereignis-Listenern usw.), daher möchten wir diese Logik nicht wiederholen, wenn wir mehrere Datensätze in einer Batch-Anweisung speichern. wo wir nur die SQL-Anweisung generieren möchten, nicht tatsächlich ausführen. Beispiel:UPDATE
Record
store()
store()
Wenn wir die
store()
Logik in eine "wiederverwendbare" API ausgelagert hätten, die so angepasst werden kann, dass die SQL optional nicht ausgeführt wird, würden wir versuchen, eine ziemlich schwer zu wartende, kaum wiederverwendbare API zu erstellen.Fazit
Im Wesentlichen entspricht unsere Verwendung dieser nicht lokalen
goto
s genau dem, was [Mason Wheeler] [5] in seiner Antwort gesagt hat:Beide Verwendungen von
ControlFlowExceptions
waren im Vergleich zu ihren Alternativen recht einfach zu implementieren, sodass wir eine breite Palette von Logik wiederverwenden konnten, ohne sie aus den relevanten Interna heraus zu überarbeiten.Das Gefühl, dass dies für zukünftige Betreuer eine kleine Überraschung ist, bleibt jedoch bestehen. Der Code fühlt sich ziemlich empfindlich an, und obwohl dies in diesem Fall die richtige Wahl war, würden wir es immer vorziehen, keine Ausnahmen für den lokalen Kontrollfluss zu verwenden, bei denen es einfach ist, die Verwendung einer normalen Verzweigung zu vermeiden
if - else
.quelle
Normalerweise ist an sich nichts falsch daran, eine Ausnahme auf niedrigem Niveau zu behandeln. Eine Ausnahme ist eine gültige Nachricht, die viele Details darüber enthält, warum eine Operation nicht ausgeführt werden kann. Und wenn Sie damit umgehen können, sollten Sie es tun.
Wenn Sie wissen, dass es eine hohe Ausfallwahrscheinlichkeit gibt, auf die Sie prüfen können, sollten Sie im Allgemeinen die Prüfung durchführen ... dh wenn (obj! = Null) obj.method ()
In Ihrem Fall bin ich mit der C # -Bibliothek nicht vertraut genug, um zu wissen, ob die Datums- und Uhrzeitzeit eine einfache Möglichkeit bietet, zu überprüfen, ob ein Zeitstempel außerhalb der Grenzen liegt. Wenn dies der Fall ist, rufen Sie einfach if (.isvalid (ts)) auf, andernfalls ist Ihr Code grundsätzlich in Ordnung.
Im Grunde kommt es also darauf an, auf welche Weise sauberer Code erstellt wird ... wenn die Operation zum Schutz vor einer erwarteten Ausnahme komplexer ist als nur die Behandlung der Ausnahme; Dann haben Sie meine Erlaubnis, die Ausnahme zu behandeln, anstatt überall komplexe Wachen zu erstellen.
quelle
Wenn Sie Ausnahmebehandlungsroutinen für den Kontrollfluss verwenden, sind Sie zu allgemein und faul. Wie bereits erwähnt, wissen Sie, dass etwas passiert ist, wenn Sie die Verarbeitung im Handler übernehmen, aber was genau? Im Wesentlichen verwenden Sie die Ausnahme für eine else-Anweisung, wenn Sie sie für den Kontrollfluss verwenden.
Wenn Sie nicht wissen, welcher mögliche Status auftreten kann, können Sie einen Ausnahmebehandler für unerwartete Status verwenden, z. B. wenn Sie eine Bibliothek eines Drittanbieters verwenden müssen oder wenn Sie alles in der Benutzeroberfläche abfangen müssen, um einen netten Fehler anzuzeigen Nachricht und protokollieren Sie die Ausnahme.
Wenn Sie jedoch wissen, was möglicherweise schief geht, und keine if-Anweisung oder etwas anderes eingeben, um dies zu überprüfen, sind Sie nur faul. Es ist faul, dem Ausnahmehandler zu erlauben, das Allheilmittel für Dinge zu sein, von denen Sie wissen, dass sie passieren könnten, und es wird Sie später verfolgen, da Sie versuchen werden, eine Situation in Ihrem Ausnahmehandler aufgrund einer möglicherweise falschen Annahme zu beheben.
Wenn Sie Logik in Ihren Ausnahmebehandler einfügen, um festzustellen, was genau passiert ist, wären Sie ziemlich dumm, wenn Sie diese Logik nicht in den try-Block einfügen würden.
Ausnahmebehandlungsroutinen sind der letzte Ausweg, wenn Ihnen die Ideen / Möglichkeiten ausgehen, um zu verhindern, dass etwas schief geht, oder wenn Dinge außerhalb Ihrer Kontrollmöglichkeiten liegen. Der Server ist ausgefallen und es tritt eine Zeitüberschreitung auf, und Sie können nicht verhindern, dass diese Ausnahme ausgelöst wird.
Wenn alle Überprüfungen im Voraus durchgeführt werden, zeigt dies, was Sie wissen oder erwarten, und macht es explizit. Der Code sollte in seiner Absicht klar sein. Was würdest du lieber lesen?
quelle
Vielleicht möchten Sie einen Blick auf das Zustandssystem von Common Lisp werfen, das eine Art Verallgemeinerung von Ausnahmen darstellt, die richtig gemacht wurden. Da Sie den Stapel auf kontrollierte Weise abwickeln können oder nicht, erhalten Sie auch "Neustarts", die äußerst praktisch sind.
Dies hat nicht viel mit Best Practices in anderen Sprachen zu tun, zeigt Ihnen jedoch, was mit einigen Designgedanken in (ungefähr) der Richtung, in die Sie denken, getan werden kann.
Natürlich gibt es immer noch Leistungsüberlegungen, wenn Sie wie ein Jojo auf und ab hüpfen, aber es ist eine viel allgemeinere Idee als der Ansatz "Oh Mist, lass uns auf Kaution gehen", den die meisten Catch / Throw-Ausnahmesysteme verkörpern.
quelle
Ich glaube nicht, dass etwas falsch daran ist, Ausnahmen für die Flusskontrolle zu verwenden. Ausnahmen ähneln Fortsetzungen und in statisch typisierten Sprachen sind Ausnahmen leistungsfähiger als Fortsetzungen. Wenn Sie also Fortsetzungen benötigen, Ihre Sprache diese jedoch nicht hat, können Sie sie mit Ausnahmen implementieren.
Nun, wenn Sie Fortsetzungen benötigen und Ihre Sprache diese nicht hat, haben Sie die falsche Sprache gewählt und sollten lieber eine andere verwenden. Aber manchmal haben Sie keine Wahl: Die clientseitige Webprogrammierung ist das beste Beispiel - es gibt einfach keine Möglichkeit, an JavaScript vorbei zu kommen.
Ein Beispiel: Microsoft Volta ist ein Projekt, mit dem Webanwendungen in unkompliziertem .NET geschrieben werden können und mit dem das Framework herausfinden soll, welche Bits wo ausgeführt werden müssen. Eine Folge davon ist, dass Volta in der Lage sein muss, CIL zu JavaScript zu kompilieren, damit Sie Code auf dem Client ausführen können. Es gibt jedoch ein Problem: .NET verfügt über Multithreading, JavaScript nicht. Daher implementiert Volta Fortsetzungen in JavaScript mithilfe von JavaScript-Ausnahmen und anschließend .NET-Threads mithilfe dieser Fortsetzungen. Auf diese Weise können Volta-Anwendungen, die Threads verwenden, so kompiliert werden, dass sie in einem unveränderten Browser ausgeführt werden - Silverlight ist nicht erforderlich.
quelle
Ein ästhetischer Grund:
Ein Versuch ist immer mit einem Haken verbunden, während ein Wenn nicht mit einem anderen verbunden sein muss.
Mit try / catch wird es viel ausführlicher.
Das sind 6 Codezeilen zu viele.
quelle
Sie werden jedoch nicht immer wissen, was in den von Ihnen aufgerufenen Methoden passiert. Sie wissen nicht genau, wo die Ausnahme ausgelöst wurde. Ohne das Ausnahmeobjekt genauer zu untersuchen ....
quelle
Ich habe das Gefühl, dass an Ihrem Beispiel nichts falsch ist. Im Gegenteil, es wäre eine Sünde, die von der aufgerufenen Funktion ausgelöste Ausnahme zu ignorieren.
In der JVM ist das Auslösen einer Ausnahme nicht so teuer. Sie erstellen die Ausnahme nur mit der neuen xyzException (...), da letztere einen Stack-Walk umfasst. Wenn Sie also einige Ausnahmen im Voraus erstellt haben, können Sie diese viele Male ohne Kosten auslösen. Auf diese Weise können Sie natürlich keine Daten mit der Ausnahme weitergeben, aber ich denke, das ist sowieso eine schlechte Sache.
quelle
Es gibt einige allgemeine Mechanismen, über die eine Sprache das Beenden einer Methode ermöglichen kann, ohne einen Wert zurückzugeben und zum nächsten "catch" -Block abzuwickeln:
Lassen Sie die Methode den Stapelrahmen untersuchen, um die Aufrufstelle zu bestimmen, und verwenden Sie die Metadaten für die Aufrufstelle, um entweder Informationen zu einem
try
Block innerhalb der aufrufenden Methode oder den Ort zu finden, an dem die aufrufende Methode die Adresse ihres Aufrufers gespeichert hat. Überprüfen Sie in letzterem Fall die Metadaten, die der Anrufer des Anrufers auf dieselbe Weise wie der unmittelbare Anrufer ermittelt, und wiederholen Sie diese, bis eintry
Block gefunden wird oder der Stapel leer ist. Dieser Ansatz fügt dem Fall ohne Ausnahme nur sehr wenig Overhead hinzu (er schließt einige Optimierungen aus), ist jedoch teuer, wenn eine Ausnahme auftritt.Lassen Sie die Methode ein "verstecktes" Flag zurückgeben, das eine normale Rückgabe von einer Ausnahme unterscheidet, und lassen Sie den Aufrufer dieses Flag überprüfen und zu einer "Ausnahme" -Routine verzweigen, wenn es gesetzt ist. Diese Routine fügt dem Fall ohne Ausnahme 1-2 Anweisungen hinzu, aber relativ wenig Aufwand, wenn eine Ausnahme auftritt.
Lassen Sie den Anrufer Informationen oder Code zur Ausnahmebehandlung an einer festen Adresse relativ zur gestapelten Rücksprungadresse platzieren. Beispielsweise könnte man mit dem ARM anstelle der Anweisung "BL-Subroutine" die folgende Sequenz verwenden:
Um normal zu beenden, würde das Unterprogramm einfach tun
bx lr
oderpop {pc}
; Im Falle eines abnormalen Austritts würde das Unterprogramm entweder 4 von LR subtrahieren, bevor die Rückgabe durchgeführt oder verwendet wirdsub lr,#4,pc
(abhängig von der ARM-Variation, dem Ausführungsmodus usw.). Dieser Ansatz funktioniert sehr schlecht, wenn der Anrufer nicht dafür ausgelegt ist.Eine Sprache oder ein Framework, das aktivierte Ausnahmen verwendet, kann davon profitieren, dass diese mit einem Mechanismus wie oben Nr. 2 oder Nr. 3 behandelt werden, während nicht aktivierte Ausnahmen mit Nr. 1 behandelt werden. Obwohl die Implementierung von geprüften Ausnahmen in Java ziemlich lästig ist, wären sie kein schlechtes Konzept, wenn es ein Mittel gäbe, mit dem eine Aufrufstelle im Wesentlichen sagen könnte: "Diese Methode wird als Auslösen von XX deklariert, aber ich erwarte es nicht Wenn dies der Fall ist, wird dies als "ungeprüfte" Ausnahme erneut ausgelöst. In einem Rahmen, in dem geprüfte Ausnahmen auf diese Weise behandelt wurden, könnten sie ein wirksames Mittel zur Flusskontrolle für Dinge wie Analysemethoden sein, die in einigen Kontexten eine haben können hohe Wahrscheinlichkeit eines Misserfolgs, aber wo ein Misserfolg grundlegend andere Informationen als der Erfolg zurückgeben sollte. I ' Ich kenne jedoch keine Frameworks, die ein solches Muster verwenden. Stattdessen besteht das üblichere Muster darin, für alle Ausnahmen den ersten Ansatz oben zu verwenden (minimale Kosten für den Fall ohne Ausnahme, aber hohe Kosten, wenn Ausnahmen ausgelöst werden).
quelle