Round Table #1: Should Exceptions Ever be Used for Flow Control?

I'm pleased to release our first ever round table, where we place a group of developers in a locked room (not really), and ask them to debate one another on a single topic. In this first entry, we discuss exceptions and flow control.

Round Table? Have you ever gone out to dinner with some developer friends, and found yourself engaging in long programming-specific discussions/debates? Well, now, we're bringing that format to Nettuts+. A group of friends casually debating one another on a given topic.

Should Exceptions Ever be Used for Flow Control?

Csaba Patkos
From my point of view, exceptions are situations in your code that you should never reach. By their name, they suggest exceptional, unexpected and uncontrollable situations. Throwing an exception should stop the execution of your code all together.

Pavan Podilla
There are three broad categories of "abnormal" condition in software, as defined by Bertrand Meyer: Failure, Exception and Error.

Failure is the inability of a software element to perform its function. Exception is an abnormal condition in software. Errors are due to unmet expectation/specification.

Errors cause Failures and are propagated, via Exceptions.


Jonathan Cuttrell
I like the idea of explaining what we consider to be exceptions. My idea of an exception is a named error class that can be handled in a try-catch statement.

So:

Catching exceptions, as Csaba mentioned, is engineered to be fringe, unexpected cases. These are often a result of malformed input or failed data transfer.

Control flow structures in most languages are optimized to handle known cases, whether that's via an if/else stack or a switch/case. Error throwing, in general, wouldn't be as optimized as control flow in most programming languages.


Pavan Podilla
So, essentially, exceptions are an "abstraction" purely to model the abnormality. There is a natural flow defined by most language runtimes, starting with the close to metal "C" language, or something as dynamic as "JavaScript" or "Ruby," which is to exit the runtime altogether, unless handled, via an "exception handler."

Csaba Patkos
Nice scientific definition Pavan. Jonathan, I agree. The part of the code / program / module that throws the exception should stop its execution.

The client of that code can use a try-catch to prevent the automatic propagation of the exception (as most languages will propagate it). However, I believe that try-catch should be used only for logic that is required to handle these unexpected situations. For example, a broken data transfer can be retried when the exception occurs. Or, some exceptions do not affect the outcome (such as when you want to create a directory that already exists). In this case, it can simply be suppressed.


Gabriel Manricks
So what's the use case we are talking about where it is used for control flow?

Jonathan Cutrell
Can someone who is for exceptions as control flow provide us with an example of code that conceptually is an acceptable scenario for your opinion?

Aaron Kuzemchak
Here's an example that kind of spawned a flame war on Reddit, which lead to this discussion.

Gabriel Manricks
I think that the use there seems perfectly valid. Is the argument against it that it's not as "optimized." Because it is much more verbose when it comes to reading your code, and you don't have to deal with numbers or constants in a switch statement.

Pavan Podilla
There is an underlying assumption in the code there: the calls to the service are synchronous. In the real world, I doubt if it is that straightforward.

Aaron Kuzemchak
Personally, I envision exceptions more as "objections." While they are typically used to represent errors, I think that they can be utilized to raise a flag against any kind of undesired result in your application and be handled specifically by type (if desired). As a simple example, attempting to log a user into an application could be handled on a more granular level than "was this attempt successful?" If not, why not? Perhaps the username didn't exist, or the password was incorrect. Maybe their account is banned. Throwing and catching exceptions in these scenarios can give a developer a lot more control over how they handle the result.

Pavan Podilla
Also worth noting is that exceptions are caught naturally in synchronous code and require "special propagation" in async code.

Gabriel Manricks
That is my opinion as-well. Exceptions do not have to be relative to the app, like it seems people were suggesting earlier. The app shouldn't end. It's an error related to the specific class.

Csaba Patkos
Hmm. That code is actually quite okay. Other than not being optimal and being quite lengthy, I see no problem with it. Probably, I would have created only a single exception and would have echoed the message from the exception ,rather than all those catches and hard-coded strings.

