Exceptions and abstraction

Tags:

So you already know how to handle your errors properly. Even if you’re already using exceptions, there are some nuances to the use of exceptions that are important to know and understand in order to write code that is easier to reuse and more decoupled.

Let’s talk about exceptions and how they relate to your classes and abstraction.

Classes and abstraction?

You might already know what I will be referring to when I talk about abstraction, but for the sake of this article let’s establish a base:

Say you have a class for database access. Maybe it’s called MysqlDatabase. You could also have a PostgreDatabase class in addition.

These two classes abstract how you access these specific databases. They both have a connect method which takes the same arguments – essentially, all you need to know is that this is a database class which conforms to the specific interface (maybe you have a separate IDatabase interface)

Okay, so what do exceptions have to do with this?

Your database classes throw exceptions when something goes wrong. Let’s say they throw instances of the standard Exception. When using these classes you catch this type of exception. Easy!

Let’s add another class to the mix. Say it’s some class which uses the database to fetch some specific sort of objects – let’s call it FooRepository. We would like to handle exceptions from the database and from the new class nicely, so we add a new exception type, MysqlException.

Now our MySQL class is throwing MySQL exceptions.

But now you decide to use PostgreSQL and you must change all your code to catch PostgreExceptions.

You also must change all code using FooRepository, because any code using it may have to deal with a database exception coming from this class.

Now that we know what will go wrong with our plan, how do we fix this?

There are two ways to improve this situation

  • Designing our exception classes better
  • Wrapping less relevant exceptions

Let’s look at these next…

Designing exception classes better

Firstly, to combat the issue we face when swapping the implementation of the database connection classes…

To fix this, we can use inheritance to our benefit: Create a base exception type, from which the implementation-specific exceptions inherit from.

Say we create a base DatabaseException class, and both MysqlException and PostgreException inherit from it. Now we can just catch a DatabaseException in our catch-block. Easy fix while keeping all the benefits.

Wrapping less relevant exceptions

When you have a class with a collaborator, or multiple collaborators, we face the problem of having to catch all sorts of exceptions in our user code. Our user code may not even care that the class we are using uses the database (or maybe the filesystem), and as such, we should do something to hide this fact in our exceptions too.

A very good example is that our FooRepository class uses the database, and as such, you would need to catch database exceptions when using it. But what if we changed the implementation of FooRepository so, that it uses a web service or perhaps the filesystem? We would again need to change all our code using this class to catch the new possible exceptions.

To fix this sort of thing, we must make sure to catch everything in FooRepository, and wrap them into a RepositoryException. This way code which uses the repository will only need to be aware of and catch the repository-specific exception.

In most languages, we can do something like this:

try {
    //Do something with the DB which may throw an exception
    $db->doSomething();
}
catch(DatabaseException $ex) {
    //Wrap the exception in another to keep abstraction
    throw new RepositoryException($ex);
}

Conclusion

To use exceptions in an efficient way we must consider the whole application and not just the immediate code.

Give your exceptions base classes they inherit from – this will make it easier to write reusable code, especially if you’re using something like the adapter pattern.

Make sure you catch and wrap exceptions of objects you use. This may seem a bit pointless occasionally, but as with using inheritance to your advantage, this will make your code easier to use.

Both of these approaches will also help in decoupling your code – you can make changes without having to change everything using the code.