200

Back in the late 90's I worked quite a bit with a code base that used exceptions as flow control. It implemented a finite state machine to drive telephony applications. Lately I am reminded of those days because I've been doing MVC web apps.

They both have Controllers that decide where to go next and supply the data to the destination logic. User actions from the domain of an old-school telephone, like DTMF tones, became parameters to action methods, but instead of returning something like a ViewResult, they threw a StateTransitionException.

I think the main difference was that action methods were void functions. I don't remember all the things I did with this fact but I've been hesitant to even go down the road of remembering much because since that job, like 15 years ago, I never saw this in production code at any other job. I assumed this was a sign that it was a so-called anti-pattern.

Is this the case, and if so, why?

Neuron
  • 109
  • 4
Aaron Anodide
  • 5,463
  • 5
  • 28
  • 37
  • 4
    Related question: http://programmers.stackexchange.com/questions/107723 – Eric King Mar 04 '13 at 22:33
  • 39
    No. In Python, using exceptions as control flow is considered "pythonic". – user16764 Mar 05 '13 at 01:37
  • 3
    I suspect you're being ironic or bitter. I appreciate both. Just keep your damned dirty empty catches out of the JavaScript. – Erik Reppen Mar 05 '13 at 03:43
  • 6
    *If* I were to do such a thing I Java, I certainly would not throw an exception for it. I would derive from some non-Exception, non-Error, Throwable hierarchy. – Thomas Eding Aug 15 '14 at 14:58
  • 1
    Wikipedia's definition of *anti-pattern* says it's common. Given you never saw this again, and I've never seen it ever, maybe it's just bad style and not technically an anti-pattern? – Fuhrmanator Dec 29 '15 at 04:39
  • 4
    To add to the existing answers, here is a short guideline that has served me well: - Never use exceptions for "the happy path". The happy path can be both (for web) the entire request, or simply one object / method. All the other sane rules still apply, of course :) – Houen Mar 18 '16 at 06:40
  • 2
    Another related question on SO: http://stackoverflow.com/questions/729379/why-not-use-exceptions-as-regular-flow-of-control – cwap Jun 29 '16 at 05:46
  • 18
    Aren't exceptions always controlling the flow of an application? –  Jun 30 '17 at 12:46
  • 2
    That sounds a lot like what `async` `await` does, actually. Except that `async` and `await` are better for numerous reasons (somewhat less overhead, immune to overgeneralized `catch` clauses, …). – binki Mar 01 '18 at 15:22
  • 3
    C++ exceptions are more worst than `goto`. If you see a `goto hell` statement, at least you can be certain it will indeed go there. But in the case of an exception, you cannot know where it will go. In the call sequence `A()->B()->C()`, an exception thrown in C will not propagate to A if B catches it. If somewhere in C catches, it will not even propagate to B. If there are finishing codes in A, these will not be invoked. Of course, C can re-throw it. But you see now the point, unlike `goto` which is certain, exception flow is not. We even drop the honest `goto` because of its non-linear nature – daparic Apr 25 '19 at 00:27
  • 1
    I would note that on some languages/platforms creating an exception is fairly expensive. On .Net it needs to create a call stack, so is much more expensive than return codes. – JonasH Jan 10 '22 at 09:09
  • I can't give a specific answer to your question. However, to me exceptions quickly become the "tail that wags the dogs". On the other hand, one has to deal with web-connections that don't come, database connections that don't come, etc. Thus: a conundrum .... – JosephDoggie Jul 20 '22 at 21:37
  • @truthadjustr: I've always thought that goto is overly criticized so I agree to a certain extent. I used to do assembly language, and a variant of goto such as jump, branch, and their conditional variants usually arise. However: I wouldn't say you like goto in a job interview. – JosephDoggie Jul 20 '22 at 21:39
  • Python exceptions are how you say "I can't do that" - that's pretty much the only use; we don't return values by raising a tuple or anything. – tottinge Aug 22 '23 at 10:36

10 Answers10

173

The use case that exceptions were designed for is "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."