Gabriel Manricks
That's assuming that you just want to echo the error. But to handle each event separately, exceptions are a much cleaner route.

Pavan Podilla
Going back to the original topic for this discussion, should exceptions be used for handling "control flow"?

Csaba Patkos
And one more thing here: exceptions like this should be caught at some point and transformed into a friendly message to the user. You don't want you users to see .... PHP stack traces :)

Aaron Kuzemchak
Csaba, agreed. Although, in a webapp, I would want to have a global exception handler to show some styled "error" page as a fallback. And, in PHP, you would have display_errors turned off in production.

Pavan Podilla
Since we seem to have a common ground on when an exception makes sense (which is to notify bad situations), for normal control flow, it is not the first goto solution. It is easier to map the happy path with simple control constructs (if/else, while, switch/case, etc).

Csaba Patkos
Pavan, I think the biggest question right now is what one would understand by "flow control." The example given above simply has a bunch of echoes.

Aaron, exactly. What you've said does not contradict what I said about catching errors. Thanks for pointing out the more detailed solution.


Gabriel Manricks
That's why I think we need an example of when you think exceptions are wrong. It would be hilarious if we all agree, and it's an issue of definition.

Jonathan Cutrell
I think when you're talking about control flow, that the "expected" behavior for a login is a correct email and correct password. The fringe case is that a user doesn't enter these properly, or their account has been suspended, or some unknown server error occurs.

If the authentication fails, it fails because of fringe cases - bad input, or some problem outside of the actual authentication function. I think, in these instances, we're talking more about a definition of control flow and fringe case than whether or not exceptions are a good way to handle the process of logging in.

This is also an abstraction of the authentication function itself.

An acceptable alternative might be to return a login object that you could run an if/else control over.


Pavan Podilla
I think the auth example is a great one and can be modeled with and without exceptions. I vote for not using exceptions here - especially for more complicated (say 2 step) and distributed (oauth) authentication.

Jonathan Cutrell
Really, it's not about whether or not exceptions are "wrong" in a given scenario. It's about what is best for the job.

Gabriel Manricks
But Jonathan, surely, if you had a big list of cases like in the auth example, having a registered error handler would be quicker and more verbose then sifting through each option?

Csaba Patkos
And here comes the question, what is considered an exception, and what is just another situation that you have to deal with.

For example, in the authentication form, if the user name must be an email address and the user types in something else, that could be an exception.

But if the user fills in the form correctly and just the user/pass combination is not a match, that is more likely another case to treat, and not an exception.


Jonathan Cutrell
Then perhaps the object that is returned carries a message and a status.

Csaba Patkos
Jonathan, or you could throw the error in the last else!

Pavan Podilla
If we go async on the code, you will see a different style of propagating errors - especially, via callbacks, which will have all the details of the error.

NodeJS has made a name for itself in this regard, and, for that matter, any "event-driven" runtime, like EventMachine or Twisted.


Gabriel Manricks
So you are for it, Pavan?

Csaba Patkos
Pavan, I am not that familiar with asynchronous code. Can you explain how exceptions work there in more detail?

Pavan Podilla
So I was trying to point out that we are talking about control flow in general, and not just error conditions. I think exceptions are not suitable for general control flow.

Csaba, here is a classic async code example:

Let's say that you are trying to read a file: /usr/local/app.log. The way you do it in Node is:

Because of the callback, you don't put try/catch around the call. Instead, you use a callback style to handle the result. I hope that makes it clear. In general, any operation that cannot be performed synchronously will have a callback style API to handle the results.


Gabriel Manricks
I still don't think anyone has mentioned an example where exceptions are used incorrectly. I don't fully understand the counter example.

Aaron Kuzemchak
Yes, I would like to see an example of exceptions being used for control flow and the explanations as to where the bad points are... as well as how it would be rewritten. That should give us all some common ground to work with.

