Exception handling explained
Tags: exceptions, errors
Published: 2018-05-10 11:33:24

---
title: Exception handling explained
published: true
description: This article explains what exception handling is and how it differs from traditional error handling. The article does not get into usage or exception classes, but only to explain their purpose.
tags: exceptions,error-handling
---

This article explains what exception handling is and how it differs from
traditional error handling with error codes. The article does not get
into usage or exception classes, but only to explain their purpose.

Let's start with errors.

# A brief introduction to error

From the dawn of programming, error codes have been used to deal with errors in applications. An error code is used to indicate if the
executed function was successful or not.

Here is a simple example:

```csharp
public bool SaveDefaultAccount(string userName, string accountName)
{
    int userId = GetUserIdFromName(userName);
    if (userId == -1)
        return false;

    int accountId = GetAccountFromName(accountName);
    if (accountId == -1)
        return false;

    return StoreDefaultAccount(userId, accountId);
}
```

The example illustrates that when you use error codes, it is the API
consumer that must abort if something fails. You might, but how about
the rest of the team, or those who maintained the application before
you? It is like you would retire the entire police force and expect
***all*** citizens to behave and be good law abiding citizens.

One developer could have been lazy and just written (or refactored) the
code as:

```csharp
public bool SaveDefaultAccount(string userName, string accountName)
{
    int userId = GetUserIdFromName("Arne");
    int accountId = GetAccountFromName("Savings account");
    return StoreDefaultAccount(userId, accountId);
}
``` 

The problem is that the code looks perfectly legal, but silently ignores
errors. If you for instance mistakenly add a white space after all
account names on the account selection page, the code above starts to
fail silently. What's worse is that you will not know about it until
code dependent upon the default account starts to fail, and that can be
after a while. Tracking down that subsequent error can be challenging,
primarily if the default account is set in different ways.

Error codes are easy to get started with and can be quite powerful. Even
modern languages like Go-lang uses errors instead of exceptions. Here is
a go snippet:

```csharp
f, err := os.Open("filename.ext")
if err != nil {
    log.Fatal(err)
}
```

While errors are easy to understand and use, they have three
significant drawbacks:

## They do not convey context

*This section is for languages that only uses error codes.*

For instance, error code 2 means "File Not Found" in Windows. There is
no way to state which file nor other information that might help you to
understand why the file was missing. Error codes are just that. Codes
that indicate a specific error, without context or clues.

The problem with that is that it is hard to understand why or under what
circumstances that the error occurred. The Windows API solves this by
introducing a method called `GetLastError()` which is used to get more
information about the error.

```csharp
OFSTRUCT buffer;
HFILE hFile = OpenFile("d:\\sample.txt", &buffer, OF_READ);
if (hFile == HFILE_ERROR)
{
    // all this is required to get the error message.
    // you typically add it to a separate function

    // Get the error code, as hFile only indicates an error
    // but not which one.
    var errorCode = GetLastError();

    // Get the generic error message which
    // represents the above error code.
    LPVOID lpMsgBuf;
    LPVOID lpDisplayBuf;
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        errorCode,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL );
}
```

My point is that an error code is not enough when you want to
*solve* the error.

## The burden is on the function caller

It is safe to say that all applications have errors, it is exceptionally
rare (pun intended) that errors can be ignored. Ignoring errors might
seem to work, but sooner or later consequential problems will surface.
It will be much harder to find the root cause since the found error is
just a consequence of the first one. Any kind of "fix" is just a
workaround which clutters the code base but does not prevent the root
cause from happening again.

It is crucial that all errors are handled in your code. When you use
error codes, it is so easy to ignore or forget errors. Had a tight
deadline? Wrestled with an obscure bug or incomprehensible requirements?
Those situations make it so easy to take a shortcut. If not all
developers on your team have the same discipline, errors will get
ignored.

# Enter exceptions

Exceptions are for exceptional situations. If something didn't go as
expected, you got an exceptional situation.

Sounds easy, huh?

But what does that mean?

## An exceptional example

If you expect that something can fail, you should guard against that
situation.

Here is an example:

Let's say that you love shopping the newest and hottest technical
gadgets. When the new gadget is released, you want to be first.