The secondary use case is "I just encountered a serious error, and right now getting out of this control flow to prevent data corruption or other damage is more important than trying to continue onward."

If you're not using exceptions for one of these two reasons, there's probably a better way to do it.

Mason Wheeler
  • 82,151
  • 24
  • 234
  • 309
  • 27
    This doesn't answer the question though. What they were *designed for* is irrelevant; the only thing that is relevant is *why* using them for control flow is bad which is a topic you didn't touch. As an example, C++ templates were designed for one thing but are perfectly fine to be used for metaprogramming, an use the designers never anticipated of. – Andreas Bonini Mar 05 '13 at 01:26
  • 6
    @Krelp: The designers never anticipated a lot of things, such as *ending up with a Turing-complete template system by accident!* C++ templates are hardly a good example to use here. – Mason Wheeler Mar 05 '13 at 02:33
  • 17
    @Krelp - C++ templates are NOT 'perfectly fine' for metaprogramming. They are a nightmare until you get them right, and then they tend toward write-only code if you aren't a template-genius. You might want to pick a better example. – Michael Kohne Mar 05 '13 at 02:39
  • Exceptions harms function composition in particular, and code brevity in general. E.g. while i were inexperienced I used an exception in a project, and it caused troubles long time, because 1) people have to remember to catch it, 2) you can't write `const myvar = theFunction` because myvar have to be created out of try-catch, thus it is no more constant. That doesn't mean I don't use it in C# as it is a mainstream there for whatever reason, but I'm trying to reduce them anyway. – Hi-Angel Apr 18 '16 at 12:26
  • 3
    @AndreasBonini What they are designed/intended for matters because that often results in compiler/framework/runtime implementation decisions aligned with the design. For example, throwing an exception can be a lot more “expensive” than a simple return because it collects information meant to help someone debugging the code such as stack traces, etc. – binki Mar 01 '18 at 15:25
  • 1
    @binki you mean it *optionally* collects this information, not all exceptions do (and when they don't, at least in Java, they perform as fast as direct returns). – john16384 Feb 22 '21 at 21:45
171

There's a detailed discussion of this on Ward's Wiki. Generally, the use of exceptions for control flow is an anti-pattern, with many notable situation - and language-specific (see for example Python) cough exceptions cough.

As a quick summary for why, generally, it's an anti-pattern:

  • Exceptions are, in essence, sophisticated GOTO statements
  • Programming with exceptions, therefore, leads to more difficult to read, and understand code
  • Most languages have existing control structures designed to solve your problems without the use of exceptions
  • Arguments for efficiency tend to be moot for modern compilers, which tend to optimize with the assumption that exceptions are not used for control flow.

Read the discussion at Ward's wiki for much more in-depth information.


See also a duplicate of this question, here

blueberryfields
  • 13,200
  • 8
  • 51
  • 87
  • 32
    Your answer makes it sound as though exceptions are bad for **all** circumstances, while the quesion is focused on exceptions as flow control. – whatsisname Mar 04 '13 at 22:46
  • 18
    @MasonWheeler The difference is that for/while loops contain their flow control changes clearly and make the code easy to read. If you see a for statement in your code, you don't have to try to figure out which file contains the end of the loop. Goto's aren't bad because some god said they were, they are bad simply because they are harder to follow than the looping constructs. Exceptions are similar, not impossible but hard enough that they can confuse things. – Bill K Mar 05 '13 at 00:02
  • 28
    @BillK, then argue that, and don't make over simplistic statements about how exceptions are gotos. – Winston Ewert Mar 05 '13 at 00:14
  • 1
    @MasonWheeler there's nothing sophisticated about for and while. Exceptions are different in that you need to make sure your `catch` block looks out for the right exception type. Also, in the case of C++ the compiler will generate extra invisible stack unwinding code that may be large and therefore a potential issue in resource constrained environments. – James Mar 05 '13 at 00:31
  • @James, how does having to specify the right exception type pose a problem? After all, you've got to specify the right condition for an `if` statement. – Winston Ewert Mar 05 '13 at 03:45
  • 3
    @WinstonEwert I really like exceptions for error handling, I think that was the first difference I noticed between Java and C... even a slapped-together java program can be made to run practically indefinitely with minimal effort. Thing is, they add complexity. Biggest problem I've seen is where you are tracing through a routine and suddenly a lower-level routine throws an exception that a higher level one silently eats--it can take DAYS to track down why your thread just "Stopped" at that point... – Bill K Mar 05 '13 at 04:29
  • 4
    (cont) Also note that Java helps you by keeping as much as possible local to a single class so that you can fully understand it as a whole. Exceptions, used as flow control, would force you to reach way outside that file, to the caller and to the things it calls to fully understand the flow for that little section of code. When coding your goal should not be to make the code work, that's your teams job. Your job is making code your team can understand.. With that goal in mind, I don't think anyone could say using exceptions for flow control is a good way to help others understand your code. – Bill K Mar 05 '13 at 04:40
  • 9
    Okay but seriously, what is up with server-side and app devs burying errors with empty catch statements in the JavaScript? It's a nasty phenomon that has cost me a lot of time and I don't know how to ask without ranting. Errors are your friend. – Erik Reppen Mar 05 '13 at 04:43
  • 2
    @BillK, certainly exceptions can be badly abused. But saying that exceptions are gotos is a horrendously over-simplistic way of expressing that argument. I figure that exceptions for flow control are OK if you catch them one level up from when they are thrown (since the effect is pretty much the same as a return), but beyond that its problematic. – Winston Ewert Mar 05 '13 at 21:27
  • 1
    @WinstonEwert Why are goto's bad? For instance, java has scoped gotos--they are only valid within that file and within a restricted area of code. Why not use it? Whatever arguments I can come up with against gotos certainly apply exceptions as well. What are your problems with goto's that make you feel the parallel is so unjust? – Bill K Mar 05 '13 at 22:19
  • 2
    @BillK, I'm pretty sure Java doesn't have scoped gotos. It does have labelled break statements, but I wouldn't call those gotos because they are more restricted than a true goto. As for gotos, their fault is that almost no situations are more clearly expressed using them. With exceptions, I argue that some situations are more clearly expressed using them. Hence if you assert, as the OP did, that exceptions are just gotos, I think you need to explain more of what you mean rather than just making that assertion. – Winston Ewert Mar 06 '13 at 01:10
  • 2
    @BillK, as I see it all control structures are deliberately limited versions of goto. If you want to complain about exceptions, its not enough to argue that its a version of goto, (everything is) you need to argue why its particular set of limitations are a bad idea. – Winston Ewert Mar 06 '13 at 01:28
  • 3
    @WinstonEwert You are right, goto can offer everything that the other structures can. The thing is limitations are the most important thing a language can offer. The point is not to write code to make something work--that's easy; the point is to make code that others can read and comprehend easily. A label coming into your code from elsewhere forces you to comprehend a lot of external flow whereas a structured loop limits what you have to comprehend. Exceptions are in-between. This comprehension problem is the only problem people have with Gotos, not that they are less functional or evil. – Bill K Mar 06 '13 at 21:54
  • 1
    @BillK, What makes you think that I think that gotos are less functional or evil? What I said was that "almost no situations are more clearly expressed using them." That's exactly what you are saying! We agree, gotos should be avoided because we have more readable alternatives. – Winston Ewert Mar 06 '13 at 22:01
  • 1
    @BillK, my objection is that you should say "exceptions produce unreadable code" rather than "exceptions are gotos." The readability is what matters. Whether or not something bears a resemblance to gotos is irrelevant. – Winston Ewert Mar 06 '13 at 22:04
  • 1
    @WinstonEwert That's pretty much what I said, I never said exceptions are gotos (Well, at least I'm pretty sure I didn't). My claim was that they were similar (had the same faults--which is, producing unreadable code). – Bill K Mar 06 '13 at 23:21
  • @BillK, the OP said that "exceptions are sophisticated gotos", your disagreement with MasonWheeler made it look like you were agreeing with that sentiment. – Winston Ewert Mar 06 '13 at 23:25
  • 25
    @mattnz: `if` and `foreach` are also sophisticated `GOTO`s. Frankly, I think the comparison with goto isn't helpful; it's almost like a derogatory term. GOTO usage isn't intrinsically evil - it has real problems, and exceptions *might* share those, but they might not. It would be more helpful to hear those problems. – Eamon Nerbonne Jan 11 '15 at 12:12
  • 7
    Exceptions *are* flow control. Any statement that suggest the contrary is self-contradictory. – user207421 Jun 16 '16 at 09:48
  • 4
    Sounds like comparing coding practices to use of `goto` is the new "hitler liked that too" argument. – Tomáš Zato Nov 21 '16 at 20:20
  • I always refer to this guide to see that ["yup, exceptions were made to make it *easier* to reason about code flow"](https://docs.oracle.com/javase/tutorial/essential/exceptions/advantages.html), any tool can be misused. – Zhuinden Mar 17 '19 at 19:10
  • 3
    Gotta retort to the `goto` comparison as a language nerd. You can implement `continue`, `break`, and `return` with a `goto`, hence they are merely syntax sugar for `goto`. You can implement `while` and `foreach` with a label, `if`, and `goto`, so they're also just sugar. Exceptions are a bit more complicated. They're a `goto` across function barriers. And if we're down to that level, even function calls are `goto`! (You can't implement `if` with `goto` though, @EamonNerbonne.) – TaylanKammer Apr 02 '19 at 13:36
  • @TaylanUB hence "sophisticated". That discussion isn't about the material, it's about how you squint. If you're going to call exceptions "sophisticated" goto,s then pretty much any control flow is. Which isn't a problem per se - but if you're going to distinguish maintainability and thus degree of anti-pattern-ness between goto and conventional control-flow, then you need to do the same and distinguish between gotos and exceptions. – Eamon Nerbonne Apr 28 '19 at 15:41
  • @TaylanUB - the sentence is shorthand for giving an intuitive idea of how to treat exceptions, and not meant to define technical terms. Sure, *technically* and if you really want to, you can call any control structure you want sophisticated GOTO's; it's just not a useful way to describe most of them. Exceptions stand out because it is a useful way to describe them; the difference between which control structures can be usefully described that way, is left as an exercise to the reader, with help from the external references. – blueberryfields Apr 28 '19 at 15:50
  • "Okay but seriously, what is up with server-side and app devs burying errors with empty catch statements in the JavaScript?". Its called "Why you don't let your graphic designer write server code". Its just straight up bad coding. And unfortunately like PHP, Javascript tends to attract a lot of self taught coders who never had a chance to learn the theory of why things are like they are. Although I'm not a fan of it, JS can be a powerful tool, but it needs to be used wisely or you'll end up with an absolute garbage fire. – Shayne Jul 15 '19 at 12:32
  • 1
    Ward's Wiki is alive, so I don't see a reason to use an archived version of the page. Here is the current one: https://wiki.c2.com/?DontUseExceptionsForFlowControl – user90726 Oct 10 '20 at 12:46
  • The first 2 reasons are just "poisoning the well" and goes onto criticize all exceptions, which doesn't answer the question – PandaWood Nov 02 '21 at 01:12
  • It would appear that Wards is having the debate as well so I don't see this as a fitting reference without it's own counterpoint: https://wiki.c2.com/?UseExceptionsInsteadOfErrorValues – b_levitt Jan 07 '22 at 14:18
37

Exceptions are as powerful as Continuations and GOTO. They are a universal control flow construct.

In some languages, they are the only universal control flow construct. JavaScript, for example, has neither Continuations nor GOTO, it doesn't even have Proper Tail Calls. So, if you want to implement sophisticated control flow in JavaScript, you have to use Exceptions.

The Microsoft Volta project was a (now discontinued) research project to compile arbitrary .NET code to JavaScript. .NET has Exceptions whose semantics don't exactly map to JavaScript's, but more importantly, it has Threads, and you have to map those somehow to JavaScript. Volta did this by implementing Volta Continuations using JavaScript Exceptions and then implement all .NET control flow constructs in terms of Volta Continuations. They had to use Exceptions as control flow, because there is no other control flow construct powerful enough.

You mentioned State Machines. SMs are trivial to implement with Proper Tail Calls: every state is a subroutine, every state transition is a subroutine call. SMs can also easily be implemented with GOTO or Coroutines or Continuations. However, Java doesn't have any of those four, but it does have Exceptions. So, it is perfectly acceptable to use those as control flow. (Well, actually, the correct choice would probably be to use a language with the proper control flow construct, but sometimes you may be stuck with Java.)

Jörg W Mittag
  • 101,921
  • 24
  • 218
  • 318
  • I believe JavaScript actually does have continuations, starting in version 1.7 (see https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Iterators_and_Generators); but since no one actually writes JavaScript proper, I'm not sure what that's useful for. :-P – ruakh Mar 05 '13 at 01:43
  • @ruakh: Aren't those Coroutines rather than Continuations? (Of course, it's highly likely that they are actually *implemented* with Continuations. Note also that they actually use Exceptions as flow control in the form of a `StopIteration` exception just like Python from where this feature was taken.) Anyway, the Volta project was several years ago and intended to work in *every* browser, so they couldn't rely on anything more than the cross-browser subset of ECMAScript 3.1. – Jörg W Mittag Mar 05 '13 at 03:39
  • 26
    @ErikReppen: I realize that you're just being sarcastic, but . . . I *really* don't think the fact that we "have dominated client-side web development with JavaScript" has anything to do with the language's features. It has a monopoly in that market, so has been able to get away with a lot of problems that cannot be written off with sarcasm. – ruakh Mar 05 '13 at 06:51
  • 1
    Tail recursion would've been a nice bonus (they're deprecating function features to have it in the future) but yes, I would say it won out against VB, Flash, Applets, etc... for feature-related reasons. If it didn't reduce complexity and normalize as handily as it does it would have had real competition at some point. I'm currently running Node.js to handle the rewriting of 21 config files for a hideous C# + Java stack and I know I'm not the only one out there doing that. It's very good at what it's good at. – Erik Reppen Mar 05 '13 at 14:33
  • 1
    Many languages have neither gotos (Goto considered harmful!), coroutines or continuations and implement perfectly valid flow control simply using ifs , whiles and function calls (or gosubs!). Most of these can in fact be used to emulate each other, just as a continuation can be used to represent all of the above. (For instance you can use a while to perform an if, even if its a stinky way to code). So no exceptions are NOT necessary to perform advanced flow control. – Shayne Nov 14 '14 at 03:05
  • 2
    @Shayne: those are not universal. It is not possible to implement all control flow in terms of one of them. You can't implement exceptions or threads or `GOTO` using just `if`. In fact, a language with `if` and `for` isn't even Turing-complete! (A language with only `while` and nothing else OTOH, *is*.) You say, exceptions aren't needed, then show how you would implement threads in ECMAScript using just subroutine calls, `if` and `while`. And note that Volta was written targetting ES3, so no WebWorkers, no Promises, no Proper Tailcalls. – Jörg W Mittag Nov 14 '14 at 03:24
  • BTW: I never said that exceptions are *needed* to perform advanced control flow. I said that they are *sufficient* and that sometimes they are all you have. If you have `GOTO`, you don't need exceptions or proper tail calls or coroutines or threads or continuations. And if you *do* need them, you build them on top of `GOTO`. Likewise, if you have continuations, you don't need `GOTO`. But if you want to compile a language like C# with threads, generators (basically coroutines), exceptions (which are not identical to ECMAScript exceptions), limited `GOTO` and other stuff into a language like … – Jörg W Mittag Nov 14 '14 at 03:29
  • … ECMAScript 3, then you have to work with what you got, and what you get are exceptions. In modern ECMAScript 6, you can probably do much better by using continuation-passing style and proper tailcalls, but ES3 didn't have proper tailcalls, they were only added in ES6. – Jörg W Mittag Nov 14 '14 at 03:32
  • 2
    Well yes you might be right about if. "For" in its C style form however can be used as an awkwardly stated while, and a while can then be used coupled with an if to implement a finite state machine that can then emulate all other flow control forms. Again, stinky way to code, but yeah. And again, goto considered harmful. (And I'd argue, so too with using exceptions in non exceptional circumstances). Keep in mind there are perfectly valid, and very powerful languages that provide neither goto nor exceptions and work just fine. – Shayne Nov 19 '14 at 04:38
  • 2
    I'm curios about how exceptions can be used to implement continuations .. – hasen Jan 11 '15 at 08:50
  • It's just not true that exceptions are required to manage "complex" control flows. It has been mathematically proved literally decades ago that if-else + (conditioned) loops + functions are enough, and languages such as Rust clearly prove the point (including multithreading). – castarco Jan 28 '21 at 09:26
25

As others have mentioned numerously, (e.g. in this Stack Overflow question), 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) (disclaimer: I work for the vendor), 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. Example:

    // 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() (e.g. 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. Example:

    // 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.

