Exceptions vs. Return Values to represent errors (in F#) – I – Conceptual view

Recently I’ve been reading numerous articles on the age old question of exceptions vs. return values. There is a vast literature on the topic with very passionate opinions on one side or the other. Below is my view on it.

First of all, I’ll define my terms.

  • Success code path: chunk of code that is responsible to perform the main task of a function, without any regard for error conditions
  • Contingency: an event that happens during the success code path execution that is of interest for the caller of the function.
    • It happens rarely
    • It can be described in terms that the caller understands, it doesn’t refer to the implementation of the function
    • The caller stands a chance to recover from it
  • Fault: an event that happens during the success code path execution that is not expected by the caller of the function.
    • It happens rarely
    • It cannot be described in terms that the caller understands, it requires reference to the implementation of the function
    • The caller has no way to recover from it

Examples of the above for a FetchUser(userName) function:

  • Success code path: the code that retrieves the user from the database
  • Contingency: the fact that the requested user is not in the database
  • Fault: userName = null, divide by zero, stack overflow, …

The difference between Contingency and Fault is not sharp in practice and requires common sense, but it is useful none less. When in doubt, it would appear prudent to consider an event as a Contingency, so that the caller gets a chance to recover.

Ideally, you would like a Contingency to be part of the signature of a function, so that the caller knows about it. On the other end, a Fault shouldn’t be part of the signature of a function for two reasons:

  • The user cannot recover from it
  • Being part of the signature would break encapsulation by exposing implementation details of the function

The above seems to suggest that Contingencies should be represented as return values and Faults as exceptions. As an aside, in Java the former is represented as checked exceptions, which is part of the signature. We’ll tackle checked exceptions later on.

An important point that is often neglected in the discussions on this topic is that there are two categories of applications: applications that care about Contingencies (Critical apps) and applications that don’t (Normal apps). I am of the opinion that the latter category is the largest.

In many cases you can indeed write just the success code path and, if anything goes wrong, you just clean up after yourself and exit. That is a perfectly reasonable thing to do for very many applications. You are trading off speed of development with stability.  Your application can be anywhere on that continuum.

Examples of Normal apps are: build scripts, utility applications, departmental applications where you can fix things quickly on the user machine, intranet web sites, internet web sites that are purely informative, etc …

Examples of Critical apps are: servers, databases, operating systems, web site that sell stuff,  etc …

For Normal apps, treating Contingencies as Fault is the right thing to do. You just slap a try … catch around your event loop/ thread/ process and you do your best to get the developer to fix the problem quickly. I think a lot of the angst of the ‘return value crowd’ is predicated on not having this distinction in mind. They are making very valid point regarding Critical apps to a crowd that is thinking about Normal apps. So the two sides are cross-talking.

Also, in my opinion, the main problem with Java checked exceptions is that they make writing Normal apps as cumbersome as writing Critical apps. So, reasonably, people complain.

The .NET framework decided to use Exceptions as the main way to convey both Faults and Contingencies. By doing so, it makes it easier to write Normal apps, but more difficult to write Critical apps.

For a Critical app or section of code, you’d want to:

  • Express contingencies in the function signature
  • Force/Encourage people to take an explicit decision at the calling site on how they want to manage both Contingencies and Faults for each function call

In the next post, let’s see how we can represent some of this in F#.

Advertisements

9 thoughts on “Exceptions vs. Return Values to represent errors (in F#) – I – Conceptual view

  1. Pingback: Dew Drop – November 19, 2012 (#1,445) | Alvin Ashcraft's Morning Dew

  2. Many APIs should arguably support both styles of error handing, in particular if those APIs are used as building-blocks for higher-level ones. Often this can be done rather effortlessly by providing an exception-throwing wrapper function for the non-throwing one. Or you can support both styles of error handling in the same function by accepting a parameter that determines the kind of error handling and that may also be used for returning an error code. If you want to make it explicit at every call site that a function can throw an exception, you could accept a special constant named e.g. “throwOnError” as a parameter.

  3. Pingback: F# Weekly #47, 2012 « Sergey Tihon's Blog

  4. Hi Luca, IMHO, talking about “critical vs normal” code is a bit inaccurate, if not dangerous.

    More precisely, we should talk about “reliable vs unreliable” code.
    Reliability is a feature like others: when we need it, we pay for it.

    Indeed to some applications, other features might be critical. To some, for example, “fast prototyping” is by far more critical. Moreover, to define “normal” you have to define a population.

    Then the question becomes: what makes “reliability” a critical feature of a software? Unsurprising, reply to this question deeply impacts on the related one: “which tools do allow us to provide reliability?”. Don’t you think?

    • Agreed. Apart from the dangerous part.
      IMO people don’t think of these topics very much when writing code.
      Even a simplified taxonomy as I propose is better than none.
      At least it is simple. Wait for the next installments. It gets worse from here :-)

  5. > “For Normal apps, treating Contingencies as Fault is the right thing to do.”

    This is good, as long as there is no catch (before the last exception handling and logging, usually provided by the framework). Then the error will actually be found. And it will be fixed.

    > “You just slap a try … catch around your event loop/ thread/ process and you do your best to get the developer to fix the problem quickly.”

    Typical mistakes:
    1) Catch may not clean up all the resources.
    2) Hide or eat the exception and lose some real user data… “On error resume next.”
    3) Catch will catch different exception that developer wanted
    4) Lose some data or stacktrace by converting exceptions

    But I assume this is a guide for senior developers, as there are many books for not-so-aware developers (to name a few: Cwalina & Abrams: Framework Design Guidelines, Richter: CLR via C#).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s