![](https://thepracticaldev.s3.amazonaws.com/i/o83dfiz9eacxmggbl7pf.jpg)


### Scenario 1

If you are like most of us, you can probably not just go on a shopping
spree. You need to make sure that you have enough money.

1.  Check your bank account.
2.  Go shopping.
3.  Pay

### Scenario 2

However, if you are fortunate enough to have plenty of money, you can
go shopping directly:

1.  Go shopping.
2.  Pay

### The difference

In the first scenario, we have a known issue that we need to deal with:
A money limit. Therefore, we always need to check that we have enough
money. In the second scenario, we should have enough money.

What happens if someone has hacked us in scenario 2:

1.  Someone hacked us
2.  Go shopping
3.  Pay <-- Failed, no money

In that case, we got an exception, since it is a case that shouldn't
happen since we ***expect*** to have enough money.

That is an exceptional situation.

### *Why can't we always write the code like in scenario 1?*

First, it's about communicating intent. We mostly get a set of business
requirements that we should fulfill. They tell us what to expect when
implementing different use cases. If we go and add many checks for
things that might, but should not, happen we lose the connection between
our use cases and the code. It will be hard to tell the intent of the
code, which in turn lead to assumptions and in the end decreased code
quality.

Second, if we add many checks we are coding workarounds as the real
problem is that our account was hacked, not that we could not pay. By
adding checks and abort instead of paying we are hiding that fact.

## What exceptions are

Exceptions are for situations that wasn't considered when defining what
the application should do. When writing a messaging library for message
queues you expect to receive complete messages, but when you write one
for TCP you expect to receive partial messages. What's exceptional in
one case doesn't necessarily have to be exceptional in another.

If you would open a file there are several errors that can happen.
Non-existent directory, file is missing, access denied, partial file,
etc. All those errors are known to most programmers, so they are not
exceptions, right?

Wrong. In most cases, they are exceptions. Because you typically do
expect that a file exists and that it's complete and readable. Well, if
you are writing a data forensics application, most of those errors are
expected and should be dealt with (and therefore not exceptional cases).

### Exceptions exist to prevent your application from doing something stupid.

It's crucial for you to understand that. Don't think of exceptions as
something you can use to control your application when coding. Think of
exceptions as a mechanism to guard against your application doing
something wrong/unexpected.

### Exceptions exist to help you fix problems in the future

Since exceptions are not a flow control mechanism, they do add little
value when executing your code (previously described point excluded).

However, writing informative exception messages makes it much easier to
correct future bugs, since they add context to the error. Always try to
do so, your future self will thank you for it.

## Code example

Let's take the same code as was found in the beginning of this article,
but changed to use exception handling instead.

```csharp
public void SaveDefaultAccount(string userName, string accountName)
{
    int userId = GetUserIdFromName("Arne");
    int accountId = GetAccountFromName("Savings account");
    StoreDefaultAccount(userId, accountId);
}
```

As you can see, the method now returns void as it does not need to
indicate that everything went successfully. Nor does it need to validate
error codes from the called methods. One can safely assume that an
exception abort the processing if the expected result cannot be
guaranteed.

In fact, in most cases, we do not have to care if exceptions are thrown
at all. Remember, exceptions are used to communicate that something
unexpected happened. If we cannot predict it, how on earth could we be
able to handle the exception?

Let's look at the `StoreDefaultAccount` method. The most important thing
to understand is that the method says that a default account should be
stored successfully. Since that is the method promise, we must throw an
exception every time we find something that would prevent the default
account from being stored.

```csharp
public void StoreDefaultAccount(int userId, int accountId)
{
    if (userId <= 0)
        throw new ArgumentOutOfRangeException(nameof(userId), userId,
            "A valid user id must be specified.");
    if (accountId <= 0)
        throw new ArgumentOutOfRangeException(nameof(accountId), accountId,
            "A valid account id must be specified.");

    var account = _accountRepository.GetById(accountId);
    if (account.OwnerId != accountId)
        throw new InvalidOperationException($"User {userId} do not own account {accountId}.");

    var user = _userRepository.GetById(userId);
    user.DefaultAccount = accountId;
    _userRepository.Update(user);
}
```

The first two exceptions are used to mitigate errors like parse errors
or invalid data.

The third exception is for a business rule. We may only use the user's
own accounts as default accounts.

`_accountRepository.GetById(accountId);` will in turn throw an
exception if the given accountId do not exist in the database since the
method name states that an account should be fetched.

# Summary

The purpose of exceptions is not to allow you to take different actions
depending on if something failed or not. i.e. they are not a flow
control mechanism. Instead, exceptions are used to make sure that your
application delivers the expected result (or die trying).

With that in mind, I hope that you find them as useful as I do. With
the right mindset (and using exceptions) you can save much time since
you do not have to track down why your database has a lot of data
inconsistencies (which leads to bugs later).

*This article is part of our exception handling series. To learn more, visit our [website](https://coderr.io/exception-handling/).*