Conclusion

In essence, our usage of these non-local gotos is just along the lines of what Mason Wheeler 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.

Lukas Eder
  • 1,110
  • 3
  • 20
  • 31
20

Using exceptions for control flow is generally considered an anti-pattern, but there are exceptions (no pun intended).

It has been said a thousand times, that exceptions are meant for exceptional conditions. A broken database connection is an exceptional condition. A user entering letters in an input field that should only allow numbers is not.

A bug in your software that causes a function to be called with illegal arguments, e.g. null where not allows, is an exceptional condition.

By using exceptions for something that is not exceptional, you are using inappropriate abstractions for the problem you are trying to solve.

But there can also be a performance penalty. Some languages have more or less efficient exception handling implementation, so if your language of choice does not have efficient exception handling, it can be very costly, performance-wise*.

But other languages, for example Ruby, have an exception-like syntax for control flow. Exceptional situations are handled by the raise/rescue operators. But you can use throw/catch for exception-like control flow constructs**.

So, although exceptions are generally not used for control flow, your language of choice may have other idioms.

* En example of a performance costly use of exceptions: I was once set to optimize a poorly performing ASP.NET Web Form application. It turned out, that the rendering of a large table was calling int.Parse() on approx. a thousand empty strings on an average page, resulting in approx. a thousand exceptions being handled. By replacing the code with int.TryParse() I shaved off one second! For every single page request!