Jonathan Cutrell
Pavan, very true. I think, conceptually, the idea is the same, however, when we talk about things like jQuery's AJAX implementation, in which you can define .error(), .success(), etc. This is an abstraction from what's really going on (checking the XHR object).

While this isn't either exception or if/else, it still solves the problem: handle both successful and non-successful AJAX requests. However, the jQuery implementation itself is not try/catch, because it is asynchronous.


Pavan Podilla
Going back to our auth example, if we were to do it async, where the auth service is remotely located and talks to several other backend services to perform multi-step authentication, then an exception style API will not work. An alternative approach is given below:

Note that we are using the concept of a system-wide eventbus to propagate messages. In this case, we will have a success or failure message with the appropriate payload. This kind of message passing is common in distributed systems and is a great way to have a control flow that spreads across machines.


Jonathan Cutrell
As an add-on to that, this is also pertinent, not only to cross-machine, but also to multi-threaded environments, which is in some ways conceptually similar to async. (Correct me if I'm wrong on that, multi-thread gurus.)

Gabriel Manricks
Isn't that code just passing the buck? Somewhere, there needs to be some code that performs an unsafe operation and needs to handle the exception and set the error.

Pavan Podilla
Gabriel, that's exactly what's happening and is an example of control flow across different software boundaries.

Other more generally understood cross-boundary control flow is none other than the venerable "Email" and "SMS" messages. It may not be obvious at first glance, but a little introspection and you will see how it is control flow of a different kind, and not done via exceptions.

You can disagree or raise hell in an email, but the receiver is told in a message that may arrive much later than the time it was sent (and may be too late).


Gabriel Manricks
Correct me if I'm wrong, but my understanding is that you guys are saying the following is bad:

Pavan Podilla
Gabriel, if the call to connectToDatabase is synchronous, exceptions will work. Otherwise, you need callbacks. Also, there could be several forms of failures (different classes of Exceptions). Do you care what kind of failure it is - especially if you are logging it somewhere?

Jonathan Cutrell
Also, what about uncaught exceptions in that example? (Not sure what they would be at the moment, but shouldn't there be a way to catch everything?) In an if/else block, the final else handles all remaining cases.

Pavan Podilla
Also, it's more useful to understand how you propagate errors in your software stack. I assume a LoginException has to be notified to the user?

Gabriel Manricks
Potentially, they can all follow a protocol and have a function, like $e->handleIt().

I happened to have picked a bad example, because, in these situations, you basically need to get new data from the user. But, conceptually, you can have full logic, like:

Obviously, this isn't the best example and you would probably want to determine if a port is already there, instead of infinitely appending them. But what I'm saying is to use exceptions for actual code, besides simply "alerting" the user.


Aaron Kuzemchak
In PHP, you can either use set_exception_handler() to handle any uncaught exceptions. You can also have a catch (Exception $e) block as the last catch statement in the control flow, since all exceptions in PHP extend the native Exception class.

Gabriel Manricks
I think one of the bonuses to using exceptions instead of an if statement is you will get a detailed error if you forget to accommodate one, whereas with an if statement, it would merely default to the else.

Csaba Patkos
Gabriel, I think that, in your example, it is a sensible solution to allow using logic when you catch the connection problem. Maybe you want to retry the connection because your network is unreliable. But if the user can not authenticate over an already existing connection, then something is very wrong. You don't want to retry on that one.

Aaron Kuzemchak
Still not a great example, but here's a demonstration of handling different scenarios:

Pavan Podilla
But with most runtimes, exceptions are seen with special lenses and there is some loss of "control" because the runtime directs the exception in a certain order from the nearest "exception handler" to the top-most exception handler - and, ultimately, to the default runtime's uncaught exception handler. You lose the contextual information as you go further away and you have to think hard about who should be handling it.

Eventually, even if you do decide where it should be handled, debugging is still a problem. If you revisit the code after a few months, you will have little clue what is going on.

The point that I am trying to make is, with control flows of any kind, you are building an implicit state machine and you should strive hard to keep all possible states of control localized in your code. With exceptions, that can be difficult.


Csaba Patkos
On the other hand, as Gabriel mentioned, exceptions are a great way to communicate problems in detail. If you decide to not use them at all, and just use return in your functions to communicate both good and bad scenarios, you may and up with some very unpredictable functions. This is mostly a problem with dynamically-typed languages like PHP, where even the some built-in functions have this problem. For some reason, the people who made them decided that, for example, a function returns a string or an array on success, and false or -1 on failure.

Aaron Kuzemchak
Pavan, PHP allows for nested exceptions; so you could throw another and reference the previous exception, allowing for a more granular look in the trace of what happened.

Csaba Patkos
Aaron, about your example: I would consider the wrong user and password to be simple paths my application may go down, not an exception. All other login-specific exceptions, like account locked and account expired, may be threatened by the same handling functions.

Gabriel Manricks
Csaba, but why would you nest control statements in exceptions? While your at it, would it be neater to simply handle each one separately (unless, of course, they have the same outcome)?

Pavan Podilla
I think I was little unclear about exception handling in my earlier post. The point I was attempting to make was that exception handlers spread in multiple layers of your software stack. In this case, an exception in your DB layer, which ultimately needs to be propagated to the user in the form of a "failed to login/connect" message.

There has to be a couple exception handlers before it reaches the user, starting from your DB layer to your web layer, and finally rendering it on the client. If you notice the control flow, which is currently spread across many layers, it can be difficult to handle smoothly. What's been your experience in this regard?


Gabriel Manricks
I would definitely not allow exceptions to be taken out of a class. That is where the status variable can come in handy. That's what I was referring to earlier, when I mentioned the whole "passing the buck" thing. I agree that, at that point, it is important to use control statements and status variables. But, internally, Exceptions are fine for code flow.

Csaba Patkos
Gabriel, I wanted to point out that I would throw exceptions only when there is something exceptional happening. The user failing to correctly fill in his/her username and password is not an exception in my eyes. It's simply another situation that my program has to deal with.

As for propagating exceptions between layers. In the project I work on at my job, most exceptions thrown are automatically propagated, and caught very close to the UI, at which point a message is presented to the user.

In other cases, such as creating a directory that already exists, the exception is simply caught by the client code, and discarded with something along the lines of:

Of course, other exceptions, like NoPermissionToCreateDirectory will be propagated. I think this is a good example of controlling flow based on an exception.


Pavan Podilla
The only control flow that we have seen so far is a failure case. Would exceptions still be used for normal control flow? For example, a batch process that is applying some transformation on an image. There can be multiple transforms in a pipeline for an image. I begin with 100 images, and 100 transformed images come out after the batch process. How might you model this program?

Gabriel Manricks
Pavan, where would you put a control statement? Are you talking image in -> process -> image out?

Pavan Podilla
The transformation on an image can be one of, say, ten different types. Each image has a specification about the series of transforms that should be performed, and in what order. The image also has a path pointing to the file. The "process" block takes in one image at a time and applies these transforms in order. At the end, the transformed image is collected in some output location.

The user needs to be notified about the progress of the batch process, and eventually alerted when the process is complete. There could be failures, like invalid file, invalid transform, not enough disk space to store the file, etc.

Do you see a control flow here, more like a factory assembly line? How will this be modeled with exceptions, and with regular control constructs?


Csaba Patkos
In a simplified way, not with all the details you specified, here is my first take on the image processing problem:

Gabriel Manricks
This is mostly pseudo code, but I would call this a valid programming style. It demonstrates using exceptions for error reporting, as well as code flow and propagation.

Jonathan Cutrell
That's pretty interesting, Gabriel. I think one danger is the looping retry without any input from the user, but I'm assuming that's just an example more than a real practice.

The real question here is really about what kinds of cases are considered exceptions. We could easily rewrite this to localize the errors, which would reduce the time needed to identify error source. Of course, with this example, it wouldn't be too difficult to identify a source.


Gabriel Manricks
Jonathan, after sleeping on it, I realized that I could further localize it by moving the loading of the image data into the image class, itself. This would clean up the double try statement, and keep in line with the transformations.

Ben Corlett
Being one of the main authors for Sentry I thought I'd throw my two cents in about why we did what we did in Sentry and my thoughts on Exceptions in control flow.

When authenticating a user, there essentially two main ways we could have tackled it:

  1. try/catch
  2. if/else

Let me explain the ways that we could have tackled if/else:

But, what happens if we want to find out a little more information than a simple "Nope, you're not allowed"? Getting an object back is a great approach:

This has some advantages, primarily due to the switch statement:

However, exceptions give a lot more control because you can let them be handled at any level within your application. Exceptions can also extend each other, while all extend the base Exception class (obviously talking PHP here).

A downside to the Exceptions used (particularly with Sentry) is the verbosity of them. This is because we have separated out the different components within Sentry (groups / users / throttling) so that you can take the components you want and build a totally kickass auth system. So, everything that belongs to the 'users' component of Sentry sits in the Cartalyst\Sentry\Users namespace. A dead-simple way to decrease verbosity is either through the use keyword: use Cartalyst\Sentry\Users\LoginRequiredException;. Or, of course, you can go ahead and add a class_alias() for global aliasing of the class. All of the sudden, we bring the verbosity down to (and with some practical examples):

Verbosity is one downside to try/catch, but it can be decreased through the use of use (bad wording there, right?) and class aliases.

Let's consider the positives:

  • Logic can be handled at any level of the app or through a custom registered handler (at least in PHP).
  • try/catch are "low-level". I mean this in the sense that they don't really change. In PHP, there is always $e->getMessage() and $e->getCode() (due to inheritence from "Exception"). If I return an object to (such as $response->hasError()), the developer needs to know the exposed API for that object. Also, the object may change in the future. try/catch is a syntax which I don't see changing. It's intuitive.
  • The only real alternative to having multiple catches (with a catch-all) is switch. But the verbosity of a switch statemtn is much the same as try/catch.
  • Mixing true/false and try/catch in the same statement is a recipe for confusion. As @philsturgeon said so well "With a mailer for example, a LOT of things can go wrong in the sending of an email, so you want to throw exceptions if the email fails to contact the SMTP server, if it fails to include a from address, if it cannot find the sendmail install, whatever. What if it doesnt have a valid email address? Is that an exception, or should it return false and make you look up an error code? Why half-and-half it?"
  • In PHP, there's no such (real) thing as asynchronous. Before you all jump on my back about spawning processes and all that jank, PHP doesn't really support it. I don't see how using a callback can really improve the application (reminder: I'm talking PHP) or the user experience, as you can achieve the same "progress" feedback (through an app polling your script) throughout the process of whatever happens in the "try" block. 10 points for the worst explanation there, but I think the point still comes across. You can do everything the same through a try/catch as you can using a callback in a single-thread language.

I think, at the end of the day, it comes down to different use-cases. Some may argue that it's the same as "tabs vs spaces," but I don't believe it is. I think there are scenarios when an if/else is appropriate, but, if there is more than one possible non-successful outcome, I believe that a try/catch is usually the best approach.

Here's two more examples that we could discuss:

And, for another example, in Laravel 4, there is a validation class. It works like so:

What if it worked like this?

Just food for thought. The first one reads "prettier," but the second one could be seen as optimal. Any thoughts regarding this?


Gabriel Manricks
I agree with Ben; there is always a right and wrong way to do something. It's not like every if statement should now be replaced with an exception. But they are far more "accurate" and readable than, for example. a function that returns -1 or false.

Jonathan Cutrell
I think the most pertinent point here is the "multiple bad outcomes" argument. I think that is an important value-based judgement we should consider.

Perhaps exceptions, then, are best suited for when something goes wrong by definition. For instance, when there is input error, when there is server error, etcetera.

Maybe a bad use-case scenario is catching exceptions for things that are expected and good cases. I certainly agree with what Ben laid out, particularly when it comes to what I have now (un)officially trademarked the Un-informed else block™. (You may affectionately refer to it as UEB™.) This poor catch-all doesn't have anything good to tell us, and unfortunately gets hit with all the bad, with little-to-no recourse. What a shame.

And, thus, we may happen upon an answer! To keep all of our control flow informed, and to use exceptions for things that are, indeed, exceptions.

I should be able to try to log in a user, and know why it failed. Now, the question is, how? I think the answer, as we have all agreed, is that it simply depends.


Gabriel Manricks
I agree with Jonathan. Using exceptions for a good case would be wrong. For instance:

So my opinion is to use them for errors. Not limited to "unexpected" errors, but any errors.

Also a point worth adding is that it depends on what you are doing - whether you are performing an action or checking something.

In my Gist above, I had a method validate, and, within the code, I wrote something like:

I could have thrown the exception inside the validate method, but that would be destructive to the code's readability. You would have something like:

This makes it unclear what is going on. To summarize my point of view, I would say: use Exceptions when performing an action, and use if statements when verifying data.

It also works out syntax-wise, because you can say "if (x) then y", when you are checking data, and try { x } catch (a snag) for actions. It wouldn't be grammatically correct to interchange them.


Aaron Kuzemchak
I'm in total agreement in saying that exceptions should never be used for a positive result. For me, it boils down to these two scenarios:
  • There are multiple points of failure in a particular function/method.
  • There is a need to discern the difference between those points of failure and handle them in more than one way.

Csaba Patkos
I disagree, especially with the assumption that using only exceptions in the authentication example makes the code more readable, or more obvious.

I think the problem is that some people consider the language an impediment to expressing their ideas. Thus, a mix of if/else with try/catch makes the code harder to understand.

The problem, as I see it, is the exact opposite. The code should reflect the concepts that you want to implement. Using try/catch for everything, except for the happy path, hides a great deal of execution logic details. Anything becomes white or black.

On the other hand, if you use if/else for the cases when your application goes down paths that are part of its logic (like wrong username - passord pair), and then try/catch for the situations when your application gets into an unexpected / unrecoverable state, it would be much more obvious what the possible behavior and paths of execution are.


Aaron Kuzemchak
The original reason why this got me thinking in the first place was because of the knee-jerk, "NO" responses.
  • Should I ever use JavaScript as a server-side language? No, because that's not what it was designed for.
  • Should I use LESS to write CSS? No, because it might confuse someone.
  • Is it ever okay to use static methods? No, it is never okay.

All extreme and not constructive.

I think that by presenting valid reasoning from both sides, we reach an even more important conclusion: not condemning other developers for not seeing eye-to-eye with ourselves. If any of you hand me some code, I'm not going to complain about your decision to use exceptions or not. I just want you to be consistent in your approach, and you better comment it! :)

I doubt this will change anything, but most developers could use some more humility and empathy - me especially. That's at least my goal in this: not to convince anyone that my way is the way, or even a better way, but to demonstrate that you shouldn't tell me that my way is flat-out wrong, because maybe we're not looking at it the same way.


Pavan Podilla
I think we have a group consensus on using Exceptions only for handling the error/failure cases. It should never be used for normal control flow.

It is worth noting why Exceptions are particularly suited for propagating errors. It's all about context and making sure this context is passed on to wherever is best to handle the exception. Most runtimes have an automatic way of bubbling up the exception and that makes it very convenient to inject exception handlers at the right places in the software stack.

You cannot define a bubbling route for exceptions using any language construct. The only possibility is to raise or throw them. It's the runtime's responsibility to do the bubbling. This implicit mechanism makes exceptions the ideal abstraction for propagating errors.


Your Turn

So that's what we have to say on this subject. What are your views? Can an argument be made for using exceptions for flow control?

Tags:

Comments

Related Articles