Clarity Through Error Handling in Swift 2.0

Logan Wright
5 min readOct 5, 2015

Beginning in Swift 2.0, we were introduced to an entirely new system for dealing with potential errors in our code. Within this new system, errors are now represented by the ErrorType protocol, which is simply an empty protocol that any type can conform to. This provides a great deal of flexibility, and opportunities to introduce clarity into our program’s potential fail points.

Hero Quest

For every demonstration, a hypothetical is required. For ours, let’s imagine that we’re building a simple game where a Hero is trying to save the kingdom. We’ll focus on a very small aspect of this game, picking up a new weapon.

Failure Points

There are many situations where a Hero may be unable to pickup a weapon. We want to abstract the possible failure points in a way that can be clearly communicated to a player as well as our future developers.

The WeaponError possibilities represented by an enum.

Right off the bat, a fellow dev can quickly understand what possible situations might cause our weapon pickup to fail. This information can also be easily forwarded to a user.

Model Our Game

Before we go any further, let’s add some of the other models necessary for our example.

The models necessary for our example.

These could be constructed a number of ways, but this provides a relatively succinct implementation that can be used to demonstrate some error handling.

Performing A Failable Action

Our models don’t do much yet, so let’s add a function that can be used by our Hero to equip a new Weapon.

A poor implementation of our weapon pickup function

It’s fairly obvious from the above implementation that there are many points of potential failure in this example and none of them are appropriately handled. These possibilities fail silently, which is only marginally better than breaking outright and crashing the app.

One can imagine how frustrating it might be for a user trying to equip a weapon only to have nothing happen. It’s easy to recognize this is a problem when considering user experience, but what about our fellow developers. By indicating potential failure points in our code, the Swift compiler will force us to deal with these errors. If these indications are also paired with useful insights as to why something failed, we can save hours debugging and ship safer, more stable code.

Facing Our Errors

Instead of ignoring our potential failure points, let’s give them the attention they deserve. First, the function is going to be marked with the keyword throws to indicate that it contains potential fail points, then it will utilize the WeaponError type defined earlier to define these fail points.

The throws keyword indicates something can go wrong, the throw keyword is used to it inform what went wrong.

A better implementation of our equipWeapon: function that provides clarity into its fail points.

This implementation has the same validation checks as our previous example, but instead of circumventing the failure points, they are thrown back to the caller in a way that can be easily caught and understood.

Try

Along with the rest of Swift’s new error handling system we’ve been given a new keyword, try. I think this is a very important feature, partially because of its honesty. The unit of work we want to do can’t be guaranteed to work, so we have to try it. Try statements are required to be encapsulated in a d0-catch statement as demonstrated below.

Notice first that the above example is an Orc trying to pickup an Elvish sword. This can’t work given the parameters of our game logic, so we can expect it to throw an error which will be accessible in the catch block. We could possibly check if the error is of WeaponEquip type within the scope of the catch block, but there’s a better way.

Pattern Matching

Let’s stick with our Orc hero, but instead, let’s use pattern matching to declare different catch blocks based on the type of error thrown. do-catch statements provide pattern matching in the same way that switch statements do, and we can use this to add more complex logic to our catching. The associated provided by the error can also be unwrapped and used within the scope of the catch block.

This example clearly accounts for all of the possible weapon pickup errors and provides very meaningful error messages to the user. As a player of the game, one can quickly understand why they’re unable to equip that weapon.

As a developer on this project, we can quickly understand why code may be failing, and handle that failure appropriately. By providing proper error handling and being clear about possible error conditions, the consumer is forced to deal with these possibilities instead of skirting around them.

Where Clause

In addition to general pattern matching, the where clause can provide additional logic based on the associated values of the error. Let’s look at a very contrived example that creates the possibility of a slightly more complex error handling scheme.

Wrapping Up

For those with a long history in Objective-C, this way of handling errors and composing application logic can seem strange at first. However, once we start to dig in and get used to them, their power to improve the stability and readability of a code base becomes clear. Like any framework, they’re not without limitations, but they’re a great first step towards safer, and more explicit programming in Swift.

You can read more about their potential here!

More

Feel free to follow me on twitter @logmaestro, and see what my colleagues and I are up to over at Intrepid.

--

--