** This can be very confusing for a programmer coming to Ruby from other languages, as both throw and catch are keywords associated with exceptions in many other languages.

Pete
  • 8,916
  • 3
  • 41
  • 53
12

Programming is about work

I think the easiest way to answer this is to understand the progress OOP has made over the years. Everything done in OOP (and most programming paradigms, for that matter) is modeled around needing work done.

Every time a method is called, the caller is saying "I don't know how to do this work, but you do know how, so you do it for me."

This presented a difficulty: what happens when the called method generally knows how to do the work, but not always? We needed a way to communicate "I wanted to help you, I really did, but I just can't do that."

An early methodology to communicate this was to simply return a "garbage" value. Maybe you expect an positive integer, so the called method returns a negative number. Another way to accomplish this was to set an error value somewhere. Unfortunately, both ways resulted in boilerplate let-me-check-over-here-to-make-sure-everything's-kosher code. As things grow more complicated, this system falls apart (or must be very carefully managed).

An Exceptional Analogy

Let's say you have a carpenter, a plumber, and an electrician. You want to plumber to fix your sink, so he takes a look at it. It's not very useful if he tells only you, "Sorry, I can't fix it. It's broken." Hell, it's even worse if he were to take a look, leave, and mail you a letter saying he couldn't fix it. Now you have to check your mail before you even know he didn't do what you wanted.

