Warum nicht Ausnahmen als regulären Kontrollfluss verwenden?

Um alle Standard-Antworten zu vermeiden, die ich gegoogelt habe, 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 gewisses Überlaufverhalten, das ich überhaupt nicht mag (zB type.MaxValue + type.SmallestValue == type.MinValue zum Beispiel: int.MaxValue + 1 == int.MinValue ).

Aber DateTime meiner bösartigen Natur werde ich diese Verletzung etwas beleidigen, indem ich dieses Verhalten auf einen überschriebenen DateTime Typ DateTime . (Ich weiß, DateTime ist in .NET versiegelt, aber für dieses Beispiel verwende ich eine Pseudo-Sprache, die genau wie C # ist, mit Ausnahme der Tatsache, dass DateTime nicht versiegelt ist).

Die überschriebene Add Methode:

 ///  /// Increments this date with a timespan, but loops when /// the maximum value for datetime is exceeded. ///  /// The timespan to (try to) add /// The Date, incremented with the given timespan. /// If DateTime.MaxValue is exceeded, the sum wil 'overflow' and /// continue from DateTime.MinValue. ///  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 If dies genauso einfach lösen, aber die Tatsache bleibt, dass ich einfach nicht verstehe, warum Sie keine Ausnahmen verwenden können (logisch, das heißt, dass ich, wenn Performance ein Problem ist, in bestimmten Fällen Ausnahmen vermeiden sollten ).

Ich denke, dass sie in vielen Fällen klarer sind als wenn-Strukturen und brechen keinen Vertrag, den die Methode macht.

Meiner Meinung nach ist die Reaktion “Niemals für regelmäßigen Programmablauf”, die jeder zu haben scheint, nicht so gut unterbaut, wie die Stärke dieser Reaktion rechtfertigen kann.

Oder irre ich mich?

Ich habe andere Beiträge gelesen, die sich mit allen möglichen Sonderfällen befassen, aber mein Punkt ist, dass nichts falsch daran ist, wenn Sie beide sind:

  1. klar
  2. Ehre den Vertrag deiner Methode

Erschieß mich.

Solutions Collecting From Web of "Warum nicht Ausnahmen als regulären Kontrollfluss verwenden?"

Haben Sie jemals versucht, ein Programm zu debuggen, das im normalen Betriebsablauf fünf Ausnahmen pro Sekunde austriggers?

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 einem völlig anderen Ort zerstören.

Ich wünschte, ich hätte das Programm einfach starten und auf Ausnahmen warten können, aber es gab rund 200 Ausnahmen während der Inbetriebnahme im normalen Betriebsablauf

Mein Punkt: Wenn Sie Ausnahmen für normale Situationen verwenden, wie finden Sie ungewöhnliche (dh Ausnahme ) Situationen?

Natürlich gibt es andere gute Gründe, Ausnahmen nicht zu sehr zu nutzen, insbesondere nicht in Bezug auf die performance

Ausnahmen sind grundsätzlich nicht-lokale goto statementen mit allen Konsequenzen der letzteren. Die Verwendung von Ausnahmen für die Ablaufsteuerung verletzt das Prinzip der geringsten Verwundbarkeit , macht Programme schwer lesbar (denken Sie daran, dass Programme zuerst für Programmierer geschrieben werden).

Außerdem erwarten Compilerhersteller dies nicht. Sie erwarten, dass Ausnahmen selten ausgetriggers werden, und sie lassen den Code normalerweise ziemlich ineffizient. Das Auslösen von Ausnahmen ist eine der teuersten Operationen in .NET.

Einige Sprachen (insbesondere Python) verwenden Ausnahmen als Flow-Control-Konstrukte. Zum Beispiel StopIteration Iteratoren eine StopIteration Ausnahme aus, wenn keine weiteren Elemente vorhanden sind. Sogar Standardsprachkonstrukte (wie for ) verlassen sich darauf.

Meine Faustregel ist:

  • Wenn Sie etwas tun können, um einen Fehler zu beheben, fangen Sie Ausnahmen ab
  • Wenn der Fehler sehr häufig auftritt (z. B. wenn der Benutzer versucht hat, sich mit dem falschen Kennwort anzumelden), verwenden Sie returnvalues
  • Wenn Sie nichts tun können, um sich von einem Fehler zu erholen, lassen Sie den Fehler unberücksichtigt (oder fangen Sie ihn in Ihrem Haupt-Catcher ab, um die Anwendung halbwegs herunterzufahren)

Das Problem, das ich bei Ausnahmen sehe, ist rein rechnerischer Natur (ich bin mir ziemlich sicher, dass der performancesaufwand minimal ist). Ich mag keine Testblöcke überall.

Nimm dieses Beispiel:

 try { DoSomeMethod(); //Can throw Exception1 DoSomeOtherMethod(); //Can throw Exception1 and Exception2 } catch(Exception1) { //Okay something messed up, but is it SomeMethod or SomeOtherMethod? } 

.. Ein anderes Beispiel könnte sein, wenn Sie einem Handle etwas unter Verwendung einer Factory zuweisen müssen und diese Factory eine Ausnahme auslösen könnte:

 Class1 myInstance; try { myInstance = Class1Factory.Build(); } catch(SomeException) { // Couldn't instantiate class, do something else.. } myInstance.BestMethodEver(); // Will throw a compile-time error, saying that myInstance is uninitalized, which it potentially is.. :( 

Soo, persönlich, ich denke, Sie sollten Ausnahmen für seltene Fehlerbedingungen (nicht genügend Speicher usw.) behalten und returnvalues ​​(valueclasses, structs oder enums) verwenden, um stattdessen Ihre Fehlerprüfung durchzuführen.

Hoffe ich habe deine Frage richtig verstanden 🙂

Eine erste Reaktion auf viele Antworten:

Sie schreiben für die Programmierer und das Prinzip des geringsten Erstaunens

Na sicher! Aber ein wenn, ist nicht immer klarer die ganze Zeit.

Es sollte nicht erstaunlich sein, zB: dividieren (1 / x) catch (divisionByZero) ist klarer als alles andere (für Conrad und andere). Die Tatsache, dass diese Art von Programmierung nicht erwartet wird, ist rein konventionell und in der Tat immer noch relevant. Vielleicht wäre in meinem Beispiel ein if klarer.

Aber DivisionByZero und FileNotFound sind klarer als ifs.

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, um das Gesamtdesign zu vermeiden.

Soweit das Prinzip der geringsten Verwunderung gilt: Hier besteht die Gefahr des Zirkelschlusses: Angenommen, eine ganze Gemeinschaft benutzt ein schlechtes Design, wird dieses Design erwartet! Daher kann das Prinzip kein Gral sein und sollte sorgfältig durchdacht werden.

Ausnahmen für normale Situationen, wie finden Sie ungewöhnliche (dh außergewöhnliche) Situationen?

In vielen Reaktionen etw. Akk. So glänzt es. Fang sie einfach, oder? Ihre Methode sollte klar und gut dokumentiert sein und den Vertrag erfüllen. Ich verstehe diese Frage nicht, ich muss zugeben.

Debuggen auf alle Ausnahmen: das gleiche, das ist nur manchmal getan, weil das Design keine Ausnahmen zu verwenden ist üblich. Meine Frage war: Warum ist es überhaupt üblich?

Vor Ausnahmen in C gab es setjmp und longjmp , die verwendet werden könnten, um ein ähnliches Abrollen des longjmp zu erreichen.

Dann erhielt das gleiche Konstrukt einen Namen: “Exception”. Und die meisten Antworten beruhen auf der Bedeutung dieses Namens, um über seine Verwendung zu streiten. Es ist für außergewöhnliche Bedingungen vorgesehen. Das wurde nie in der ursprünglichen longjmp reflektiert. Es gab nur Situationen, in denen Sie den Steuerungsfluss über viele Stack-Frames hinweg unterbrechen mussten.

Ausnahmen sind etwas allgemeiner, da Sie sie auch im selben Stack-Frame verwenden können. Dies führt zu Analogien mit der Meinung, dass ich falsch liege. Gotos sind ein eng gekoppeltes Paar (und so sind setjmp und longjmp ) Ausnahmen folgen einem locker gekoppelten Publish / Subscribe, das viel sauberer ist!

Die dritte Quelle der Verwirrung bezieht sich darauf, ob es sich um geprüfte oder ungeprüfte Ausnahmen handelt. Natürlich scheinen unkontrollierte Ausnahmen für den Kontrollfluss besonders schlimm zu sein.

Überprüfte Ausnahmen sind jedoch großartig für den Kontrollfluss, sobald Sie alle viktorianischen Abhängungen überwunden haben und ein wenig leben.

Meine Lieblingsverwendung ist eine Sequenz von throw new Success() in einem langen Code-Fragment, das eine Sache nach der anderen versucht, bis es findet, wonach es sucht. Jedes Ding – jedes Stück Logik – kann arbritrary Verschachtelung haben, also break ‘s sind als auch irgendeine Art Bedingungstests aus. Das if-else Muster ist spröde. Wenn ich ein else editiere oder die Syntax auf andere Weise durcheinander bringe, dann gibt es einen haarigen Bug.

Mit throw new Success() der Code-Fluss linearisiert . Ich verwende lokal definierte Success classn – natürlich überprüft – damit ich den Code nicht kompiliere, wenn ich ihn vergesse. Und ich fange nicht die Success anderen Methode.

Manchmal sucht mein Code nach dem anderen und funktioniert nur, wenn alles in Ordnung ist. In diesem Fall habe ich eine ähnliche Linearisierung mit dem throw new Failure() .

Die Verwendung einer separaten function beeinträchtigt das natürliche Niveau der Kompartimentierung. Daher ist die return nicht optimal. Ich bevorzuge aus kognitiven Gründen eine oder zwei Seiten Code an einem Ort. Ich glaube nicht an ultra-fein geteilten 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 geworfene und gefangene Exceptions nicht zu erkennen und sie einfach als sehr effiziente goto s auf dem Maschinencode-Level zu behandeln.

Was die Verwendung der functionen für den Kontrollfluss betrifft – also eher für häufige als für außergewöhnliche Fälle -, kann ich nicht sehen, wie sie weniger effizient sind als Multiple-Break- und Condition-Tests der Stapelzeiger.

Ich persönlich benutze das Muster nicht über Stapelrahmen hinweg, und ich kann sehen, wie es elegant und elegant aussehen würde. Aber sparsam verwendet sollte es in Ordnung sein.

Schließlich, bezüglich der überraschenden unberührten Programmierer, ist es kein zwingender Grund. Wenn Sie sie sanft in die Praxis einführen, werden sie lernen, sie zu lieben. Ich erinnere mich, dass C ++ früher C-Programmierer überrascht und erschreckt hat.

Der Standard ist, dass Ausnahmen nicht regelmäßig sind und in Ausnahmefällen verwendet werden sollten.

Ein Grund, der mir wichtig ist, ist, dass wenn ich eine try-catch Kontrollstruktur in einer Software lese, die ich pflege oder debugge, versuche ich herauszufinden, warum der ursprüngliche Coder eine Ausnahmebehandlung anstelle einer if-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 Programmierer schreiben. Es gibt eine Semantik, die einem Exception-Handler zugeordnet ist, den Sie nicht wegcasting können, nur weil es der Maschine nichts ausmacht.

Wie wäre es mit performance? Beim Laden einer .NET-Webanwendung haben wir 100 simulierte Benutzer pro Webserver erreicht, bis wir eine häufig auftretende Ausnahme behoben haben und diese Zahl auf 500 erhöht wurde.

Josh Bloch beschäftigt sich in Effective Java ausführlich mit diesem Thema. Seine Vorschläge sind aufschlussreich und sollten auch für .Net gelten (abgesehen von den Details).

Insbesondere sollten Ausnahmen für außergewöhnliche Umstände verwendet werden. Die Gründe dafür sind hauptsächlich Usability-bezogen. Damit eine gegebene Methode maximal verwendbar ist, sollten ihre Eingabe- und Ausgabebedingungen maximal eingeschränkt sein.

Zum Beispiel ist die zweite Methode einfacher zu verwenden als die erste:

 /** * Adds two positive numbers. * * @param addend1 greater than zero * @param addend2 greater than zero * @throws AdditionException if addend1 or addend2 is less than or equal to zero */ int addPositiveNumbers(int addend1, int addend2) throws AdditionException{ if( addend1 < = 0 ){ throw new AdditionException("addend1 is <= 0"); } else if( addend2 <= 0 ){ throw new AdditionException("addend2 is <= 0"); } return addend1 + addend2; } /** * Adds two positive numbers. * * @param addend1 greater than zero * @param addend2 greater than zero */ public int addPositiveNumbers(int addend1, int addend2) { if( addend1 <= 0 ){ throw new IllegalArgumentException("addend1 is <= 0"); } else if( addend2 <= 0 ){ throw new IllegalArgumentException("addend2 is <= 0"); } return addend1 + addend2; } 

In beiden Fällen müssen Sie sicherstellen, dass der Aufrufer Ihre API entsprechend verwendet. Aber im zweiten Fall benötigen Sie es (implizit). Die weichen Ausnahmen werden weiterhin ausgetriggers, wenn der Benutzer das Javadoc nicht gelesen hat, aber:

  1. Sie müssen es nicht dokumentieren.
  2. Sie müssen es nicht testen (abhängig davon, wie aggressiv Ihre Teststrategie ist).
  3. Sie müssen den Aufrufer nicht drei Anwendungsfälle behandeln.

Der grundlegende Punkt ist, dass Ausnahmen nicht als Rückgabecodes verwendet werden sollten, hauptsächlich weil Sie nicht nur Ihre API, sondern auch die API des Aufrufers kompliziert haben.

Das Richtige zu tun hat natürlich seinen Preis. Die Kosten sind, dass jeder verstehen muss, dass er die Dokumentation lesen und befolgen muss. Hoffentlich ist das sowieso so.

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 Angelegenheit, da sie eine Stapelverfolgung erstellen müssen. Wenn Sie also Exceptions häufiger verwenden möchten, als nur eine Ausnahmesituation zu signalisieren, müssen Sie sicherstellen, dass das Erstellen der Stack-Traces Ihre Performance nicht negativ beeinflusst.

Um die Kosten für das Erstellen von Ausnahmen zu reduzieren, sollten Sie die fillInStackTrace () -Methode wie folgt überschreiben:

 public Throwable fillInStackTrace() { return this; } 

Solch eine Ausnahme wird keine Stacktraces enthalten.

Ich sehe nicht wirklich, wie Sie den Programmablauf in dem von Ihnen angegebenen Code steuern. Außer der ArgumentOutOfRange-Ausnahme wird nie eine andere Ausnahme angezeigt. (Ihre zweite Fangklausel wird also nie getroffen). Sie verwenden nur einen extrem kostspieligen Throw, um eine if-statement nachzuahmen.

Außerdem führen Sie nicht die unheimlicheren Operationen durch, bei denen Sie einfach eine Ausnahme auslösen, nur um irgendwo anders gefangen zu werden, um die Flusskontrolle durchzuführen. Sie behandeln tatsächlich einen Ausnahmefall.

Da der Code schwer zu lesen ist, haben Sie möglicherweise Schwierigkeiten, ihn zu debuggen. Sie werden neue Fehler bei der Behebung von Fehlern nach einer langen Zeit einführen, es ist teurer in Bezug auf Ressourcen und Zeit und es ärgert Sie, wenn Sie Ihren Code debuggen der Debugger hält beim Auftreten jeder Ausnahme an;)

Abgesehen von den genannten Gründen besteht ein Grund, keine Ausnahmen für die Ablaufsteuerung zu verwenden, darin, dass der Debugging-process erheblich erschwert werden kann.

Wenn ich zum Beispiel versuche, einen Fehler in VS zu finden, werde ich normalerweise “alle Ausnahmen brechen” aktivieren. Wenn Sie Ausnahmen für die Ablaufsteuerung verwenden, bringe ich den Debugger regelmäßig ein und muss diese nicht außergewöhnlichen Ausnahmen ignorieren, bis ich zu dem wirklichen Problem komme. Das wird wahrscheinlich jemanden verrückt machen !!

Nehmen wir an, Sie haben eine Methode, die einige Berechnungen durchführt. Es gibt viele Eingabeparameter, die validiert werden müssen, und dann eine Zahl, die größer als 0 ist.

Die Verwendung von Rückgabewerten zur Signalisierung des validationserrorss ist einfach: Wenn die Methode eine Zahl kleiner als 0 zurückgegeben hat, ist ein Fehler aufgetreten. Wie kann man dann sagen, welcher Parameter nicht validiert wurde?

Ich erinnere mich an meine C Tage viele functionen zurückgegeben Fehlercodes wie folgt:

 -1 - x lesser then MinX -2 - x greater then MaxX -3 - y lesser then MinY 

etc.

Ist es wirklich weniger lesbar, eine Ausnahme zu casting und zu fangen?

Sie können eine Hammerklaue verwenden, um eine Schraube zu drehen, genau wie Sie Ausnahmen für den Kontrollfluss verwenden können. Das bedeutet nicht, dass es die beabsichtigte Verwendung des Features ist. Die if statement drückt Bedingungen aus, deren beabsichtigte Verwendung der Fluss ist.

Wenn Sie eine function auf eine nicht beabsichtigte Weise verwenden, während Sie sich entscheiden, die für diesen Zweck entwickelte function nicht zu verwenden, entstehen zusätzliche Kosten. In diesem Fall leiden Klarheit und performance ohne wirklichen Mehrwert. Was kostet Sie die Verwendung von Ausnahmen über die allgemein akzeptierte if statement?

Anders gesagt: Nur weil du kannst, heißt das nicht, dass du es tun solltest .

Hier sind die Best Practices, die ich in meinem Blogbeitrag beschrieben habe:

  • Übergeben Sie eine Ausnahme, um eine unerwartete Situation in Ihrer Software anzugeben.
  • Verwenden Sie Rückgabewerte für die Eingabeüberprüfung .
  • Wenn Sie wissen, wie Sie mit Ausnahmen umgehen, die eine Bibliothek verursacht, fangen Sie sie auf der niedrigsten möglichen Ebene ab .
  • Wenn Sie eine unerwartete Ausnahme haben, vercasting Sie den aktuellen Vorgang vollständig. Tu nicht so, als wüsstest du, wie du damit umgehen sollst .

In der Regel ist an sich nichts falsch, wenn eine Ausnahme auf einem niedrigen Niveau behandelt wird. Eine Ausnahme ist eine gültige Nachricht, die eine Vielzahl von Details zur Verfügung stellt, warum eine Operation nicht ausgeführt werden kann. Und wenn Sie damit umgehen können, sollten Sie.

Im Allgemeinen, wenn Sie wissen, dass es eine hohe Ausfallwahrscheinlichkeit gibt, nach der Sie suchen können, sollten Sie die Prüfung durchführen … ie (obj! = Null) obj.method ()

In Ihrem Fall ist mir die C # -Bibliothek zu wenig vertraut, um zu wissen, ob die Datumszeit eine einfache Möglichkeit bietet, zu überprüfen, ob ein Zeitstempel außerhalb der Grenzen liegt. Wenn dies der Fall ist, ruf einfach if (.isvalid (ts)) an, ansonsten ist dein Code grundsätzlich in Ordnung.

Also kommt es im Grunde immer darauf an, welchen Weg saubererer Code schafft … wenn die Operation, die vor einer erwarteten Ausnahme schützt, komplexer ist, als nur die Ausnahme zu behandeln; Dann hast du meine Erlaubnis, mit der Ausnahme umzugehen, anstatt überall komplexe Wachen zu erstellen.

Wenn Sie Ausnahmehandler für den Kontrollfluss verwenden, sind Sie zu allgemein und faul. Wie jemand anders erwähnt hat, wissen Sie, dass etwas passiert ist, wenn Sie mit der Verarbeitung im Handler umgehen, aber was genau? Im Wesentlichen verwenden Sie die Ausnahme für eine else-statement, wenn Sie sie für den Kontrollfluss verwenden.

Wenn Sie nicht wissen, welcher mögliche Zustand eintreten könnte, können Sie einen Ausnahme-Handler für unerwartete Zustände verwenden, zum Beispiel wenn Sie eine Bibliothek eines Drittanbieters verwenden müssen oder Sie alles auf der Benutzeroberfläche abfangen müssen, um einen schönen Fehler anzuzeigen Melden Sie die Ausnahme und protokollieren Sie sie.

Wenn Sie jedoch wissen, was schief gehen könnte, und Sie nicht eine if-statement oder etwas, um es zu überprüfen, dann sind Sie nur faul. Allowing the exception handler to be the catch-all for stuff you know could happen is lazy, and it will come back to haunt you later, because you will be trying to fix a situation in your exception handler based on a possibly false assumption.

If you put logic in your exception handler to determine what exactly happened, then you would be quite stupid for not putting that logic inside the try block.

Exception handlers are the last resort, for when you run out of ideas/ways to stop something from going wrong, or things are beyond your ability to control. Like, the server is down and times out and you can’t prevent that exception from being thrown.

Finally, having all the checks done up front shows what you know or expect will occur and makes it explicit. Code should be clear in intent. What would you rather read?

You might be interested in having a look at Common Lisp’s condition system which is a sort of generalization of exceptions done right. Because you can unwind the stack or not in a controlled way, you get “restarts” as well, which are extremely handy.

This doesn’t have anything much to do with best practices in other languages, but it shows you what can be done with some design thought in (roughly) the direction you are thinking of.

Of course there are still performance considerations if you’re bouncing up and down the stack like a yo-yo, but it’s a much more general idea than “oh crap, lets bail” kind of approach that most catch/throw exception systems embody.

I don’t think there is anything wrong with using Exceptions for flow-control. Exceptions are somewhat similar to continuations and in statically typed languages, Exceptions are more powerful than continuations, so, if you need continuations but your language doesn’t have them, you can use Exceptions to implement them.

Well, actually, if you need continuations and your language doesn’t have them, you chose the wrong language and you should rather be using a different one. But sometimes you don’t have a choice: client-side web programming is the prime example – there’s just no way to get around JavaScript.

An example: Microsoft Volta is a project to allow writing web applications in straight-forward .NET, and let the framework take care of figuring out which bits need to run where. One consequence of this is that Volta needs to be able to compile CIL to JavaScript, so that you can run code on the client. However, there is a problem: .NET has multithreading, JavaScript doesn’t. So, Volta implements continuations in JavaScript using JavaScript Exceptions, then implements .NET Threads using those continuations. That way, Volta applications that use threads can be compiled to run in an unmodified browser – no Silverlight needed.

One aesthetic reason:

A try always comes with a catch, whereas an if doesn’t have to come with an else.

 if (PerformCheckSucceeded()) DoSomething(); 

With try/catch, it becomes much more verbose.

 try { PerformCheckSucceeded(); DoSomething(); } catch { } 

That’s 6 lines of code too many.

As others have mentioned numerously, the principle of least astonishment will forbid that you use exceptions excessively for control flow only purposes. On the other hand, no rule is 100% correct, and there are always those cases where an exception is “just the right tool” – much like goto itself, by the way, which ships in the form of break and continue in languages like Java, which are often the perfect way to jump out of heavily nested loops, which aren’t always avoidable.

The following blog post explains a rather complex but also rather interesting use-case for a non-local ControlFlowException :

It explains how inside of jOOQ (a SQL abstraction library for Java) , such exceptions are occasionally used to abort the SQL rendering process early when some “rare” condition is met.

Examples of such conditions are:

  • Too many bind values are encountered. Some databases do not support arbitrary numbers of bind values in their SQL statements (SQLite: 999, Ingres 10.1.0: 1024, Sybase ASE 15.5: 2000, SQL Server 2008: 2100). In those cases, jOOQ aborts the SQL rendering phase and re-renders the SQL statement with inlined bind values. Beispiel:

     // Pseudo-code attaching a "handler" that will // abort query rendering once the maximum number // of bind values was exceeded: context.attachBindValueCounter(); String sql; try { // In most cases, this will succeed: sql = query.render(); } catch (ReRenderWithInlinedVariables e) { sql = query.renderWithInlinedBindValues(); } 

    If we explicitly extracted the bind values from the query AST to count them every time, we’d waste valuable CPU cycles for those 99.9% of the queries that don’t suffer from this problem.

  • Some logic is available only indirectly via an API that we want to execute only “partially”. The UpdatableRecord.store() method generates an INSERT or UPDATE statement, depending on the Record ‘s internal flags. From the “outside”, we don’t know what kind of logic is contained in store() (eg optimistic locking, event listener handling, etc.) so we don’t want to repeat that logic when we store several records in a batch statement, where we’d like to have store() only generate the SQL statement, not actually execute it. Beispiel:

     // Pseudo-code attaching a "handler" that will // prevent query execution and throw exceptions // instead: context.attachQueryCollector(); // Collect the SQL for every store operation for (int i = 0; i < records.length; i++) { try { records[i].store(); } // The attached handler will result in this // exception being thrown rather than actually // storing records to the database catch (QueryCollectorException e) { // The exception is thrown after the rendered // SQL statement is available queries.add(e.query()); } } 

    If we had externalised the store() logic into "re-usable" API that can be customised to optionally not execute the SQL, we'd be looking into creating a rather hard to maintain, hardly re-usable API.

Fazit

In essence, our usage of these non-local goto s is just along the lines of what [Mason Wheeler][5] said in his answer:

"I just encountered a situation that I cannot deal with properly at this point, because I don't have enough context to handle it, but the routine that called me (or something further up the call stack) ought to know how to handle it."

Both usages of ControlFlowExceptions were rather easy to implement compared to their alternatives, allowing us to reuse a wide range of logic without refactoring it out of the relevant internals.

But the feeling of this being a bit of a surprise to future maintainers remains. The code feels rather delicate and while it was the right choice in this case, we'd always prefer not to use exceptions for local control flow, where it is easy to avoid using ordinary branching through if - else .

But you won’t always know what happens in the Method/s that you call. You won’t know exactly where the exception was thrown. Without examining the exception object in greater detail….

I feel that there is nothing wrong with your example. On the contrary, it would be a sin to ignore the exception thrown by the called function.

In the JVM, throwing an exception is not that expensive, only creating the exception with new xyzException(…), because the latter involves a stack walk. So if you have some exceptions created in advance, you may throw them many times without costs. Of course, this way you can’t pass data along with the exception, but I think that is a bad thing to do anyway.

There are a few general mechanisms via which a language could allow for a method to exit without returning a value and unwind to the next “catch” block:

  • Have the method examine the stack frame to determine the call site, and use the metadata for the call site to find either information about a try block within the calling method, or the location where the calling method stored the address of its caller; in the latter situation, examine metadata for the caller’s caller to determine in the same fashion as the immediate caller, repeating until one finds a try block or the stack is empty. This approach adds very little overhead to the no-exception case (it does preclude some optimizations) but is expensive when an exception occurs.

  • Have the method return a “hidden” flag which distinguishes a normal return from an exception, and have the caller check that flag and branch to an “exception” routine if it’s set. This routine adds 1-2 instructions to the no-exception case, but relatively little overhead when an exception occurs.

  • Have the caller place exception-handling information or code at a fixed address relative to the stacked return address. For example, with the ARM, instead of using the instruction “BL subroutine”, one could use the sequence:

      adr lr,next_instr b subroutine b handle_exception next_instr: 

To exit normally, the subroutine would simply do bx lr or pop {pc} ; in case of an abnormal exit, the subroutine would either subtract 4 from LR before performing the return or use sub lr,#4,pc (depending upon the ARM variation, execution mode, etc.) This approach will malfunction very badly if the caller is not designed to accommodate it.

A language or framework which uses checked exceptions might benefit from having those handled with a mechanism like #2 or #3 above, while unchecked exceptions are handled using #1. Although the implementation of checked exceptions in Java is rather nuisancesome, they would not be a bad concept if there were a means by which a call site could say, essentially, “This method is declared as throwing XX, but I don’t expect it ever to do so; if it does, rethrow as an “unchecked” exception. In a framework where checked exceptions were handled in such fashion, they could be an effective means of flow control for things like parsing methods which in some contexts may have a high likelihood of failure, but where failure should return fundamentally different information than success. I’m unaware of any frameworks that use such a pattern, however. Instead, the more common pattern is to use the first approach above (minimal cost for the no-exception case, but high cost when exceptions are thrown) for all exceptions.