What you would prefer is to have him tell you, "Look, I couldn't fix it because it seems like your pump isn't working."

With this information, you can conclude you want the electrician to take a look at the problem. Perhaps the electrician will find something related to carpenter, and you'll need to have the carpenter fix it.

Heck, you might not even know you need an electrician, you might not know who you need. You're just middle-management in a home repair business, and your focus is plumbing. So you tell you're boss about the problem, and then he tells the electrician to fix it.

This is what exceptions are modeling: complex failure modes in a decoupled fashion. The plumber doesn't need to know about electrician- he doesn't even need to know that someone up the chain can fix the problem. He just reports on the problem he encountered.

So... an anti-pattern?

Ok, so understanding the point of exceptions is the first step. The next is to understand what an anti-pattern is.

To qualify as an anti-pattern, it needs to

  • solve the problem
  • have definitively negative consequences

The first point is easily met- the system worked, right?

The second point is stickier. The primary reason for using exceptions as normal control flow is bad is because that's not their purpose. Any given piece of functionality in a program should have a relatively clear purpose, and co-opting that purpose leads to unnecessary confusion.

But that's not definitive harm. It's a poor way to do things, and weird, but an anti-pattern? No. Just... odd.

MirroredFate
  • 725
  • 8
  • 19
  • There's one bad consequence, at least in languages providing a full stack trace. As the code is heavily optimized with a lot of inlining, the real stack trace and the one a developer wants to see differ a lot and therefore the stack trace generation is costly. Overusing exceptions is very bad for performance in such languages (Java, C#). – maaartinus Jan 06 '17 at 04:03
  • "It's a poor way to do things" - Shouldn't that be enough to classify it as an anti-pattern? – Maybe_Factor Apr 05 '18 at 06:30
  • 1
    @Maybe_Factor Per the definition of an ant-pattern, no. – MirroredFate Apr 06 '18 at 17:31
  • @MirroredFate i like how you separated the problem it solves from the negatives. i think this is a pretty good. except for the last section about anti-pattern. you could mention lower performance as a negative consequence. you could also give a concrete example of exception as control flow, so that we are all on the same page. you could even give an example of exceptions fixing the the arrow anti-pattern, before the last section – symbiont Nov 17 '22 at 08:09
  • There are two exceptions, low level and high-level. A low level exception is one thrown by a file or keyboard io function further down the stack, for instance. A high level one catches a lower level one and rethrows it as is, or rethrows it with a more specific or detailed message which can be presented to the user. High level exceptions can be abused, but they are the best way of meaningfully reporting errors. – user148298 Aug 13 '23 at 03:44
9

It's completely possible to handle error conditions without the use of exceptions. Some languages, most notably C, don't even have exceptions, and people still manage to create quite complex applications with it. The reason exceptions are useful is they allow you to succinctly specify two essentially independent control flows in the same code: one if an error occurs and one if it doesn't. Without them, you end up with code all over the place that looks like this:

status = getValue(&inout);
if (status < 0)
{
    logError("message");
    return status;
}

doSomething(*inout);

Or equivalent in your language, like returning a tuple with one value as an error status, etc. Often people who point out how "expensive" exception handling is, neglect all the extra if statements like above that you are required to add if you don't use exceptions.

While this pattern happens to occur most often when handling errors or other "exceptional conditions," in my opinion if you start seeing boilerplate code like this in other circumstances, you have a pretty good argument for using exceptions. Depending on the situation and implementation, I can see exceptions being used validly in a state machine, because you have two orthogonal control flows: one that's changing the state and one for the events that occur within the states.

However, those situations are rare, and if you're going to make an exception (pun intended) to the rule, you had better be prepared to show its superiority to other solutions. A deviation without such justification is rightly called an anti-pattern.

Karl Bielefeldt
  • 146,727
  • 38
  • 279
  • 479
  • 2
    As long as those if statements only fail on 'exceptional' circumstances the branch prediction logic of modern CPUs make their cost negligible. This is one place where macros can actually help, as long as you're careful and don't try to do too much in them. – James Mar 05 '13 at 00:25
  • This doesn't answer the question at all. Many things are "completely possible" without X. – john16384 Feb 22 '21 at 21:34
7

In Python, exceptions are used for generator and iteration termination. Python has very efficient try/except blocks, but actually raising an exception has some overhead.

Due to lack of multi-level breaks or an goto statement in Python, I have at times used exceptions:

class GOTO(Exception):
  pass

try:
  # Do lots of stuff
  # in here with multiple exit points
  # each exit point does a "raise GOTO()"
except GOTO:
  pass
except Exception as e:
  #display error
gahooa
  • 2,111
  • 12
  • 16
  • 7
    If you have to terminate calculation somewhere in a deeply nested statement and go to some common continuation code, this particular path of execution can very probably be factored out as a function, with `return` in place of `goto`. – 9000 Mar 05 '13 at 00:40
  • 3
    @9000 I was about to comment exactly the same thing ... Keep `try:` blocks to 1 or 2 lines please, never `try: # Do lots of stuff`. – wim Mar 05 '13 at 03:57
  • 2
    @9000, in some cases, sure. But then you lose access to local variables, and you move your code yet-another-place, when the process is a coherent, linear process. – gahooa Mar 14 '13 at 05:45
  • 4
    @gahooa: I used to think like you. It was a sign of poor structure of my code. When I put more thought in it, I noticed that local contexts can be untangled and the whole mess made into short functions with few parameters, few lines of code, and very precise meaning. I never looked back. – 9000 Mar 14 '13 at 05:52
7

Let's sketch such an Exception usage:

The algorithm searches recursively till something is found. So coming back from recursion one has to check the result for being found, and return then, otherwise continue. And that repeatedly coming back from some recursion depth.

Besides needing an extra boolean found (to be packed in a class, where otherwise maybe only an int would have been returned), and for the recursion depth the same postlude happens.

Such an unwinding of a call stack is just what an exception is for. So it seems to me a non-goto-like, more immediate and appropriate means of coding. Not needed, rare usage, maybe bad style, but to-the-point. Comparable with the Prolog cut operation.

Joop Eggen
  • 2,011
  • 12
  • 10
  • Hmm, is the downvoting for the indeed contrary position, or for insufficient or short argumentation? I am really curious. – Joop Eggen Oct 20 '14 at 21:09
  • I'm facing exactly this predicament, I was hoping you could see what you think of my following question? Recursively using exceptions to accumulate the reason (message) for a top level exception http://codereview.stackexchange.com/questions/107862/throw-an-exception-which-contains-a-nested-set-of-previous-exceptions-rather-th – Jodes Oct 19 '15 at 14:36
  • @Jodes I have just read your interesting technique, but I am quite occupied for now. Hope someone else will shed her/his light on the issue. – Joop Eggen Oct 19 '15 at 15:01
0

The "pro-antipattern" argument is not very strong and is having it's own internal debate:

The same reference used to support the antipattern case can also be found to support the benefits of exceptions:

https://wiki.c2.com/?UseExceptionsInsteadOfErrorValues

Further, I believe the case made that it is an antipattern is pretty weak:

From the oft-referenced wiki: enter image description here

The best example is a for loop with no constraint? Who would do this?

On that very same page he admits to using exceptions for flow control:

enter image description here

"Flow control" by itself is overly generalized The term "flow control" has limited use as it describes the meat and potatoes of programming. There is no black and white here, only shades of gray.

Sure, it makes little sense to catch an exception raised by Parse when a TryParse exists. But that does not mean that the tester-doer pattern needs to apply to every method. Do we really need a TrySave()? Or can we say that there is some level of granularity that represents a tipping point for when we should be able to simply code for the successful case (should Save() simply continue if it failed??)?

In the absence of exceptions, what choice do you have? I guess if this is an antipattern but the same reference is indicating that return codes should be avoided (I consider true/false return codes), what are we supposed to do?

b_levitt
  • 201
  • 2
  • 5